ラベル SE の投稿を表示しています。 すべての投稿を表示
ラベル SE の投稿を表示しています。 すべての投稿を表示

2012年12月31日月曜日

AjaxベースのWebアプリにおけるCSRF対策

私の苦手なセキュリティまわりの話。
CSRFについて勉強、備忘録をここに残します。

言うまでもありませんが、CSRFは
攻撃者が作成した攻撃用サイトを利用者に利用させ、
利用者の意図しないリクエストを送信させる手法をさします。

CSRF対策として、form送信ベースのWebアプリでは、
ワンタイムトークンを利用するのがメジャーなようです。
ワンタイムトークンは危険なCookieには格納せず、
ページ内のhidden等に格納するのが安全とされます。
また、ブラウザのキャッシュ機構によるトークン再利用を避けるために、
リクエストにはGETを使わずPOSTにするなど。

ワンタイムトークンの欠点としては、
(1)タブ等による画面分裂に対応しづらい、
(2)ブラウザの戻る進むによってトークンが再利用されてしまう、
(3)Ajaxなど平行して複数リクエストが送信される場合の対応が難しい、
などがあります。

(1)(3)は複数のトークンをページに与えることで対応できなくはないのですが、
画面分裂、同時リクエスト数をクライアント側で制御する必要があり、実装が複雑になりそう。

そこで調べたところ、
(3)のAjaxで送信する場合に限れば、
ワンタイムトークン以外の簡単な方法でCSRF対策を補えそうです。

HTTPヘッダのX-requested-withに値が設定されているか否かを
サーバ側でチェックすることにより、
CSRF対策ができます。(ただしクロスドメイン間通信を許容していない限り)

X-requested-withの値によって、
リクエストがAjaxのものなのか、Ajax以外のものかを判別することができるからです。
jQueryなどでAjax関数を実行すると、
X-requested-withに"XMLHttpRequest"を自動的につけるらしい。

同一生成元ポリシーにより
攻撃サイトからのAjax(XMLHttpRequest)リクエストが原則不可能なため、
Ajaxによるリクエストであるということが分かれば正規のリクエストと判別できます。

CSRFで使われるであろう、ブラウザのURL欄直接入力や、
javascriptのlocation.hrefやsubmit()からのリクエストには
X-requested-withが無い(かは確証がないが、少なくとも"XMLHttpRequest"以外である)
のと、ブラウザ側でX-requested-withを設定することができないことから、
攻撃サイトがAjaxリクエストを偽装することも不可。
上記理由によりJSONハイジャックも拒絶することができます。

ただし、XMLHttpRequestのLevel2ではクロスドメイン間でAjaxリクエストが可能なため、
当対策だけでは不十分になるので注意。

また、CSRFは防げてもセッションハイジャックなどの他のセキュリティ問題への
解決にはならないので、可能な限りトークンなどの他対策と組み合わせたほうがよいと思います。

SEがこんなこと言ってはいけないのですが、
セキュリティって考えるの面倒ですね・・・

2012年12月22日土曜日

Selenium@Androidの罠

この記事では、私が遭遇したSeleniumの問題およびその解決策を共有することを目的としています。
Android向けにSeleniumを利用している方の参考になれれば幸です。


Selenium WebDriverはWebアプリケーションの自動テストツールとして有名です。
SeleniumとWebDriverは元々別ツールだったのですが、1つのツールとして統合され、どちらの機能も利用できるようになり便利になりました。

統合前のSeleniumはテストドライバのコアがHTML/javascriptで実装されていた。
テスト対象アプリと同じブラウザ上で実行させ、テスト対象アプリをjavascriptにより操作、情報取得を行っていた。
ドライバはHTML/jsベースなため、様々なブラウザで汎用的に利用できるとされている。





一方、WebDriverはドライバがブラウザのプラグイン等を利用して実装されている。
ブラウザのプラグイン仕様はブラウザの種類毎に異なるため、
ドライバをブラウザ毎に開発する必要があるが、
HTML/jsベースのSelenium1よりも豊富かつ高性能なコマンドを利用することができ、
テスト性に優れているとされる。

ドライバはInternetExploreDriver、FirefoxDriver、ChromeDriverはもちろんのこと、
AndroidDriverやIPhoneDriverまで用意されている。




















Selenium1とWebDriverは統合され、
Selenium WebDriver(Selenium2)となった。
統合と言っても、製品を単に抱き合わせただけではない。
WebDriverでSelenium1のエミュレーションが利用できるようになり、
WebDriverを利用しながらSelenium1の機能を利用することができるのだ。
この場合SeleniumRCの起動は不要となる。

Selenium2では
WebDriverBackedSelenium

という名のエミュレート化されたSeleniumインスタンスが利用できる。

ここで取得したSeleniumインスタンスを用いて、従来のSelenium1同等の機能が利用できる。
しくみは至って単純である。
エミュレートに対応しているドライバは、「任意のjavascriptをブラウザ上で実行する」という指示をブラウザ上のドライバ(Plugin)に行うことができる。
Selenium1時代にドライバとして利用されていたjavascriptをPluginに実行指示することで、エミュレーションを実現している。























以上が私が付け焼き刃で覚えたSelenium2の知識です。(間違いが含まれているかも・・・)
さて、前置きがだいぶ長くなりましたが、ここからが本題です。
仕事でAndroid、iPhone向けにSelenium2を利用して自動テストを実施することになったのですが、
Androidのテスト実行時に問題が発生し、数日間頭を悩ませられました。
結果的には解決したのですが、情報が少なく解決に苦労した。
同じ事象に悩んでいるかた向けに、その時のノウハウを共有したいと思います。
(iPhoneはまだ未実施なので対象外)
対象バージョンは
Selenium2.25
AndroidDriver2.21
です。

Selenium2ではAndroidDriverというドライバが用意されており、Android標準ブラウザ上で自動テストを走行させることができます。

(余談)
ちなみにこのAndroidDriver、Androidアプリ(apk)として用意されています。アプリ内のWebViewにテスト対象アプリの画面を表示するようになっています。
標準ブラウザにプラグインを組込む術が無かったのでこのような実装になっていると思われますが、
WebViewと標準ブラウザは動きが若干異なるので、テストとして完全ではないと思います。。。


さて問題となっている罠です。
まず試しに、WebDriver流の書き方のテストスクリプトで走行させてみると、正常に動きました。


正常に走行する例)

それを見て安心し、Selenium1時代のSelenium流の書き方のテストスクリプトを再利用し、
先ほど紹介したSeleniumエミュレーションで走行させたところ、assertionエラーやnullpoが発生してしまいました。


エラーが発生する例)


Selenium流の書き方なんて必要ないし、たいした問題じゃないでしょ、という意見もあるかもですが、
回帰テストとして古いテストスクリプトを流用する必要がある場合に、大変な問題になります。

エラーの原因は当初謎でしたが、同じスクリプトをドライバをFirefoxDriverやChromeDriverに変えて走行させると正しく動作するので、Android由来の問題なのは確実だと思いました。
ソースの静的解析、ステップ実行、Android側のログ監視を続けた結果、ドライバ@Android側でjavascriptの文法エラーが発生していることを確認ました。
(私はAndroidエミュレータ4.0でのみ確認しています。)

Unexpected token ILLEGAL ...

タイミングからして、ドライバ@サーバ側から送られたSelenium1のjavascriptをWebView上で実行しようとしたタイミングで発生しているようです。
他ブラウザでは正常に動作することから、そのjavascript自体に文法ミスがあるとは考え辛い。
疑うべきはWebViewのjavascript実行機能のバグ、もしくは制限。

Selenium1のjavascriptはHTML要素を検索したり、ボタン押下を指示したりするような処理が書かれたコードであるが、比較的ボリュームがある(30kb前後)。
ちなみにSelenium2のjarの以下に当jsファイルが格納されている。
org/openqa/selenium/internal/seleniumemulation/

で、原因の仮説を二つ立てました。
(1) WebVewで実行できるjavascriptのサイズ上限がある。
(2) WebVewで実行できるjavascriptの行数上限がある。

(2)はAndroidで出力されていた「Unexpected token ILLEGAL ...」のエラー箇所がいつも13行目を示していたため、
行数が関係しているのでは、という思いつきから立てました。

(1)は以下の情報より正しくないことが分かったので、あとは(2)を確かめるのみ。
http://stackoverflow.com/questions/5051670/is-there-any-limitation-on-urls-length-in-androids-webview-loadurl-method

そこで
org/openqa/selenium/internal/seleniumemulation
配下の全jsの全改行文字を取っ払って、jarを作り直し、エラーとなっていたテストスクリプトを走行させたところ、
見事エラーが解消されました!
どうやら仮説(2)が正しかったようです。
Selenium2の問題というよりかはAndroid自体の問題のようですね。


最後に、当問題についてまとめます。

・Selenium2.25
・AndroidDriver2.21
で、WebDriverBackedSelenium(Selenium1のエミュレーション)を利用したテストスクリプトをAndroid上で走行させると、エラーが発生しテストが正しく行われない可能性がある。

原因は、Android側にエラーログ「Unexpected token ILLEGAL ...」が出力されていることから、
AndroidのWebViewが多数の改行を含むjavascriptを正常に実行できないことにあると推測された。

Selenium2のjar内にある、
org/openqa/selenium/internal/seleniumemulation/
配下のjsファイルの改行をすべて除去したjarで差し替えることにより、
当問題を回避することができた。