最新 追記

高木浩光@自宅の日記

目次 はじめに 連絡先:blog@takagi-hiromitsu.jp
訪問者数 本日: 1768   昨日: 1295

2006年11月04日

駄目な技術文書の見分け方 その1

はてなブックマークのホッテントリを見ていたところ、300を超えるユーザに登録された以下の記事があった。

また上野宣か。顔見知りなのでズバリいくことにする。

しかし、その対策はまだ本当に理解されていないように思える。

へえ。

終わりの方を見てみると、

Webアプリケーションの対策

  • 入力値のSQLの特殊文字を適切にエスケープ
    入力値=プログラム(プロセス)に外部から入ってくるもの
  • シフトJISの場合には1バイト文字を整理
  • SQLの記述をなくすためにO/R(Object/Relational)マッピングを活用
  • 攻撃者に役立つ情報を与えないために、不要なエラーメッセージ(データベースが出力するエラーなど)の表示を抑止

対策に「準備された文」(prepared statement) のことを書かないのは、どういうつもりなわけ?

「入力値をエスケープ」だあ? いまだにサニタイズ脳から抜けられないのかね君は。「サニタイズ」って書かなきゃいいってもんじゃない。言っていることはサニタイズそのものなんだよそれは。どうして入力値だけをエスケープするわけ? SQL文に埋め込むところで全部するよう解説しろって言ってるのに*1、まだわからんのかね。

戻ってみると、

この問題の対策としては、入力値には半角英数字のみを許可するよう制限する方法や、以下の例のようにSQLで使える特殊文字をエスケープして対処するという方法が一般的になっている。

そんな方法が「一般的になっている」なんて書くなって。そんなのは、はじめっから間違ってるって書くべきこと。

この対策自体の方向性は正しいのだが、SQLインジェクションを理解していないため、対策が漏れている場合がある。

「方向性は正しい」と言いながら「理解していないため」というのはどういう了見なわけ? 「方向性」って何? 方向性からして間違ってるんだって。

■セカンドオーダーSQLインジェクション

SQL インジェクション対策として、入力値を適切にエスケープするという対策を行うが、この「入力値」が何なのか間違えていたり、チェックが漏れている場合がしばしば見受けられる。また、HTMLを生成する段階でエスケープすればよいと考えている人も見受けられるが、それも間違いである。

「入力値が何なのか」なんてぜんぜん考えなくていいっての。「チェックが漏れている」? どこでチェックするの? 入力からSQL文までのデータフローパスのどこかでとでも言うのかい?

「HTMLを生成する段階でエスケープすればよいと考えている人」って何それ? HTML? むりやり勘違い例を捏造してどうすんの? 「SQL文を生成する段階でエスケープすればよい」が正しいのをわかってて、わざと違えているわけ?

フォームに入力可能な部分だけが対策個所ではない。不正な入力の可能性を考える必要があるのは、GETやPOSTなどのクエリーやCookieの値、 HTTPヘッダなど、HTTP経由で送られてくるもの“すべて”と、それに加えてデータベースやファイルなどに保存されたデータを呼び出す際にも対策を怠ってはならない。ここに問題があると、セカンドオーダーSQLインジェクションと呼ばれる脆弱性を持つことになる。

ほら出た。ハッカー様の出番ってわけだね。利権構造ですかこれは。本当の解決方法を教えると出番がなくなるってか?

セカンドオーダー云々なんてどうでもいいの。普通の開発者は覚えなくてよい用語。これは攻撃手法の用語であって、間違った方向性の対策がされているときにのみ意味のある話で、はじめから正しい対策を理解すればよいだけの話。

SELECT name FROM user where uid = '$uid' AND age > $age

このSQLに渡される$uidと$ageの特殊文字は、適切にエスケープされているとしよう。

おいおい、「$age」を「'」で括っていない段階で「$age」が「適切にエスケープされている」って何言ってんの? 「'」をエスケープするという概念が生ずるのは「'」で括っている中においてだってことが、まだわからないのかね。

この問題は、ageの値がシングルクオーテーションで囲われていなかったことが原因である。以下のように囲われていれば、この問題は起きることがない(もちろん、数値以外は受け付けないという前段階の処理も欲しい)。

SELECT name FROM user where uid = '$uid' AND age > '$age'

「囲われていなかったことが原因である」と簡単に言うが、なぜ囲われていなかったのかについては言わないの? 数値フィールドを文字列で比較するとSQLではどうなるの? 文字列が数値に自動変換されるのはSQLの仕様なの? それとも実装依存でたまたま動作するの?

「(もちろん、数値以外は受け付けないという前段階の処理も欲しい)」ってのは何なの? SQLインジェクション対策として言ってるのか、それとも別の話をしているの? 他の人がこの問題への対策にその手のことを書いてるから、なんとなく書かないとまずいと思った?

■マルチバイト文字の問題

(略)

PHPならばaddslashes()といった関数を使って、フォームから受け取った入力値に含まれる「'」を「\'」と置換するなどの処理をしてSQLインジェクションによる攻撃を回避しようとするかもしれない。(略)

ポイントとなるのは「\x97'」の部分で、これの「'」をエスケープするために「\'」と置換すると「\x97\'」となる。(略)

\x97\x5C\x27 → 予'

こんなのは PHPのバグでしょ。エンコーディングがShift_JISの文字列を addslashes() するときに、Shift_JISとして処理していない addslashes() のバグ。なぜそれを言わないで、Webアプリプログラマの責任であるかのように書くわけ?

そして結論部分はこう。

また、どれも1つだけで決定的な手段というわけではないので、対策の際には併用してセキュリティレベルを高めることをお勧めする。

出た。「ぼくちゃんよくわからないから、とりあえず併用せよって解説しとけばいいよね」ってか?

SQL文の作成を正しく行うという「1つだけ」の方法で「決定的手段」なのに、なんで併用とか言い出すわけ? たくさんのSQL文があるときに漏れが生じるからということが言いたいなら、そう書けばいい。

去年4月のときにも似たようないい加減なことを書いていたよね。

  • 「ぼくはまちちゃん」 ――知られざるCSRF攻撃, 上野宣, @IT, 2005年4月27日

    この方法は簡単に実装できて比較的効果の高い方法である。しかし、リファラー情報はリクエスト発信者が自由に発行できる情報であるので、偽装されてしまう恐れもあり100%防ぐといった効果はない。

「100%」て何? 攻撃を防ぐ話をしているのだから、防げないなら対策じゃないでしょ。(しかも、CSRFの文脈でRefererは偽装できないと言ったのに、まだ直してないね。去年会ったときに、「直します」って約束してたよね?)

マスコミの記事でよく見かける表現に、「完全に○○ないわけではない」というものがあるが、これは断定できないことを言うときの逃げ口上だってことを知っておいたほうがよい。マスコミには許されるが、技術文書には許されない。技術文書なら、前提を明らかにした上でその前提の上で何が言えるかを書くのであって、例外があるなら例外を書くようにする。

セキュリティの解説で「どれも完全じゃないからいろいろやっておこう」という表現が出てきたら、その著者は信頼するに値しないと判断してよい。書いてる当人がわかっていないから、問題を整理できていないから、そういう表現が出てくる。

本日のリンク元 TrackBacks(12)

2006年11月15日

暗黙的に形成する事実標準の話と回避策の話を混同してはいけない

  • 「ぼくはまちちゃん」 ――知られざるCSRF攻撃, 上野宣, @IT, 2005月4月27日初版、2006年11月8日修正版

    ●リファラーで発信元をチェック

    HTTPリクエストを受けたとき、そのリクエストがどこのWebページから発行されたものかを示すリファラー(REFERER)と呼ばれる情報を得ることができる。この情報を活用し、本来意図したWebページ以外からのリクエストを拒否することで、CSRFによる外部からのリクエストを防ぐことができる。

    ただし、ユーザーがリファラー情報を出力しないブラウザを使っている場合、このチェックを導入すると正当な操作でも受け付けなくなってしまう。

    懸念されるリファラー情報偽装に対する問題だが、以前はリファラー情報を発行するのは攻撃を踏んでしまった自分自身なので、リファラー情報を偽装する動機がなく、この対策は安全であるとされていた。しかし、Flashのアクションスクリプトを利用することでではRefererヘッダーを自由に作成できてしまうため偽装できてしまう。よって、この対策だけでは安全と言えなくなった。

    修正履歴

    【2006/11/8】 リファラーで発信元をチェックについて、Flashのアクションスクリプトを用いることでリファラーを偽装できる手段が発見されましたため、内容を全面的に見直しました。

またそれか。Refererのクロスドメイン送信がデファクトスタンダードになったとでも言うのかね? そんなのは Flashの仕様の脆弱性*1とするべきものだ*2。独自の対策において配慮に入れるのは自由だが、あるいは、回避策として伝えるならわかるが、あたかもそれがスタンダードになったかのように公衆に伝えるのはやめてもらいたい。*3

Webアプリケーションはサーバ側だけのシステムではない。法律ではわざわざ「電子情報処理組織」という用語を用意しているくらいで、クライアント側と一体になってひとつのシステムを構成するものである。しかも、クライアントはそのサーバ専用ではなく任意のサーバ向けであるし、サーバもまた複数の種類のクライアントによって利用される。したがって、あるセキュリティ上の脆弱性が存在するとき、その原因がサーバ側にあるのかクライアント側にあるのか、その責任の切り分けが常に重要となる。

しかしながら、Webアプリケーションで用いられる各技術要素は、一部を除いて、セキュリティに関わる仕様が標準規格として明確にされているわけではない。HTTPの規格は存在しても、その上で動く「Webアプリケーション」についての規格というものが存在するわけではない。結果論としての攻撃可能性の有無はクライアント側のブラウザ等の個々の仕様に左右されるが、全体の標準が何かひとつのブラウザ仕様によって決定されるわけではない。

したがって、ブラウザ側の脆弱性は実装の欠陥だけを指すものではなく、仕様の欠陥を指すこともある。ある製品の仕様が欠陥であるかどうかは誰が決めているかというと、それは、業界のコンセンサスによって暗黙的に決まっていると言うほかないだろう。

そうしたコンセンサスを決定付ける大きな要素は、BUGTRAQなどの脆弱性報告の場であろう。特に脆弱性を発見して公表する人の役割は大きい。発見者は、発見した現象の原因がどこにあるかを示し、それがどのように危険であるかを示しながら、製品側の仕様に問題があるという帰結を導いて公表する。これに反対意見がなく、製品ベンダーがそれを修正するならば、あるいは、追試や類似事例の報告の積み重ねによって、その考え方が強まったならば、そこで新たなデファクトスタンダード*4が形成されたということになる。

Refererの話に戻ると、攻撃者が任意に指定したRefererを被害者に送信させることはできないということが、デファクトスタンダードになっているかどうかは、tDiaryのCSRF脆弱性の解決策を導き出す際に、職場の同僚の大岩さんと議論した(2005年7月の話)。その議論の流れは概ね次のようなものだったと記憶している。


高木: CSRFの解決策なんだけど、Basic認証の場合をどうするかなのよね。tDiaryとか。

大岩: tDiaryはたしかにCSRFできちゃうでしょうね。

大岩くんは、tDiaryでCSRFが実際に可能なことを実験してみせた。

高木: やはりね。対策なんだけど、既存の秘密情報がパスワードしかない。ユーザ名はCGIの「HTTP_USER」で取れるんだけど、パスワードって取れないの?

大岩: できない。できないようになっている。

高木: どうして?

大岩くんは、CGIでBasic認証のパスワードを取得できないようになっている理由を説明した。

高木: なるほど。それはそれで興味深いけど別問題として置いといて、そうすると次は、秘密情報を乱数で作る方法なんだだけど、Rubyに secure random(暗号学的に安全な擬似乱数生成系)ってある?

大岩: なかったと思う。ないですね。

高木: /dev/urandom を直接使うのはどうなの?

大岩: OSによっては存在しない。tDiaryは実行環境としてOSを指定していないので、どのOSでも使えるようにしないと。

高木: ここで自作するのはどうかと思うね。Rubyが用意するべきだから。そうすると次は、時刻情報を使う方法だけど、HMACとかの鍵付きハッシュを使うことになるので、鍵の管理が生ずるよね。

大岩: うん。いやな感じですね。

高木: そう。tDiaryは個人で使うとは限らないから、ユーザの安全のための鍵管理を、管理者が責任を負うというのは避けたいね。ときどき変更するべきとか、十分に予測できない文字列にすべきとか。ユーザのパスワードを使えば、ログインのセキュリティと同等にユーザの責任とできるのだけど、パスワードは取得できないわけで。そうすると、ユーザごとの固定のキーをユーザに設定させる方法になるのかな?

大岩: 秘密情報として弱いのでは?

高木: そうだね。ただ、弱いキーが設定された場合はユーザの責任にできる。そうすると、Refererチェックの方がよい場合もあることになるんだけど……

大岩: で、例の話ですね。

高木: Referer送出を止めている人の件ね。昔のNorton Internet SecurityではデフォルトでRefererを止めていたらしいけど、最近のバージョンではデフォルトでは止めてないらしい。今時だと、止めている人はMozillaのextensionとかでサイトごとに個別に止めてるんじゃないか。だから、ユーザの選択で、固定のキー方式か Refererチェック方式か、選ばせるという方法が考えられる。

大岩: Referer方式は、同じホスト上のページに罠があるとチェックになりませんね。tDiaryは日記ですから。でも、これは POSTに限定することで防げますね。

高木: POSTでのリンクを書き込まれたら?

大岩: コメント欄やトラックバックにはHTMLタグを許していないので POSTリンクは作れない。日記本文には POSTリンクを作れるけど、tDiaryでは、異なるユーザの日記は異なるホストに配置することになっているので、Refererでチェックできる。

高木: 異なるホストとは?

大岩くんは、同一ホストに異なるユーザの日記本文が書ける場合の脅威について説明した。

高木: なるほど。

大岩: ところで、Refererって本当に偽装させることができないことになってるんですか?

高木: たとえば?

大岩: リダイレクトとか。

大岩くんはリダイレクトについて調査した。

大岩: リダイレクトでは起きないようになっているようですね。Ajaxはどうなってるんですか? XMLHTTPはヘッダがセットできるんじゃなかったでした?

高木: ヘッダがセットできることは新しくない。XMLHTTPとかはJavaアプレットと同類で、Javaアプレットでは90年代のころから、URLConnection に setRequestProperty というメソッドがあってヘッダをセットできるようになっていたんだけど、アプレットの置かれているホスト以外には接続できないよう、セキュリティ制限がかかっているので、問題は起きないようになっている。XMLHTTPも当然、同様の制限がかかっているはずだよね。

大岩: 他のサイトを表示させる方法ってありませんでした?

高木: AppletContextクラスの showDocument() メソッドのことかな? これは、ブラウザを指定されたURLにジャンプさせるだけの機能なので問題ない。アプレットで結果を受信できないからね。ヘッダをセットする方法も用意されてないし。

大岩: ふーん。

高木: というわけで、tDiaryの対策なんだけど、Refererチェックをデフォルトとして、Refererを止めてる人向けに、固定キーをユーザの責任で設定させてそれを使う方法も選べるようにするというのがよいと思う。

大岩: 了解、実装します。デフォルトは Refererの方にして、設定で変更するときだけ Refererオフを解除してもらうようにします。

高木: 設定画面のUIに工夫が必要だね。うまく説明しないと。

二人は、修正案をtDiaryの開発元に伝えるとともに、tDiaryのCSRF脆弱性としてIPAに届け出た。

数日後……

大岩: IEは信頼済みサイトゾーンだと、XMLHTTPは任意のサイトへ接続できるという話が話題になってますね。

高木: ほほう。セキュリティ設定の「ドメイン間でのデータソースのアクセス」が、信頼済みサイトゾーンでは「有効にする」がデフォルトになってるからかな。まあ、信頼済みサイトゾーンに登録する人の責任かな。

大岩くんは、実験してみた。

大岩: たしかにその設定だと任意サイトへ接続できるようですが、Refererはセットできないようですね。他にもセットできないヘッダがあるようです。

高木: ほほう。そういえば、TRACEメソッドも使えなくなったという話をどっかで見たよ。Microsoftはどういう理由で止めるようにしたんだろうね。

大岩: 例の、CGIでパスワードが取れないようになっている理由と同じじゃないですか? Mozillaはどうなってるんだろ。

大岩くんは、実験してみた。

大岩: ヘッダは禁止されてないようです。でも、Mozillaに信頼済みサイトゾーンはないし、任意サイトへの接続は禁止ですからね。

翌日……

大岩: XMLHTTPの脆弱性を発見しましたよ。Mozillaで、リクエストヘッダのフィールド値に改行コードが入れられちゃいます。

高木: おお。HTTPレスポンス分割のクライアント側版ということ?

大岩: そう。いわば「HTTP Request Splitting」ですね。それから、(略)

大岩くんは見つけた脆弱性を説明し、脆弱性レポートをMozillaに報告した。さらにいくつかの発見をして、追加レポートをMozillaに報告した。

高木: その脆弱性を使うと、ドメインをまたがってリクエストを送れるので、Refererを偽装させることができるけど、ブラウザの脆弱性だね。

大岩: そう。

高木: request splittingしないで単に Referer:をセットする件はどうなった?

大岩: 脆弱性レポートで最後に「the following headers should not be overwritable」として Refererと Dateと Upgradeと Viaも挙げたんだけど、同一ホスト制限があるから問題が見つからないと言われたので、まあ blacklist じゃなくて graylist だと言った。修正されないかも。

高木: まあ、セキュリティの問題はないとしても、HTTPの仕様として、アプレットやXMLHTTPからのリクエストというものにおける Referer というのをどう考えるかだね。RFC 2616によると、「The Referer field MUST NOT be sent if the Request-URI was obtained from a source that does not have its own URI, such as input from the user keyboard.」とあるけど、これはどういう意味かな――(1)。まあ、XMLHTTPで取得したページのURIを Refererとして次の XMLHTTPのリクエストを出すというのは間違っていないかも。そうすると、Refererのセットを全部禁止するというわけにもいかないのかなあ。

大岩: ところで、XMLHTTP以外にも同類のものに脆弱性があるかも? Flashとかスクリプトがあるんでしたよね?

高木: あるかもね。Flashは作ったことがないので知らないけど、JavaScriptのようなものがあるらしい。PDFとかもあり得るのかな。既に誰かが調べてるだろうね。


詳細は大岩さんが技術文書の形式でそのうち書いてくれるだろう。

このように、前例に照らして、同一ホスト以外に任意のヘッダを送ることは、それを許すような製品があればそれは脆弱性であるというコンセンサスが業界にあると判断した。

そしてその後、2006年7月に、Amit Klein氏(Smuggling系の脆弱性発見で知られる)が「Forging HTTP request headers with Flash」という記事をBUGTRAQで公表した。

この問題は私なりに整理すると次のように言える。

まず、FlashのActionScriptには、任意の指定のURLへのアクセスを発生させるものとして、getURL() というAPIが古くからあったらしい。これは、Javaアプレットにおける showDocument() と同じで、ブラウザを指定のURLにジャンプさせるものらしい。(ただし、JavaのshowDocument()では POSTメソッドによるアクセスを生じさせることはできなかったのに対し、ActionScriptのgetURL()では、POSTメソッドも可能らしい。)

それが後にFlash Player 5のバージョンでXMLクラスが、Flash Player 6でLoadVarsクラスが追加されて、これらは、load()、sendload()、send() というメソッドを持つようだ。

このうち、load() と sendload() については、Javaにおける URLConnectionに相当するものと言え、ヘッダをセットする addRequestHeader() メソッドが用意されている(Flash Player 6以降)。と同時に、やはりJavaアプレットと同様に、同じホストへの接続しか許さないセキュリティモデルが採用されているようだ。

ところが、send() メソッドだけはこれらとは異なる動作をする。これは、getURL()と同じように、ブラウザをジャンプさせる機能(レスポンスをブラウザに表示させる機能)だ。ちょっと歪な設計だと感じるが、この動作を前提としていることからか、send() は getURL() と同様に任意のホストのURLを指定できるようになっている。そして、getURL() と異なるのは、addRequestHeader() メソッドを使ってヘッダをセットできることだ(Flash Player 5のXMLクラスには存在しなかったのが、Flash Player 6で導入されたようだ)。

リクエストヘッダを設定でき、かつ任意のホストへのアクセスを許すという機能は、同類のプラグイン等で他になかったと思うが、さすがにどんなヘッダもセットできてよいとは考えなかったようで、次のように制限されている。

The following standard HTTP headers cannot be added or changed with this method: Accept-Ranges, Age, Allow, Allowed, Connection, Content-Length, Content-Location, Content-Range, ETag, Host, Last-Modified, Locations, Max-Forwards, Proxy-Authenticate, Proxy-Authorization, Public, Range, Retry-After, Server, TE, Trailer, Transfer-Encoding, Upgrade, URI, Vary, Via, Warning, and WWW-Authenticate.

addRequestHeader (LoadVars.addRequestHeader method), Flash Lite 2.x ActionScript Language Reference

ここに Refererが含まれていない。またこの制限は、送信先によらず規定されているようだ。

これは許すべき仕様だろうか。

CSRFは新しい脆弱性ではない。CSRFの名前が付いたのは2001年7月のことで、そのとき既にRefererチェックによる方法も挙げられていた*5。名前が付いたのはこのときだったが、それまでにも漠然とそれなりに知られていた(が、当時はまださほど重大な脅威とは評価されない状況があった*6)のであり、Refererによる対策をとっていたところはけっこうあったと思われる(参考: Bugzilla Bug 141641: disabling cross-site HTTPS referrers breaks sites)。この時点で既に、Refererは偽装させられないのがWebアプリケーションのセキュリティモデルであるという業界コンセンサスがあったと言える*7

それに対し、Macromedia社が FlashのActionScriptに addRequestHeader機能を追加したのは、Flash Player 6からだから、2002年3月のこと。つまり、後になってやってきたものが、スタンダードを破って登場したということになる。

次に、HTTPのプロトコル規格への準拠性としてはどうなのか。上の会話の中で出てくる (1) の部分を詳しく書くと次のようになる。

HTTP/1.1におけるRefererを規定したRFC 2616の14.36節には、次の記述がある。

14 Header Field Definitions
14.36 Referer

The Referer[sic] request-header field allows the client to specify, for the server's benefit, the address (URI) of the resource from which the Request-URI was obtained (the "referrer", although the header field is misspelled.) (略)

(訳: Referer(原文ママ:"referrer" だが、ヘッダフィールドが綴りを間違えている)リクエストヘッダフィールドは、Request-URIが得られた出所のアドレス(URI)を、サーバの利便性のために、クライアントに明示させることを可能にする。)

The Referer field MUST NOT be sent if the Request-URI was obtained from a source that does not have its own URI, such as input from the user keyboard.

(訳: Request-URIが、それ自身のURIを持たない出所から得られたもの(例えばユーザのキーボード入力のような)である場合には、Refererフィールドは送信してはならない。)

RFC 2616: Hypertext Transfer Protocol -- HTTP/1.1

つまり、リンクをクリックしてのジャンプかどうかは規定にないものの、ジャンプ先のURLが書かれているページ がURLとして存在していない限り、Refererを送信してはいけない、つまり、少なくとも、任意のURLを Referer として送信することはしてはいけないことになっている。

そうすると、任意のURLをRefererとして送信してしまう FlashコンテンツやXMLHTTPを使うWebページは、HTTP 1.1に違反した通信をすることになる。だけども、だからといって、そのようなコンテンツを許すブラウザ(およびプラグイン)も、HTTP/1.1 の規格に違反しているとまで言えるかどうかだ。

たとえば、Javaアプレットを想定した場合、Socketクラスを使って任意のTCP通信ができる(ただし接続先はアプレットの置かれているホストのみに制限される)のだから、ここで、HTTP/1.1もどきの通信を実装することができる(アプレットで作られたブラウザ上ブラウザのようなものが実現可能)わけだが、そのプロトコルが HTTP/1.1の規格に違反するものとなることは当然起き得るわけで、それが許されているからといって、Socketクラスの使用を許すブラウザが HTTP/1.1の規格に違反するなどと言えるわけがない。ただし、これは、アプレットの置かれているサイトとの通信に限られた話だ。アプレットの置かれたサーバとクライアントは一体となっているので、アプレットが行う通信はサーバ側の意図に沿うものであるから、サーバ側としてはそれは HTTP/1.1でないことを認めているという考え方もできる。それに対して、アプレットの置かれているホスト以外のホストとの通信に関しては、showDocument() 以外は禁止されているとすれば、HTTP/1.1 の規格は結果として守られていることになる。

このように考えると、HTTPのプロトコル規格の面から見ても Flashの仕様は異端だと言うことができるだろう。

次に、一般的に言うと、ある脆弱性があるときに、修正すべき原因がサーバ側にあると言えるか、クライアント側にあると言えるかが、簡単には切り分けられない場合もある。その最たる例は、10月21日の日記「Session Fixation脆弱性の責任主体はWebアプリかWebブラウザか」に書いた、Session Fixationの問題だ。また、仮に原因がどちらかにあるとしても、両方で対策をとることが強く求められる場合もある。そうしたケースでは、個々の状況に応じて結論が導き出されている。以下にこれまでのそれらの前例を列挙し、どのような理由でどのような方針が出されてきたかを整理してみる。

Session FixationとCookie Monster問題

Cookie Monster問題の解決が簡単でない。Set-Cookie:で指定できるdomain属性のドメインの範囲を、ドメイン登録されるドメインの単位で決定するべきであるが、トップレベルドメイン(TLD)によってその下位のドメイン構造が異なっており、各TLDごとの構造を公表する義務がその管理主体に課されているわけでもないため、公式な構造情報が存在しない。Mozillaプロジェクト内で独自にその構造を表すリスト作りが開始されたが、実用化に至っておらず、今後の成り行きも不明である。

RFC 2965で定められたCookie2を利用すればCookie Monster問題は解決するかもしれない(未確認)が、主要なブラウザで実装されておらず、現時点では現実的な解決策でない。またこの場合、Webアプリケーション側もCookie2を使用するように変更が必要となる。

他方、Webアプリケーション側での対策は比較的容易な場合が多いと思われ、また、かつては元々ログイン後にセッションIDを発行するのが普通だったところ、近年になって、自動セッション管理機能を持つWebアプリケーションサーバの普及とともに、ログイン前からセッションIDを発行するサイトが増えてきたことからこの問題が顕在化してきたという経緯があるので、ログイン後にセッションIDを発行するようにするという方針は自然な面がある。

以上のことから、責任の所在がサーバ側であるかクライアント側であるかが極めて不明確な事例であるものの、サーバ側で対策をとるのが妥当と考えられるようになってきているように思う。

https:// のURL上にセッションIDを乗せることの是非

URL上にセッションIDを乗せると、Refererによってリンク先にセッションIDが流出することになるが、リンク先を当該サイト内(もしくは信頼できる関係サイト)のみに限定しているならば流出とは言えず、問題ないとする考え方が存在するところ、SSLによる暗号化で情報を保護しているつもりのページが、セッションIDをURLに乗せており、かつ、http:// のページへのリンクが存在すると、パケット盗聴によりセッションIDを盗まれることになる。

https:// のページから http:// のページへのリンクにおいて、Refererを送出するか否かはブラウザによって異なるが、これは、RFC 2616のSecurity Considerationsの章に次のように書かれていることが影響しているものと思われる。

15.1.3 Encoding Sensitive Information in URI's

Because the source of a link might be private information or might reveal an otherwise private information source, it is strongly recommended that the user be able to select whether or not the Referer field is sent. For example, a browser client could have a toggle switch for browsing openly/anonymously, which would respectively enable/disable the sending of Referer and From information.

Clients SHOULD NOT include a Referer header field in a (non-secure) HTTP request if the referring page was transferred with a secure protocol.

Authors of services which use the HTTP protocol SHOULD NOT use GET based forms for the submission of sensitive data, because this will cause this data to be encoded in the Request-URI. Many existing servers, proxies, and user agents will log the request URI in some place where it might be visible to third parties. Servers can use POST-based form submission instead RFC 2616: Hypertext Transfer Protocol -- HTTP/1.1, 15.1.3 Encoding Sensitive Information in URI's

「SHOUD NOT」であって「MUST NOT」ではないこと、また、Security Considerationsにしか書かれていないことから、規格としてRefererを送出することが禁止されているわけではない。また、次の段落でこれと並置して、sensitive dataをGETで送るようにしないべき(SHOULD NOT)とサーバ側の責任にも触れていることから、ブラウザがその場合にRefererを送っても、ブラウザの脆弱性とみなすコンセンサスはできていないと思われる。

また、https:// ページから他のドメインの https:// ページへのReferer送信については、どのブラウザも送信するようになっている*8ため、「https:// のURLならセッションIDを乗せてもよい」ということにはならない。

したがって、セッションIDをURLに乗せることが許され得るのは、リンク先が当該サイト内(もしくは信頼できる関係サイト)のみに限定されていて、かつ、リンク先が https:// のページである場合だけとなり、ルールとしては複雑なものとなる。

また、(携帯電話向けサイトの場合を除いて)セッションIDをURLに入れざるを得ないとする理由がない。(1990年代にはユーザがcookieの受け入れを拒否するのが流行したが、現在はそうでもない。また、POSTでセッションIDを引き継ぐ方法もある。)

以上のことから、2005年3月当時、URLに埋め込むIDに頼ったセッション管理方式の脆弱性(2)− REFERER情報流出によるセッションハイジャック攻撃の問題 −の開発者向けのアドバイスとして、URLにセッションIDを乗せないことを推奨した*9

TRACEメソッドをサーバ側で止めるべきか

2003年1月に「Cross-Site Tracing (XST) -- The New Techniques and Emerging Threats to Bypass Current Web Security Measures Using TRACE and XSS.」という報告が発表されたが、当時BUGTRAQでは、

I just finished reading this so-called whitepaper and the press release, and all I can say is hyped, sensationalised snakeoil.(略)

System administrators should most definitely not waste their precious time on implementing the silly workarounds suggested, such as disabling TRACE/TRACK requests. (略)

RE: TRACE used to increase the dangerous of XSS., BUGTRAQ, 2003年1月23日

という酷評も出た。なぜなら、TRACE単体で脆弱性になるわけではなく、XSS脆弱性という他の脆弱性が存在する場合にだけそれが起きるという話だったからだ。しかし、全く意味のない指摘ではなく、「Basic認証を使用している場合にXSS脆弱性があるとパスワードが漏れる」という、それまでには知られていなかったリスクが明らかになったものだった(参考 [memo:5237] )。

この発見が当時意味を持ったのは、当時はまだXSS対策が十分に普及しておらず、対策漏れの可能性を無視できない状況があり、その理由からcookieによるログイン管理自体にリスクがあると考えられ、Basic認証の方がより安全であるとする考え方が存在していたからであろう。それが、TRACEメソッドとXMLHTTPのTRACEサポートによって、Basic認証も同様のリスクがあるということが明らかになった。

これ以降、猫も杓子もHTTPサーバでTRACEメソッドを止めるという対策が流行した。脆弱性スキャナによっては、TRACEメソッドが応答するというだけで機械的に「脆弱性」としてカウントするものも現れた。それ自体は脆弱性ではないし、Basic認証を使用していないなら関係のないことなのにだ*10

そのようになったのは、TRACEメソッドを止めることが極めて簡単であり(Apacheでは最近まで止める設定機能がなかったが)、通常のWebサイトでTRACEは無用の長物だと考えられたからであろう。

ところで、ブラウザからTRACEメソッドでのアクセスを発生させられるようになったのは、このMicrosoftのXMLHTTPが初であったのだから、これは本来、ブラウザ側の脆弱性だとみなすべきものだったかもしれない。つまり、「XMLHTTPでTRACEメソッドを許すべきでない」という主張もできたはずだ。しかしなぜか、最初の報告はそれを提案しておらず、サーバ側で止めることを推奨していた。

そして、IEはその時点ではTRACEを止めることはしなかった。それは、他にXSS脆弱性がない限りそのリスクは生じないからだろう。さらに言えば、最初の報告から数日後の2003年1月26日に「XS(T) attack variants which can, in some cases, eliminate the need for TRACE」という新たな指摘が出た。これは、バーチャルホストを利用しているサイトでは、TRACEを止めていても(XSS脆弱性があるなら)別の方法で同じ結果を招くことを指摘したものだった(参考 [memo:5419])。このことからも、TRACEを無効にするのがスタンダードだとするコンセンサスは得られなかったのだろう。

さらに、2006年1月に「Technical Note by Amit Klein: "XST Strikes Back"」という指摘が出たが、これは、「サーバで止めてもプロキシサーバがTRACEを応答しちゃうよ」という指摘で、他にXSS脆弱性がないかぎり問題でないのは変わらなかった。

しかし、IE 6 SP2ではTRACEを止めている(理由は不明)。そして、Mozillaは2005年に別の明確な理由でTRACEを止めた。今では、TRACEメソッドでリクエストを発行してレスポンスを取得できることが脆弱性であると考えられていると言ってよいだろう。

Cookieによるセッション管理は脆弱か

2000年ごろには、cookieがユーザに毛嫌いされており、cookieを使わないセッション管理の実装が流行しかけていた。後に、URLにセッションIDを乗せる方法は、Refererによって流出するという理由から不適切であるという考え方は普及した。それでもなお、cookieによるセッション管理は危ないとする意見がしばしば見られた。それは、2001年くらいまでは、Internet Explorerにcookieが漏洩する脆弱性がたびたび発見され、しかもなかなか修正されないうえ、Microsoft社が危ないサイトに行かなければ問題ないといった理由で重要視していなかった状況があり、現実問題としてcookie漏洩によるセッションハイジャックの危険性は無視できない状況にあった。そのため、Basic認証やSSLのクライアント認証を使うべきであるという主張もいくらか支持されていた。

しかし、Basic認証やSSLクライアント認証ではログアウト機能を実現できないといった理由から、そうした主張は受け入れられることはなかった。また、後になって、Microsoft社のセキュリティに対する対応の方針転換があり、受動的攻撃でcookieが漏れるタイプの脆弱性(クロスドメイン系の脆弱性)は、同社も危険度の高いものとして扱うようになった。

このような情勢から、現在では、cookieによるログイン管理は普通のものとして評価されるようになり、ブラウザ側のクロスドメイン系の脆弱性は修正が強く求められるようになっている。

Smuggling系の脆弱性の責任は誰が負うか

HTTPの規格に違反する通信を処理してしまうのが原因のものについては、HTTPサーバ、CGIインターフェイス、プロキシサーバ、ブラウザのそれぞれが、規格に沿うように修正する責任があるだろう。ただし、規格に沿っても解決しない問題もあるそうだ(大岩さん談)。(以下、未整理)

このように、ブラウザ側の脆弱性かサーバ側の脆弱性かが簡単には判断できず、総合的に評価せざるを得ない場合がある。その際に加味されることは、(1)解決策は規格に照らして自然か、(2)解決策は容易なものであるか、(3)解決策がもたらす副作用は十分に小さいか、(4)解決策の実施は現実的であるかなどであろう。

FlashのXML.send()がaddRequestHeader()で任意のサイトに指定のRefererを送信できてしまう今回話題の事例は、これらの微妙なケースと比較すれば、明白に Flash側(またはブラウザ)の責任であると言える。なぜなら、(1)その機能の存在は HTTP/1.1のMUST NOT規定に違反する通信を生じさせるし、(2)それを止めることは容易に実現できる(既製の禁止リストにRefererを追加するだけ)し、(3)止めても何ら不都合はないと思われるし、(4)Flashだけが解決すればよいことで現実的だからである。

このような評価に位置する問題において、「サーバ側の対策ですよ! Flashの仕様ですから!」などと呼びかけることは、製品を修正しないベンダーを認めることにもなることに注意したい。

Amit Klein氏はこれまでに数々の報告、特に、複合的要因によって生ずる問題について、BUGTRAQ等で公表しているが、いずれも、ベンダーに事前に通知した様子が見られない。彼はそういう方針なのだろう。Webアプリケーション側の脆弱性として新たな種類の問題を発見したことにすれば、ベンダーの応答を待つことなく、広く公表して注意を呼びかけることは正当なものとすることができる。明らかにクライアント側の脆弱性と言えるような部類の問題(例えば、XMLHTTPの引数値に対する改行やタブの注入等)であっても、あえてWebアプリケーション側での対応を呼びかけるようなことをしているし、古いバージョンのブラウザを前提(2006年の時点でIE SP1を前提とか)にしたexploitを示して、Webアプリケーション側の対応を呼びかけていたりする。

もちろん、古いバージョンのブラウザを使用しているユーザを想定することが必須の要件である環境(イントラシステムとか)もあるのだから、そうした場合に備えて、回避策としてこうした知識の蓄積も必要である(そうした話題は他にも無数にあるだろう)が、それは、デファクトスタンダードの形成(Referer送信の標準仕様)とは区別して議論するべきことだ。

たしかに、特定の作り方になっているWebアプリケーションの例(A)を挙げなければ、クライアント側のその挙動(B)を脆弱性と指摘できないケースでは、必然的に、(A)が危ないかのような話にもなってしまう。(A)の作り方を止めるべきだとすれば、(B)は直さなくてもよいということにもなる。ここで、全体のデファクトスタンダードのあり方として、どっちを直すのが自然で現実的かということを見極めて示すのが、Webアプリケーション脆弱性の専門家のなすべき仕事だろう。

ましてや、個人的な都合から「Refererはやっぱり駄目なんだ(ということにしたい)」と、感情に判断を左右されるようでは困る。

駄目な技術文書の見分け方 その1の2

前回の日記駄目な技術文書の見分け方 その1で、

マスコミの記事でよく見かける表現に、「完全に○○ないわけではない」というものがあるが、これは断定できないことを言うときの逃げ口上だってことを知っておいたほうがよい。

と書いたが、「完全に○○ないわけではない」はもうちょと違う表現じゃなかったか?と思いつつもズバリの表現を思い出せなかった。それを思い出した。「完全に○○ないとは言い切れない」だ。「ないとは言い切れない」でGoogle検索するとマスコミの記事がワラワラと出てくる。

もしや自分も使ってはいないかと、念のため検索したところ、1996年に使っているものが1つみつかった。今の自分では考えられない文章だ。マスコミの文体が伝染していたのだろうか。

*1 またはブラウザのプラグインAPIの脆弱性という見かたもあり得る。

*2 Macromedia(つまりAdobe社)が send() における addRequestHeader() での Refererの指定を許す仕様をどう考えているかだが、Flash Player 9では既に Refererは addRequestHeader()で送信できない仕様になっているようだと報告されている。Flash Player 8以前で送信できる仕様が確認されている(10月に指摘された別の脆弱性であるheader injectionを使わずに送信できる)わけだが、Adobe社は Flash Player 8以前のサポート(脆弱性対応)を中止しているのではないか?

*3 周りの人もちゃんと言ってあげたほうがよいよ。

*4 市場競争の結果として生ずる標準という意味の「デファクトスタンダード」とは少し意味が異なるかもしれない。

*5 これが「The 90% solution: Referer tests」とされているのは、Refererを止めているユーザが使えなくなることを理由としている。

*6 当時は、タブブラウザではなかったし、Windowsも毎日リブートしていたので、ルータの管理者画面などに対するログイン状態が長く続くことはなく、脅威は低く見積もられていた。最近では、ブラウザを立ち上げっぱなしで使うように変化してきたため、そのログイン状態が長く続くようになり、脅威は増した。また、SNSなどのようにログインしっぱなしで使うサイトで、脅威が無視できないサイトが増えてきたという情勢変化がある。(関連: 2005年7月3日の日記「クロスサイトリクエストフォージェリ(CSRF)対策がいまいち進まなかったのはなぜか」)

*7 攻撃者本人による偽Referer送信がセキュリティ対策にならない話と嬉々として混同するせっかちな人はいただろうけども。

*8 Mozillaは一時期これも止めるようにしたが、「銀行のWebアプリが動かなくなる」といった指摘があり、RFCの記述に従って、デフォルトでは送信する(設定で止められる)ように変更された経緯がある。(Bugzilla Bug 141641

*9 この主張の理由として4番目に、「リンクされていないサーバへのReferer:の誤送出」としてブラウザのバグの可能性を挙げているが、これは補助的な理由にすぎず、これだけの理由ではこの主張をしていない。

*10 私も、TRACEメソッドが応答することをwarningとして出力する検査ツールを開発したことがあるが、Basic認証が使われている場合にだけ出力するようにしている。

本日のリンク元 TrackBacks(100)

2006年11月18日

はてブを使うことにした

昨日は日経デジタルコアの席で、はてなの川崎さんにお目にかかった。

この機会に、はてなブックマークを使うことにした*1。といっても、本来のブックマークの用途ではなく、一行批評の用途で使用する。批評の理由を100字で書ききれるはずもなく、不適切な批評となる可能性があるのでこれまで使用してこなかったが、ひとまずやってみることにする。うまくいかないようであれば中止する予定。

*1 一昨年にはてなダイアリーをやめたが、それは複合的理由によるもので、元々 tDiaryを使いたかった。

本日のリンク元 TrackBacks(71)

2006年11月23日

怪我をした

今朝ちょっと怪我をした。しばらく、仕事でご迷惑をおかけする方面があるかもしれません。すみません。

本日のリンク元 TrackBacks(100)

最新 追記

最近のタイトル

2024年11月11日

2024年07月28日

2024年07月27日

2024年07月07日

2024年04月07日

2024年04月01日

2024年03月23日

2024年03月19日

2024年03月16日

2024年03月13日

2024年03月11日

2023年03月27日

2022年12月30日

2022年12月25日

2022年06月09日

2022年04月01日

2022年01月19日

2021年12月26日

2021年10月06日

2021年08月23日

2021年07月12日

2020年09月14日

2020年08月01日

2019年10月05日

2019年08月03日

2019年07月08日

2019年06月25日

2019年06月09日

2019年05月19日

2019年05月12日

2019年03月19日

2019年03月16日

2019年03月09日

2019年03月07日

2019年02月19日

2019年02月11日

2018年12月26日

2018年10月31日

2018年06月17日

2018年06月10日

2018年05月19日

2018年05月04日

2018年03月07日

2017年12月29日

2017年10月29日

2017年10月22日

2017年07月22日

2017年06月04日

2017年05月13日

2017年05月05日

2017年04月08日

2017年03月10日

2017年03月05日

2017年02月18日

2017年01月08日

2017年01月04日

2016年12月30日

2016年12月04日

2016年11月29日

2016年11月23日

2016年11月05日

2016年10月25日

2016年10月10日

2016年08月23日

2016年07月23日

2016年07月16日

2016年07月02日

2016年06月12日

2016年06月03日

2016年04月23日

2016年04月06日

2016年03月27日

2016年03月14日

2016年03月06日

2016年02月24日

2016年02月20日

2016年02月11日

2016年02月05日

2016年01月31日

2015年12月12日

2015年12月06日

2015年11月23日

2015年11月21日

2015年11月07日

2015年10月20日

2015年07月02日

2015年06月14日

2015年03月15日

2015年03月10日

2015年03月08日

2015年01月05日

2014年12月27日

2014年11月12日

2014年09月07日

2014年07月18日

2014年04月23日

2014年04月22日

2000|01|
2003|05|06|07|08|09|10|11|12|
2004|01|02|03|04|05|06|07|08|09|10|11|12|
2005|01|02|03|04|05|06|07|08|09|10|11|12|
2006|01|02|03|04|05|06|07|08|09|10|11|12|
2007|01|02|03|04|05|06|07|08|09|10|11|12|
2008|01|02|03|04|05|06|07|08|09|10|11|12|
2009|01|02|03|05|06|07|08|09|10|11|12|
2010|01|02|03|04|05|06|07|08|09|10|11|12|
2011|01|02|03|05|06|07|08|09|10|11|12|
2012|02|03|04|05|06|07|08|09|
2013|01|02|03|04|05|06|07|
2014|01|04|07|09|11|12|
2015|01|03|06|07|10|11|12|
2016|01|02|03|04|06|07|08|10|11|12|
2017|01|02|03|04|05|06|07|10|12|
2018|03|05|06|10|12|
2019|02|03|05|06|07|08|10|
2020|08|09|
2021|07|08|10|12|
2022|01|04|06|12|
2023|03|
2024|03|04|07|11|
最新 追記