Tags: 技術日記

技術日記
CSRFについてかみ砕いて説明してみるEdit

CSRFとか、わかりにくい言葉をわかりにくいままに使っているから、いまいちちゃんと知識が広まらないし、対策もひろまらないんじゃないか。

CSRFを「よそのサイトの機能を呼び出す攻撃」とかひらいて表記するようにすれば、もうちょっと雰囲気が伝わるんじゃなかろうか。

よそのサイトの機能ってのは、ごく普通のサイトだったら「メール送信フォーム」とか「掲示板」とか「コメント投稿」とか、そんなもののこと。

高機能なサイト、たとえばmixiとかFacebookとかのSNSなんかだと、友達登録とかメッセージとかいろんな機能がある。

そういう機能は、サーバー上で動作するプログラムによって動いているんだけど、サーバー上のプログラムに指示を出すのは、Webブラウザからのリクエスト。

Webブラウザからサーバーにリクエストを出す一番簡単な方法は、URLを呼び出すこと。

ブラウザのURL欄にごちゃごちゃ書いてリターンキーを押すと、そのURLによって指定されたサーバーに、リクエストが送られる。

一番よく使われるリクエストは、そのURLにあるコンテンツ(テキスト、画像、ムービー)をくれ、というリクエスト。普通にWebページを見るために使われる機能ね。

ごくごく単純な場合は、サーバーはリクエストされたURLに対応するサーバー上のファイル(テキスト、画像、ムービー)をそのまま返してくれる。

ただし、単にURLを呼び出すだけでも、サーバー上では何らかの特別な機能を実行する場合もある。

たとえば、

http://example.com/mail_send.php?subject=test&to=someone@example.com&body=hello

みたいなURLを開くだけで、「someone@example.com」というメールアドレスに、「test」というタイトルで本文が「hello」というメールを送信するプログラムなんてものもかんたんに作れる。

セキュリティってもの全く考慮せず、「自由に宛先とかタイトルとか本文とかを指定してメールを送るプログラムをサイト上に作れ」とか言われたら、そういうものができあがる。

もちろんそれに合わせて、subject、to、bodyを入力するフォームもサイト上には用意されるだろうけれども、入力フォームは、メール送信機能そのものではなく、メール送信機能に渡すパラメータを決めているだけだ。

入力フォームなんてものがなくても、上記のURLを開けばそれだけでメールは送信されるし、パラメータの内容はフォームがなくてもかんたんに変更できる。

そんなのがそこらに放置していると、上のURLの中のsubject、to、bodyのところを書き換えれば、自分の好きなところに好きな内容のメールが送れてしまうことになる。

じゃあ、そうやって自分が送りたいメールの内容に書き換えたURLを、試しにそこらの掲示板にでも貼り付けてみよう。「すごくおもしろいゲームを見つけたよ!」とかコメントを添えて。

そのURLをクリックする人がいれば、その数だけメールが送信されてしまうことになる。

もちろんそのURLを呼び出した=メール送信機能を実行したIPアドレスは、そのリンクをクリックした人間のものになる。

ただし、上記のようなURLが直接ブラウザで開かれた場合、その結果としてはメール送信プラグラムの実行結果ページが表示されるので、メール送信が実行されたことに気づくことができるだろう。

しかしそれをごまかす方法はいくつもある。

URLを開く方法として、普通の人が普通に知っているのは、リンクをクリックしたり、Webブラウザのアドレス欄にURLを書くことだろうけれども、それ以外にもいろいろな方法がある。

たとえば、HTMLを知っている人ならば、<img src="http://example.com/img/picture.jpg"&gt;というHTMLを書けば、http://example.com/img/picture.jpgに置かれている画像が表示されることは知っているだろう

これは、WebブラウザのURL欄に書くのと同様に、http://example.com/img/picture.jpgを呼び出している。

つまりHTML中に、<img src="http://example.com/mail_send.php?subject=test&amp;to=someone@example.com&amp;body=hello"&gt;と書けば、それが書かれたHTMLを開くと、自動的にメールが送信されることになる。

でも、そんな風に書いたら画像が呼び出されくて、画像が表示されないエラーが表示されるから、すぐにわかるよね、と思う人もいるかもしれない。

じゃあ、<img src="http://example.com/mail_send.php?subject=test&amp;to=someone@example.com&amp;body=hello" style="display:none;">とかかれていたらどうだろう。

最後の「display: none;」というのは「表示しないでね」という指定だ。これならば、画像読み込みのエラーが表示されることはない。

それ以外にも外部のURLを自動的に開くHTML表現はたくさんあるので、imgタグのところだけチェックすれば大丈夫だね、ということにはならない。

ところで、今まで説明してきたみたいに単純にURLを開くだけのことをGETリクエストという。

フォームからいろいろなパラメータを指定してサーバーに送信する際に、GETリクエストを利用すると、先ほどから書いているように「subject=test&to=someone@example.com&body=hello」という「項目名=値」を「&」でつないだようなURLの形でリクエストが行われる。

HTMLで書くと

<form action="http://example.com/mail_send.php"> タイトル <input type="text" name="subject"> 宛先 <input type="text" name="to"> 本文<textarea name="body"></textarea> <input type="submit" value="送信"> </form>

みたいな フォームを用意しておくと、上記のようなGETリクエストが送られることになる。

一方、URLにパラメータが含まれないPOSTリクエストを指定したフォーム送信の方法もある。上記のHTMLの一行目を、

<form action="http://example.com/mail_send.php" method="post">

に変えると、フォーム送信でアクセスされるURLは、

http://example.com/mail_send.php

のままになり、先ほどのような「subject=test&to=someone@example.com&body=hello」という部分はURLにはつかない。

じゃあパラメータはどうやって送られているのかというと、URLの一部ではなく、普通にWebブラウザを使っているだけの人間の目には触れない別の形で、サーバーに送られる。

サーバーでは、GETリクエストで送られたパラメータとPOSTリクエストで送られたパラメータは識別できる。

だから、http://example.com/mail_send.phpで動いているプログラムが、POSTリクエストでパラメータが送られた場合にしか動かないように作られていれば、単に

http://example.com/mail_send.php?subject=test&amp;to=someone@example.com&amp;body=hello

みたいなURLを呼び出しただけでは、メール送信機能が動いてしまうことはない。

じゃあ、サーバー上のプログラムを、POSTリクエストのみで動くようにしておけば、勝手に使われることはないんじゃないの?と思うかもしれないが、そうではない。

たとえば、

<form action="http://example.com/mail_send.php" method="post"> <input type="hidden" name="subject" value="test"> <input type="hidden" name="to" value="someone@example.com> <input type="hidden" name="body" value="hello"> <input type="submit" value="このボタンを押すと面白いよ!"> </form>

みたいなフォームをどこかの無料ホームページサービスなんかに置いておくとしよう。

上のフォームの「type="hidden"」というパラメータは、Webブラウザ上には見えない形でパラメータを指定する方法なので、上のフォームを表示すると「このボタンを押すと面白いよ!」というボタン一つが表示されるだけだ。

このボタンを見ただけで、いったい何が起こるのかがわかる人はいないだろう。

「何か面白いことが起こるのかも」と期待してボタンをクリックしたら、メールが送信されてしまう。

これだとユーザーがボタンを押さなければメールが送信されないので、ボタンを押さなければ安全なのね、と思うかもしれない。

しかし、それを回避して自動的にPOSTリクエストのフォームを送信する方法もある。

<script> var forms = document.getElementsByTagName('form'); forms[0].submit(); </script>

というJavaScriptは、HTML中に含まれるフォームの最初の一つを自動的に送信する機能を持つ。

つまり、フォームのHTMLと上記のようなJavaScriptを書いておけば、ページを開いただけで自動的にフォームが送信されてしまうことになる。

ただし、これはJavaScriptが有効な場合のみ働くので、WebブラウザでJavaScriptが動作しないように設定しておけば、この攻撃は回避できる。

一昔前までは、「信用できるサイト以外ではJavaScriptはオフにしよう」なんて使い方をすることで、こういう攻撃は回避できていたのだけれども、今時はもうJavaScriptが当たり前のように使われすぎているんで、そんな面倒な使い方は現実的ではないだろう。

しかし、このような方法でフォームを自動送信した場合は、Webブラウザはメール送信結果ページを表示するので、メールが送信されたことには気づくことができるかもしれない。

しかしそれをごまかす方法もある。

たとえば、フォームの1行目を、

<form action="http://example.com/mail_send.php" method="post" target="test">

のように「target="test"」を指定する。そして、

<iframe name="test" style="display:none;"></iframe>

のように見えない(display: none;)インラインフレーム(iframe)を用意しておく。

インラインフレームというのは、Webページ(HTML)中に別のWebページを埋め込むための仕組みだ。

自動的に送信されたフォームの結果は、インラインフレーム「test」に表示されるのだが、インラインフレーム「test」は表示しない設定になっているので、そのページを開いた人にはメール送信結果ページが見えることはない。

このような仕組みの罠は、HTMLという形で記述する必要があるので、単純に攻撃URLを掲示板に貼り付けることはできない。

こういう罠を仕掛けたHTMLをどこかのサーバー上に置いておき、そのURLを掲示板なんかに貼り付けるという2段階の仕組みが必要になる。

ちゃんとしているサーバ運営会社ならば、そういう危険なHTMLを仕掛けてもすぐに撤去されるし、罠を仕掛けた人間をアクセスログなどから追跡することにも協力的だろう。

というわけで、そういう罠が仕掛けられているページは、海外のあまり日本ではメジャーではないサーバーなどを利用して設定されていたりする。

じゃあ、知らないURLっぽいページは開かなければいいよね、と思うかもしれない。

しかし、今時は長いURLを別の短いURLに変換してくれるサービスなんかもあり、そういうものが使われていると、実際にクリックしてみるまで、どういうURLが開かれるのかはわからない。

また、実際にクリックしてみたところで、人間の目には実際にどういうURLが開かれたのかわからない場合もある。

Webブラウザは、リダイレクトという機能に対応している。リダイレクトというのは、指定されたURLから別のURLに自動的に移動する機能だ。

サーバーにリダイレクト先URLを指定した結果を返されたら、ブラウザは自動的にそのURLに移動する。リダイレクトは一瞬で行われるので、ユーザーの目にはリダイレクトされたかどうかは識別できない。

だから、ある短縮URLを開いた結果、たとえばhttp://www.yahoo.co.jp/が表示されたから、安全なURLだった、とは限らない。

たとえば、http://example.co.jp/too_dangerous_page/という罠が仕組まれたページに移動した後に、なにやら危険な処理を実行し、その後すぐにhttp://www.yahoo.co.jp/に移動したのかもしれない。

ユーザーの目に見えるレベルのURLなんてものは、かんたんにごまかすことができる。

これは偽装というレベルではなく、インターネットにおける当たり前の技術を当たり前に使っているだけのことだ。

というわけで、ここまで説明したいたちごっこで、ユーザーができる比較的安全な対処方法は、JavaScriptをオフにすることくらいなのだが、今となってはもうそういう訳にはいかないだろう。

ということで、ユーザー側が自衛できない以上は、サーバーサイドで対処しなければ、こういう攻撃は回避できない。

サーバーサイドでの対処策に関しては、技術者だけがわかればいい話なので、ここでかみ砕いて説明する必要はないだろう。方法論は確立しているので当たり前に対応すればいい。

セキュリティ的な危険性がある機能に対して回避策を講じていないのは単なる手抜きなので、非難されてしかるべきだろう。

ただ、認証不要のサイトにおける送信先固定のメールフォームに、CSRF対策が必須かというと、今回の事件が起きるまでは、「これはなりすましが可能ですよ」と関係者が理解していれば、CSRF対策をしていなくてもいいと思っていた。

が、今回の事件で「これはなりすましが可能ですよ」という理解を関係者に求めることは難しいように感じたので、今後は認証不要なサイトのメールフォームレベルの機能でも、CSRF対策は必要だろう。

また、このような知識はもうずいぶん前からインターネット技術者には当たり前のように知られていた内容なので、このような知識を前提とせずにインターネット犯罪を捜査しちゃまずい。

今回の事件は、単なるCSRFとウイルス経由の遠隔操作が混ざっているっぽいんだけど、前者は十分にあり得ることであると理解できていなかったとしたら、まずい。

その辺の情報がはっきりしないんだけど、どうなんだろう?

掲示板経由で命令を出すウイルスに関しては、botネットなんかではよく使われていると言われている手法だから、インターネット犯罪を捜査する人間ならば、方法的には知っていてしかるべきではある。

けれども、この辺はセキュリティを本格的に研究している人しか、具体的な情報に触れることはないかもしれないから、詳しく知らなくてもしょうがないかもしれない。

というか、私もそういうものらしいと噂でしか聞いていなかったから、実際にこういう事件があったときにそういう可能性を主要な選択肢としては思いつかなかったかもなー。

なんかものすごく長くなった。かみ砕いて説明したつもりだったけど、あんまりかみ砕けてないかもしれない。

[宣伝] Twitterベースの掲示板システム開発中! http://tweet-bbs.com/

Published At2012-10-23 20:23Updated At2012-10-23 20:23

技術日記
iPhoneアプリ『QQ81』作りましたEdit

2012/08/29追記 現在公開されているバイナリが起動後すぐに落ちてしまうようです。開発環境では実機動作しているんですが…。原因はよくわかりませんが、ひとまず動作していた一つ前のバージョン相当に戻したバージョンを公開申請に出しました。おそらくそちらが公開されれば動作するようになると思います。動作しないバージョンをダウンロードした方、申し訳ありませんでした。


iPhoneアプリ『QQ81』作りました。暗算アクションパズルです。

画面下の5×5のパネル部分にランダムに数字が現れてくるので、それをかけ算(九九)が成立する順番にタップして消していきます。

素早く連続して消したり、同じ数字同士のかけ算で消したり、パネル上のすべての数字を消すとボーナス。

かけ算が間違っていると数字が増殖し、パネルのすべてが数字で埋まるとゲームオーバーです。

本当はちょっと前に公開したんですが、iOS4で動かないとかいろいろバグがあったのがようやく直った(というか、直した版がようやくAppStoreに載った)のでここでも紹介します。

ちなみに無料アプリです。一応広告も貼ってあるけれども、起動時にしか表示しないトップページにしか貼ってないんで、おそらくタップされることはほぼないでしょう。

で、このゲームはMonoTouchで初めてちゃんと作ったアプリだったんですが、やっぱりMonoTouchというかC#はいいねー。Objective-Cでも書けなくはないけど、主に可読性的な面でフラストレーションがたまる。

その点、C#だとほぼ考えたとおりにコーディングできるんで、非常に書きやすい。オブジェクトの生存管理もふつうにガベージコレクション任せに出来るし。

ただ、基本的なロジック部分はいいんだけど、iOSの機能を使おうとすると、ほぼC#環境に直訳されただけの、Cocoaフレームワークやその他iOS用ライブラリ群を呼び出すことになる。

だから、「MonoTouchがあればObjective-Cなんてわからなくても、iPhoneアプリがすいすい書けるぜ!」なんてことにはならない。iOS用ライブラリの使い方を調べようと思ったら、Objective-Cで書かれたサンプルとか読めないと困るし。

それでもまあ俺的には、.hファイルと.mファイルを行き来しなくても良くなったり、

[sourcecode language="objc"] MyViewController *contrller = [[MyViewController alloc] initWithNibName: @"MyViewController" bundle: nil]; [self presentModalViewController: controller animated: YES]; [controller release]; [/sourcecode]

みたいなコードを、

[sourcecode languag="csharp"] var controller = new MyViewController(); PresentModalViewController(controller, true); [/sourcecode]

なんて感じに書けるだけでもだいぶ心に平穏が訪れる。ただ、

[sourcecode language="objc"] [UIView beginAnimations: nil context: nil]; [UIView setAnimationDuration: 0.2f]; [UIView commitAnimations]; [/sourcecode]

みたいなほぼAPI呼び出しだけやるようなコードだと、MonoTouchにしたところで、

[sourcecode language="csharp"] UIView.BeginAnimations(null, null); UIView.SetAnimationDuration(0.2f); UIView.CommitAnimations(); [/sourcecode]

になるだけなんで、あんまりC#って感じもしなくなってくる。

特にiOSではまだまだ現役で古いC API(非Objective-C API)とかが残っているし、そういうのもべた移植なんで、その辺使うとだいぶ「なんだかなー」という気分になれる。

ちなみに、非標準開発環境であるMonoTouchだからこそはまった、というようなことは今回はほとんどなかった。

MonoTouchで開発中にググって見つかる関連情報はかなり少ない(特に日本語は)んだけど、互換開発環境としては結構出来がいいのか、あんまり変なはまり方はしないし、Objective-CとC#(MonoTouch)の読み替えが出来れば、Objective-C向けに書かれた情報で解決することがほとんど。

iOSの機能もほとんどもれなく移植されているし、一応自前で移植されていないAPIなんかをC#から呼び出す方法も用意されている。ただ、その辺の情報はググって草の根情報を見つけるよりも、xarmin(MonoTouchの開発元ね)のドキュメントを読み込んだ方が最終的にはわかりやすいかも。

そういえば、MonoTouchの開発環境であるMonoDevelopのMac版には、日本語入力が出来ないというかなりアレな欠陥がある。

ソースコード中に日本語を入力したければ、他のエディタとかで入力した日本語テキストをコピペしたり、そのファイルだけ別エディタで開いて編集したりする必要がある。

まあゲームプログラムなんてあんまり日本語を使うことはないし、それほど関係ないかなーと思っていたんだけど、MonoDevelopからcommitするときのコミットログでも日本語が書けないのが、さりげなく面倒くさかった。

ちなみにこの欠陥はGUIツールキット周りの不具合で、だいぶ前から直っていないようなんで、今後もすぐに直ることはなさそうだ。

Objective-C用に用意されたサードパーティライブラリをC#で使う方法とか、他の環境向けのC#用ライブラリをMonoTouchで使う方法とかは、結構はまりやすそうな話題なんだけど、今回はほとんどその辺をやることがなかったんで、今後の課題かな。

というわけで、iOS用アプリ開発環境としてのMonoTouchはかなり気に入ったんで、今後のますますの発展をお祈りしたいところなんだけど、ググって見つかる日本語情報の少なさ=今後ますますご発展する可能性の少なさ、という気がするんで、少しでもMonoTouchが流行ってくれることを祈っていろいろ書いてみた。

できればMono自体がもっと流行って、サーバーサイドアプリとかもMonoで書ける世の中が来るといいんだけどな-(今でも一応書けるけど、それで商売する勇気がある人は少ないよね? MS純正環境じゃなくて、mod_monoとかのほうね)。

Published At2012-08-24 18:42Updated At2012-08-24 18:42

技術日記
iOSアプリでUDIDを使わないようにする簡単な方法Edit

また最近iOSのUDIDがらみの話をよく見かけるんで、もっとも手間をかけずにUDIDからアプリケーションUUIDに移行する方法を

https://gist.github.com/1824855

に書いてみた。

gistの使い方がよくわからないんだが、これライセンスとかどうなってるんだ? 内容は、https://gist.github.com/1161447https://gist.github.com/1743326を足しただけなんで、俺独自のコードはない。github上でforkしたかったんだけど2つのコードの内容を足すときに、どう表現すればいいのかよくわからなかったんで、新規gistにした。

使い方としては、すでにUDIDを使っているiOSアプリのXcodeプロジェクトにこの2ファイルを追加するだけ。

NSString *udid = [[UIDevice currentDevice] uniqueIdentifier];
みたいにUDIDを取得しているつもりのコードが、自動的にアプリケーション+デバイスユニークなUUIDに差し替わる。自動生成されたUUIDはKeychainに保存されるんで、アプリケーションの再インストールをしても維持される。

UUIDをリセットしたい場合は、

[[UIDevice currentDevice] resetUniqueIdentifier];
すればいい。

これくらい簡単に差し替えられるならば、すでにUDIDを使っちゃってたアプリも気軽にUDID不使用バージョンにできるよね。

Published At2012-02-14 18:02Updated At2012-02-14 18:02

技術日記
さくらVPS+CentOS+PHPでCatchAllなメール受信Edit

試しにやってみたらやたらとはまったのでメモ。

さくらのVPSに適当なバーチャルホスト(foo.example.com)を割り振り、そのバーチャルホスト宛に届いたすべてのメール(*@foo.example.com)をPHPスクリプトで受信したい。

CatchAllじゃないんだったら、受信したいアカウントの.forwardとかからスクリプトを呼び出せばいいんだけど、CatchAllとなるとまず受信したいアカウントというものが存在しないわけで、その辺から設定する必要がある。

具体的には、まず適当な受信用のアカウント(mailreciever)を用意する。

[sourcecode language="plain"] useradd -s /sbin/nologin mailreciever [/sourcecode]

で、そのアカウントとバーチャルホストのCatchAll転送先を結びつける。まずはバーチャルホストをsendmailが受信するドメインとして追加するために、/etc/mail/local-host-namesに以下を追加。

[sourcecode language="plain"] foo.example.com [/sourcecode]

そして、そのドメインのCatchAll転送先としてmailrecieverアカウントを登録するために、/etc/mail/virtualusertableに以下を追加。

[sourcecode language="plain"] @foo.example.com mailreciever [/sourcecode]

virtualusertableはコンパイルが必要なので、

[sourcecode language="plain"] yum install sendmail-cf # 必要ならば makemap hash /etc/mail/virtualusertable.db < /etc/mail/virtualusertable [/sourcecode]

mailrecieverアカウントに受信したときにPHPスクリプト(/path/to/script.php)を実行するように/etc/aliasesに以下を追加。

[sourcecode language="plain"] mailreciever: "|/usr/bin/php /path/to/script.php" [/sourcecode]

というのは実はうまく動かなかった。smrsh環境の制約に引っかかるらしい。そこで、

[sourcecode language="plain"] ln -s /usr/bin/php /etc/smrsh/php [/sourcecode]

のように/etc/smrshディレクトリ内にPHPのCLIバイナリのシンボリックリンクを作っておいて、先ほど書いた/etc/aliasesの内容を、

[sourcecode language="plain"] mailreciever: "|/etc/smrsh/php /path/to/script.php" [/sourcecode]

と変更するとsmrsh環境が原因のエラーを回避できる。

ちなみに/etc/aliasesもコンパイルが必要なので、

[sourcecode language="plain"] newaliases [/sourcecode]

を実行してコンパイルしておく。

これでlocalhost上で*@foo.example.com宛てのメールを送ると、ちゃんと/path/to/script.phpが起動できるようになったのだが、外部からメールを送ってもちゃんと着信していない。

ああ、もちろんそれ以前に、foo.example.comのドメインを該当サーバーのIPアドレスでDNS登録しておく必要があるけど、そういう問題ではない。

あと、自分でセットしたソフトウェアファイアウォールで25ポートINがふさがっているのかと思ったりもしたが、それもない。さくらインターネットが25ポートINを外側でふさいでいるのかと思ったのだが、それでもない。

正解は、さくらVPS+CentOSでインストールされるsendmailのデフォルト設定は、localhostからのSMTPしか受信しないようになっている、だった。

sendmail.cfには触らないと心に誓っているんで、あきらめてpostfixにでも入れ直そうかと思ったんだけど、ググってみたらそれほど設定変更は大変そうじゃなかったんで、sendmailのまま設定を変えて対応した。

/etc/mail/sendmail.mcの

[sourcecode language="plain"] DAEMON_OPTIONS('Port=smtp,Addr=127.0.0.1, Name=MTA')dnl [/sourcecode]

という行をコメントアウトして、

[sourcecode language="plain"] dnl # DAEMON_OPTIONS('Port=smtp,Addr=127.0.0.1, Name=MTA')dnl [/sourcecode]

に変更し、

[sourcecode language="plain"] m4 /etc/mail/sendmail.mc /etc/mail/sendmail.cf [/sourcecode]

でコンパイルしてからsendmailを再起動したら、無事外部からのSMTP接続も受信してくれるようになり、foo.example.com宛てのすべてのエールを/path/to/script.phpで処理できるようになった。

なんかものすごくはまりどころが多かった。

Published At2011-12-19 23:22Updated At2011-12-19 23:22

技術日記
SilverlightでのWeb APIアクセス処理(無駄な試行錯誤の過程)Edit

なんかもう、Silverlightの普通の.NET Frameworkとの互換性のなさにはうんざりしているんだけど、その中でも特にうんざりしたWebRequest周りについて。

普通に.NET FrameworkでWeb APIとかにアクセスしたい場合、System.Net.WebClientとかを使うと簡単にできる。

[sourcecode language="c#"]
var client = new System.Net.WebClient();
var url = "http://example.com/path/to/api";
var values = new NameValueCollection(){{"key", "value"}};
var byteResult = client.UploadValues(url, values);
var stringResult = Encoding.UTF8.GetString(byteString);
[/sourcecode]
受け取った文字列がJSONだった場合は、
[sourcecode language="c#"]
var serializer = new JavaScriptSerializer();
var result = serializer.Deserialize(stringResult, typeof(ObjectForMapping);
[/sourcecode]
みたいな感じで受け取り用のクラス(ObjectForMapping)を用意しておいて、それにデシリアライズしたりとか。 あと、これだけだとCookieベースの認証とかが維持できないんで、webclient 継承 cookiecontainerでググると出てくるような感じで、
[sourcecode language="c#"]
class MyClient: WebClient
{
  protected CookieContainer _cookie = new CookieContainer();
  protected override GetWebRequest(Uri address)
  {
    var request = base.GetWebRequest(address);
    if (request is HttpWebRequest) {
      (request as HttpWebRequest).CookieContainer = _cookieContainer;
    }
    return request;
  }
} 
[/sourcecode]
みたいな感じでCookieを扱えるクラスを作っておくと、自動的にCookieを維持してくれるようになる。 でまあ、Silverlightでも同じようなことができるよね、と思って、クラスライブラリの名前空間とかファイルとかの構成がいろいろ変わっているのを乗り越えて、
[sourcecode language="c#"] 
using System.Windows.Browser; 
using System.Net.Browser; 
class MyClient: WebClient  
{
} 
[/sourcecode]
とやったところ、これはコンパイル自体は通るくせに、new MyClient()するコードブロックを通ったとたんに、何のエラーメッセージも出さずにそっと落ちる。 どうやらWebClientを継承したらだめなようだ。

けど、そんなドキュメント、どこにも見つからないし、だいたいコンパイルは通ってるんだけどなー。っつーか、結局WebClientを継承するだけでもだめだ、と気づくまでどれだけ時間がかかったことか。 しょうがないんで、自前でWebRequestを使って処理を行うような構成に変更することにする。必要に応じて、

[sourcecode language="c#"]
var request = WebRequest.Create(new Uri(url));
(request as HttpWebRequest).CookieContainer = _cookie; // 使い回すCookieContainer
request.BegubGetResponse(callback, request);
[/sourcecode]
みたいな感じね。でも、これが通らない。なぜかというと、
[sourcecode language="c#"]
var request = WebRequest.Create(new Uri(url));
[/sourcecode]
で返されるWebRequestがHttpWebRequestではなくBrowserHttpWebRequestってやつになっている。なんじゃそりゃ。それはどの辺のドキュメントに書いてあるんだよ。MSDN内で検索してもそのドキュメントが見つからないぞ。

いろいろ探し回った結果、方法: Cookie の取得と設定を行うというドキュメントを見つけた。解説自体は今までやっていたことを説明しているだけで、特に新しい情報はない。しかし、サンプルコードに見慣れない処理が入っている具体的には、

[sourcecode language="c#"]
IWebRequestCreate creator = WebRequestCreator.ClientHttp;
WebRequest.RegisterPrefix("http://", creator);
WebRequest.RegisterPrefix("https://", creator);
HttpWebRequest request = (HttpWebRequest)WebRequest.Create("http://api.search.live.net/clientaccesspolicy.xml")
[/sourcecode]
なんじゃこりゃ。WebRequestCreatorクラスのドキュメントを見てみると、
Silverlight では、ブラウザーとクライアントのどちらで Silverlight ベース アプリケーションの HTTP 処理を行うかを指定できます。既定では HTTP 処理はブラウザーによって行われるため、クライアントで HTTP 処理を行う場合は選択の必要があります。通常、HTTP の要求と応答の処理方法を指定するには、このクラスのプロパティを WebRequest の RegisterPrefix メソッドSilverlight では、ブラウザーとクライアントのどちらで Silverlight ベース アプリケーションの HTTP 処理を行うかを指定できます。既定では HTTP 処理はブラウザーによって行われるため、クライアントで HTTP 処理を行う場合は選択の必要があります。通常、HTTP の要求と応答の処理方法を指定するには、このクラスのプロパティを WebRequest の RegisterPrefix メソッドに渡します。に渡します。
なるほど、Silverlightってそこまでブラウザと密結合していたのね。 標準ではBrowserHttpWebRequestってのが使われるから、非Silverlight環境と互換性のあるHttpWebRequestを使いたい場合は、サンプル通りにSilverlight側でhttpスキームを処理するように指定してからWebRequest.Createしなければならないのか。そうやったらちゃんとHttpWebRequestが生成されるようになった。CookieContainerも使えている。

で、一通りコンパイルは通るようになったし、実行もできるようになったんだけど、なぜかCookieによる認証が維持されない。認証CookieがHttpOnlyなんで、中身をのぞけないからよくわからないが、少なくとも同じようなコードで通常の.NET Frameworkでは動作するものが、Silverlight環境ではうまくいかない。

うがー、どないなっとんねん。と、SilverlightのHTTP通信周りのドキュメントをもう一度良い返してみる。そうしたら、Silverlightの標準の方のBrowserHttpWebRequestでは、ブラウザの機能を使ってHTTPリクエストを処理するんで、ブラウザ側のCookieがそのまま使えるっぽい? なんか非Silverlight用に作ったコードを移植しようとしていたのは無駄な努力だった?

というわけで、単純に認証Cookieを保持したいだけだったら、わざわざHttpWebRequestを生成して自前でCookieContainerを処理したりする必要はなく、何も考えずにSilverlight標準のWebアクセスクラスを使ってしまえば、ブラウザ相当のCookie処理が使われてくれる。

先に非Silverlight用のWeb APIクライアントコードを書いて、それをSilverlightでも動くように移植しようとしたせいで、無駄にはまったんだね。はじめからSilverlight用に書いていれば何も考えなくても動くものが作れた気がする。ただし、ブラウザ互換のWebアクセスクラスにも制約があるんで、それで全部いけるわけではないけど。

書き忘れていたけど、クロスドメインアクセス用のcrossdomain.xmlとかは、Flashとかと同様、サーバーサイドにあらかじめ用意しておく必要がある。

で、APIアクセスライブラリとしては一通り動くようになったから、SilverlightアプリケーションからWeb APIをコールして、その結果をTextBoxに表示するというテストアプリを作ってみた。が、「System.UnauthorizedAccessException: 無効なスレッド間アクセスです。」というエラーが出て表示できない。

どうやらAsyncCallbackから呼び出されるスレッドと、UI部品などを管理するメインスレッドとは別スレッドだから、直接は呼び出せないと言うことらしい。

いろいろググったところ、Silverlight 奮戦記 ファイルを読む β2 対応版という情報を見つけた。AsyncCallbackから呼び出されたメソッドから、さらにSyncronizationContect.Current.Post()を使ってメインスレッドとの同期を取ってから、メインスレッド側のパーツに対する処理を行えば回避できるみたいだ。でも、この辺の情報は公式ドキュメントではどの辺に書いてあるのかなー。

といった感じで、.NET Frameworkがらみのドキュメントは非常にわかりにくいし、同じ名前のクラスが多いくせに制限事項が違うSilverlight環境の場合は、それがさらにひどくなっているんで、あまり深入りしたくない気持ち満点になった。デバッグ環境としてできがいいVisualStudioなのに、Silverlightのデバッグだとうまく動かないことがあまりにも多いし。

Published At2011-12-09 21:43Updated At2011-12-09 21:43

技術日記
Eclipse使っている人はSubversion 1.7系へのアップデートはもうちょい待ったほうがいいかもEdit

追記@2012/02/03)解決したよ→今日の検索キーワード:ASP.NET MVC3、Subversion 1.7+Eclipse動いた<!--more-->

WindowsでSubversion関連を扱うためのツール類をいろいろ使っているわけだ。具体的には、TortoiseSVNとか、EclipseのSubversionプラグイン(Subversive、Subclipse)とか、VisualStudioのSubversionプラグイン(AnkhSVN)とか。

で、各開発環境(IDE)を使うときにはそれぞれの開発環境用プラグインを使い、そうじゃない場合はTortoiseSVNを使っているわけなんだけど、TortoiseSVNは結構よくアップデートされていて、アップデートがあると起動時に「アップデートしないかい?」というダイアログが出る。

でまあ、Explorer拡張であるTortoiseSVNのアップデートはたいてい再起動を伴うんで、面倒くさくて「いいや、アップデートしない」としておくんだけど、しばらくアップデートしていなかったんで久しぶりに「いいよ、アップデートしても」と返事をした。まあTortotiseSVNのアップデートは今まで問題が出たことなかったし、特に何も考えずに。

で、アップデートしてから普通に使おうとしたら、「Working Copyの形式がSubversionの1.7形式になっていないから、1.7形式にアップグレードするかい?」と聞かれた。へー、今回のアップデートはその辺も変わったのかと「いいよ、やっちゃってよ」と返事をしたら、いろいろ問題が出た。

このフォーマット変更は、結構互換性がない変更だったのね。っつーか、.svnフォルダをルートのみに置くようになったのか。それはすげー便利。けど、Subversion 1.7形式に対応していない古いプラグイン達からこのWorking Copyを扱えなくなってしまった。くそー、Subversion関連のアップデートで今更こんな互換性がない代物が発生するとは。油断していた。

それでもまあ、VisualStudioプラグインであるAnkhSVNに関しては、ちゃんとSubversion 1.7以降に対応したバージョンがすでにリリースされていたんで、それにアップデートしたらちゃんと扱えるようになった。ありがとう。

しかし、Eclipseプラグインの方は、今までSubversiveを使っていたんだけど、こちらはまだSubversion 1.7形式に対応していない。試しにSVNKitの1.7対応アルファ版をインストールしてみたりしたんだけど、だめだった。

Subclipseは1.7対応版が出ているんだけど、今までSubclipseがちゃんと動いたことないんだよなー。インストールして動かそうとしても、JavaHL関連の依存関係エラーが出る。32ビットJava環境で動かしているはずだけど、OSはWindows 7 64ビットなんで試しにSilk SVNの64ビット版を入れてみたりもしたけど、状況は変わらず。

というわけで、結局現時点でEclipseのSubversion関連プラグインをSubversion 1.7形式に対応させるのは挫折。

まあそのうち標準的なアップデートインターフェースからアップデートされてくるだろうし、TortoiseSVNが使えるんでEclipseプラグインが使えなくても使い勝手が大幅に悪くなるわけじゃないんだけど、まだ1.7系にアップデートしていない人でEclipseも使っている人は、1.7系にアップデートするのはしばらくやめておいた方がいいと思う。

まあ、Working Copyを1.7形式にアップグレードせずに使い続ければいいんだろうけど、TortoiseSVNで新規にcheckoutしたWorking Copyを後からEclipseでマウントしようとしたら、SVNアクセスがうまくいかない、とかの罠にはまるのも面倒だし。

Published At2011-12-07 16:17Updated At2011-12-07 16:17

技術日記ZendFramework
Zend_Frameworkにおけるバリデーションコードの置き場所Edit

Zend_Frameworkにおけるバリデーションコードの置き場所ってどうよ? ってのをいろいろ試行錯誤した結果、今のところこうしてるよ、というのをまとめてみる。

DBアクセスレイヤーは基本的にZendDbTableを使っている。テーブルにマッピングできないものも、データ操作レイヤーに関するものはapplication/models/以下に置いて、ZendDbTableと同じようなインターフェースを持たせて使う。で、共通化できるバリデータはこのモデルクラスにまとめて持たせる。

[sourcecode language="php"]
class ModelBase extends ZendDbTable
{
  protected $validators = array();
  public function getValidators()
  {
    return $this->validators;
  }
  public function getValiator($name)
  {
    if (!isset($this->validators[$name]) {
      $this->validator[$name] = new ZendValidate();
    }
    return $this->validator[$name];
  }
}
[/sourcecode]
みたいなベースクラスを作っておいて、 各実装クラスでは、
[sourcecode language="php"]
class FooTable extends ModelBase
{
  public funciton init()
  {
    $this->getValidtor('foo')
    ->addValidtor(new ZendValidateStringLength(5, 15));
  }
}
[/sourcecode]
みたいな感じでinit()内でバリデータをセットしておく。そうすると、ZendDbTableRowとかでこのバリデータを使いたい場合は、
[sourcecode language="php"]
class FooTableRow extends ZendDbTableRow
{
  public function isValid()
  {
    $result = true;
    foreach ($this->getTable()->getValidators() as $name => $validator) {
      $result = $validator->isValid($this->$name) && $result;
    }
    return $result;
  }
}
[/sourcecode]
なんて感じでバリデーションを書ける(上の例は共通バリデータをぶん回すだけでOKな場合の話ね)。また、このFooTableに対してのCRUDを行うようなZendFormでは、
[sourcecode language="php"]
class FormFoo extends Zend_Form
{
  public function init()
  {
    $fooTable= new FooTable();
    $foo = $this->createElement('input', 'foo');
    $foo
    ->addValidator($fooTable->getValidator('foo'))
    ->addValidator(new CustomValidatorForForm());
    $this->addElement($foo);
  } 
}
[/sourcecode]
なんて感じでFooTableの該当パラメータ用のバリデータを使うことができるし、それにフォーム専用の追加パラメータを追加することもできる。

アクションコントローラのパラメータの場合は、

[sourcecode language="php"]
class FooController extends ZendControllerAction
{
  public function fooAction()
  {
    $foo = $this->_getParam('foo');
    $fooTable = new FooTable();
    $validator = $fooTable->getValidator('foo');
    if (!$validator->isValid($foo)) {
      // IIIINNNNVALIIIIIIIDDDDDDDDDDD!!!!!!!!!!!!!!!!
    }
  }
}
[/sourcecode]
なんて感じで、使いたい要素のバリデータだけ引っ張り出して使える。

ということで、バリデータはモデルクラスにパラメータ名ごとに共通(使い回せる)部分を突っ込んでおいて、シチュエーションごとにそれを取り出しつつ、シチュエーションに特化したバリデータはその場でaddValidator()して使う、いう置き方が一番きれいなんじゃないかと思うのだが、どうだろう。

フィルターに関しては、まとめて同じ処理をしたいという要求がそれほど大きくない、というか同じパラメータでも入力シチュエーションによってどういうフィルターをかけたいかが変わるんで、必要な場所(アクションメソッドとかフォーム要素とか)にそれぞれ書くのが無難な気がする。フィルタークラス自体はアプリケーションに特化したものをapplication/filters以下に用意しておいた方が楽だけど。

と、久しぶりにZendFrameworkで新しいアプリを書こうと思い、どうせならZendFrameworkのコンポーネントを使い倒した書き方にしてみるかといろいろ試行錯誤した結果、こんな感じになったという話でした。

Windows Server 2008でManaged DirectXを使ったASP.NET MVC3が動かないという、ググってもレアケース過ぎて解決策が見つからないシチュエーションからの現実逃避の一環でもある。単にDirect 3Dのベクトル演算関数をサーバーサイドでも使いたいだけなんだけどなー。なんで依存関係が解決できないんだろう。

Published At2011-12-02 20:16Updated At2019-12-30 15:06

技術日記
多重のセキュリティとか出力時のバリデーションとかEdit

なぜPHPアプリにセキュリティホールが多いのか?:第44回 セキュリティ対策が確実に実施されない2つの理由|gihyo.jp … 技術評論社

gihyo.jp セキュリティ対策が確実に実施されない2つの理由

はてなブックマーク - 第44回 セキュリティ対策が確実に実施されない2つの理由|gihyo.jp … 技術評論社

なんではてブでこんなに叩かれているんだ? Rails関連はよう知らんけど、それ以外の内容で特に叩かれるようなことは書いてないと思うんだけど?

「多重のセキュリティ対策」って言葉に引っかかっている人がいるみたいだけど、単に「既にチェック済み,これは数値だからエスケープ処理は不要,などとの誤った判断からエスケープ処理を行っていないコード」みたいな事例に対して、入力値でバリデーションしたから出力値はエスケープしなくていいとか考えるなって言っているだけでしょ。

で、上記みたいな誤った判断をする人がその理由として、「だってDRYって言うし、いったんこの入力値に対してはチェックコードを書いたから、出力時の処理は書かなくていいよね」とか言いかねないから、「セキュリティ対策とコーディングのベストプラクティスは相反する」と言ってるんじゃないの? 結局ここで言うベストプラクティスってのは「重複処理を排除した効率よいコード記述」のことみたいだし。「それを重複と考えるのはおかしい」とか言っても、それを重複と考えるような馬鹿を前提とした文章だって読み取れるじゃん。

あと出力時のバリデーションに関しては、「1と2の対策が不可能な場合は出力時にバリデーション処理を行う」と書いてあるとおり、どうしてもエスケープなどを使った安全な出力ロジックが使えない場合は、しょうがないから出力に関してもバリデーションしようね、という話になっていて、別に推奨しているわけじゃないし、実際特殊な環境向けの出力ではあり得る話だと思うんだけど。

それにしても「セキュリティ対策」って言葉は曖昧で良くないな。

  1. 普通に動作させるために必要な設計と実装(そして、それができなかった際に発生するバグ)。
  2. 情報漏洩や情報破壊を引き起こしうるバグに対する注意。
  3. 積極的なセキュリティアタックに対しての対抗策。
それらが全部なんとなく「セキュリティ対策」でくくられてしまうけど、全部混ぜて議論すると焦点がぼやけてぐだぐだになる。どこまでわかっている(問題にしている)人に対して、何を伝えたいのかがそれぞれ違ってくる。

Published At2011-12-01 13:08Updated At2011-12-01 13:08

技術日記PHPZendFramework
Zend_View_Filterで文字コード変換するときの問題Edit

前提条件

ZendFrameworkで、内部エンコーディングとしてUTF-8を利用しているWebアプリケーションを構築していて、一部ページのみガラケー(SHIFTJIS)対応したい。

問題

ZendViewFilterでUTF-8からSHIFT_JIS変換するフィルターを作り、ガラケー用コントローラのinitで
[sourcecode language="php"]
public function init()
{
  $this->view->addFilter('文字コード変換フィルター名');
}
[/sourcecode]
とかしてみた。すると、一見ちゃんと文字コード変換がかかったかと思いきや、一部文字化けしている。

調べてみると、addFilterしたフィルターはZendViewがレンダリング処理を走らせるたびに実行されるんで、レイアウトとかを使って内部で複数回ZendView::renderされてしまうと、その部分で複数回文字コード変換がかかる→文字化けする、ということらしい。

ZendFrameworkで文字コード変換 - slumbersでは、ZendLayoutControllerPluginlayoutを拡張したクラスで出力時に変換をかけたらうまくいったよって話が書いてあるけど、もうちょい楽な方法ないかなー。

解決策

複数回フィルターがかかるのがいやなら、出力する文字列が最後まで確定した後にフィルターがかかればいいんじゃないか、ということで、ガラケー用のレイアウトファイルの一番最後に、
[sourcecode language="php"]

....(省略)
<?php echo $this->layout()->content; ?>
....(省略)

<?php $this->addFilter('文字コード変換フィルター名');
[/sourcecode]
とかしてみた。うまくいった。

Published At2011-11-30 21:52Updated At2019-12-30 15:06

技術日記
Entity SQLで「クエリの結果を複数回列挙することはできません。」エラーEdit

Entity SQLで取ってきたデータに対して、まずCount()で全件数を取得してから、ページング用の実データをSkip、Takeするような処理を書き、それをforeachで回そうとすると、「クエリの結果を複数回列挙することはできません。」エラーが出る。

擬似コードで言うところの、

[sourcecode language="c#"]
var db = new dbEntity();
var sql = "select * from table order by id";
var parameters = new List();
var result = db.ExecuteStoredQuery(sql, parameters);
int count = result.Count();
int page = 1;
int pagesize = 20;
int pagecount = (int)(count / pagesize) + 1;
foreach (var row in result.Skip( pagesize * (page - 1)).Take(pagesize))
{
  // ...
}
[/sourcecode]
なんて感じの処理。

必ず出るわけでなく、出たり出なかったりするのだが、どうやらその条件は「パラメタライズされたクエリーかどうか」らしい。上記みたいにparametersが空の場合は問題なく動く。動的にwhere条件を組み立てたりして、parametersにその条件値を入れていたりすると、foreachのところで「クエリの結果を複数回列挙することはできません。」が出る。

LINQ to SQLの仕組み(ほぼEntity SQLと同じ仕様だと思われる)がよくわかっていないんで、内部的にどのタイミングでどういうSQL文が発行されているのかがわからないんだけど、LINQ to SQLのドキュメントを斜め読みした限りでは、実際のSQL文の発行はできるだけ遅延実行するようになっているらしいから、Count()とかTake()とかforeachでEnumerableにしたタイミングで、実際にSQL文を発行しているのかな?

で、Count()とかで一度SQL文を実行して、パラメータ埋め込みを実行してしまったあとに、もう一度SQL文を発行しなければならない処理を呼び出すと、「クエリの結果を複数回列挙することはできません。」エラーが出ている? どうもすっきりしないけど、たぶんそんな感じのことが起こっているのではないかと推測。

ともかく、内部で何が起こっているのかよくわからないEntity SQLの機能を使うのはやめて、SQL文を組み立てるときに件数取得SQLと実体取得SQLの二つを生成しておいて、それぞれ呼ぶようにして回避。擬似コードで言うと、

[sourcecode language="c#"]
var countSql = "select count(*) as cnt from table";
var selectSql = "select * from table";
var parameters = new List();
var count = db.ExecuteStoredQuery(countSql, parameters).First();
var result = db.ExecuteStoredQuery(selectSql, parameters).Skip(offset).Take(limit);
[/sourcecode]
みたいな感じね。countみたいなデータを、モデルクラス以外の型でどうやって受け取ればいいのかには、しばらく悩んだけど、こんな感じでintで受けるといけるようだ。

Published At2011-11-24 20:42Updated At2011-11-24 20:42