| SQLインジェクション対策はおすみですか? 開発開始時点からのコンサルティングから、公開済みWebサイトの脆弱性検査、 脆弱性発見後の適切な対策まで |
2008-08-18 PHP
●PHP:session_set_save_handlerリファレンスマニュアルのサンプルにパス・トラバーサル脆弱性
PHPのsession_set_save_handlerのリファレンスを眺めていて、ふと、これはパス・トラバーサルの脆弱性があるのではないかと思いました。
function read($id)
{
global $sess_save_path;
$sess_file = "$sess_save_path/sess_$id"; // ← ファイル名の組み立て
return (string) @file_get_contents($sess_file);
}
function write($id, $sess_data)
{
global $sess_save_path;
$sess_file = "$sess_save_path/sess_$id"; // ← ファイル名の組み立て
if ($fp = @fopen($sess_file, "w")) {
$return = fwrite($fp, $sess_data);
...
session_set_save_handler("open", "close", "read", "write", "destroy", "gc");
...
ここで、readはセッションデータを読み出す関数、writeはセッション値を保存する関数で、session_set_save_handlerでセットしておくものです。コメントで「ファイル名の組み立て」と示している部分でファイル名をセットしていますが、変数$idの値(セッションID)の値が未検証のまま使われています。
問題は、PHP処理系にてセッションIDの値がどの程度チェックされるかです。よく知られているように、PHPにはSession Adoptionの問題があり、素のままの状態では外部からCookie PHPSESSIDにより指定されたセッションIDをそのまま受け入れます。私が色々な文字で試した範囲では、「<」、「>」、「'」、「"」に関してはチェックが行われており、これらの文字がPHPSESSIDに含まれていた場合には、セッションIDの再設定が行われました。一方、それ以外の文字、とくに「/」、「.」、「\」などは特にチェックされないまま素通ししてしまうので、パストラバーサルの脆弱性となります。
このサンプルを流用しているようなケース、あるいは類似の処理を行っている場合(session_set_save_handlerにて、ファイルによるセッションデータ保存を行っている場合)には、この問題の影響を受けます。
この問題の影響範囲ですが、情報漏えいの可能性は低いと考えられます。パス・トラバーサルの技法で任意のファイル名を指定することは可能ですが、たまたまPHPのセッション保存形式と適合する形式のファイルでなければ、読み出しは行われないからです。そのようなファイルがたまたまWebサーバー上に存在し、かつそのファイル名が類推できる場合に限られますが、そのようなケースは想定しにくいと考えます。
一方、ファイルの破壊(書き込み)については、権限さえあれば任意のファイルを指定して破壊できるので、ある程度の影響が考えられます。UNIX系のOS上でPHP(Apache)を実行するユーザの権限で書き込みが可能なファイルは一般的には限定されますが、権限設定がゆるい場合には影響を受けます。Windows上でPHPが稼動している場合には、影響はもう少し広いと考えられます。
対策について。Webアプリケーション側でこの問題に対応するには、さしあたっては、セッションIDの妥当性確認を行えばよいと思います。セッションIDが英数字のみで構成されているか、あるいは16進文字列として妥当であるかをチェックすれば、パストラバーサルは防げます。
また、この問題はPHPがSession Adoptionの問題があることに起因していますから、Strict Sessin Patchを適用すれば、上記問題も解消されると思います。しかし、その場合でも、防衛的意味でパス・トラバーサル対策としての文字種チェックはしておくべきでしょう。
session_set_save_handlerを使わない状態のPHPでは、パス・トラバーサルの問題は起きないようです。前述の中途半端な文字種チェックといい、session_set_save_handlerを使う場合と使わない場合の挙動の違いといい、ちょっと「イラっ」と来たことを告白します。
なお、この問題を一応脆弱性情報としてIPAに届出ましたが、独立したソフトウェア製品ではないという理由で不受理となりましたので、ここに公開し、PHPの開発者に注意を喚起するものです。
Windows上のPHP 5.2.6およびCentOS 5.2上のPHP 5.1.6で検証しました。
2008-08-19 PHP
●[php]session_set_save_handlerのパストラバーサルで任意コマンドの実行が可能
昨日の日記(session_set_save_handlerリファレンスマニュアルのサンプルにパス・トラバーサル脆弱性 )で、PHPの公式リファレンスマニュアルに出ているsession_set_save_handlerサンプルにはパストラバーサル脆弱性があることを報告しましたが、その影響度について書き漏らしていて、影響度を過小に受け取られることに気がつきましたので補足します。
このパストラバーサルは情報漏えいよりは書き込み・破壊の影響の方が現実的というのはその通りなのですが、Web公開領域のファイルを書き換えられるというリスクを報告していなかった。ここで、HTMLやJavaScript、PHPスクリプトを書き込み、実行できるという問題があります。
以下のコード(a.php)で検証してみました。
// session_set_save_handlerのサンプルコード
// 以下は呼び出し部分
session_set_save_handler("open", "close", "read", "write", "destroy", "gc");
session_start();
$_SESSION['a'] = $_GET['a'];
echo "<body>done<body>";
?>
ご覧のように、クエリストリングaの値をそのままセッションに保存しています。非常に単純化していますが、現実のWebアプリケーションを極小化したモデルです。
ここで、Cookie PHPSESSIDの値を以下のようにセットします。b.phpの部分は、このサーバー上に存在するファイル名を指定します。
PHPSESSID=/../../../../../var/www/html/php/b.php
この状態で、以下のURLでa.phpを起動します
http://host-name/php/a.php?a=<script>alert(document.cookie);</script>
セッションデータはb.phpとして格納され、内容は以下のようになります。
a|s:40:"<script>alert(document.cookie);</script>";
すなわち、今後b.phpにアクセスしたユーザは、ブラウザ上でJavaScriptが起動されることになります。
同様にして、PHPのスクリプトを書き込むこともできます。例えば、以下のようにa.phpを呼び出します。
http://host-name/php/a.php?a=<%3Fphp+echo`find`;%3F>
%3Fは、「?」を表します。すなわち、PHPスクリプト中で、バッククォートによりfindコマンドを実行するスクリプトが書き込まれたことになります。攻撃者はこのb.phpにアクセスすることにより、findコマンドを実行でき、同様にして、ターゲットのwebサーバー上で任意のコマンドを実行できることになります。
このように、書き込み可能なパストラバーサルは極めて危険な脆弱性であり、該当するアプリケーションは直ちに対策をとることをお勧めします。
2008-08-20 ホワイトリスト
●プログラミングではホワイトリスティングが基本ではない
大垣さんのブログにて反論をいただいた。
長文のエントリで、「暇な方だけお付き合いください」と書かれている。あいにく締め切りを抱えていて暇ではないのだが、名指しで反論されている以上、放置するのも失礼なので、簡潔にコメントしたい。
私は、一応セキュリティの専門家の端くれのつもりで、XSS Cheat Sheetをはじめ、大垣さんや金床さんの本にも目を通している。しかし、大垣さんの上記2エントリは、私にはさっぱり理解できない。以下、プログラミングの局面で、XSS向けのホワイトリストが作成可能なのかというポイントに絞って議論する。
プログラミングの基本は仕様に忠実であること
大垣さんが具体的な「ホワイトリストの作り方」を提示してくれないので、どのようなホワイトリストをイメージしてよいのか分からない。私の主張は、以前ブログに書いている内容(アプリケーションの先頭で行う入力値検証は業務要件により行うべし)の通りだ。つまり、仕様に忠実に入力値のバリデーションを行えというだけであって、まさかこれが理解できないソフトウェア・エンジニアはいないと思う。それだけだと、XSSの対策にならないという指摘には、はいその通りと答えるしかない。それゆえ、入力値のバリデーションはXSSの保険的対策にはなっても、根本対策にはなりえない。XSS対策としては、それぞれの局面で最適なエスケープなどにより行うのが正解と考える。
大垣さんと金床さんの主張はまったく異なる
次に、大垣さんは、(私がかつて引用した)金床さんの本と大垣さんの主張は同じだという意味のことを書いておられるが、本当にそうだろうか?
金床本の3章XSS ウェプアプリケーション側の対策(P61)には、以下のような記述がある。
【前略】まずはホワイトリスト形式でのアプローチを検討するべきである。
電話番号や年齢、メールアドレスなどのように、使用可能な文字を限定できる項目について考える。これらの項目ではホワイトリスト形式でデータの確認を行った上でHTML中に埋め込むことで、確実なXSS対策を行うことが可能になる。
一方、名前や住所に代表されるような、やや自由度が高い項目も存在する。これらの項目についてはホワイトリスト形式を採ることは難しいため、ブラックリスト形式でのアプローチ、つまりエスケープ処理を行う必要がある。
これならよく分かる。しかも、表現方法は違っていても、現実的なプログラミングレベルでは、徳丸方式と金床方式は実質的な差はない。アプリケーションの仕様として数値や英字などに限定できる場合は、その文字種であることをチェックする(=業務要件チェック)が、たまたまホワイトリスト検査にもなっているというだけのことである。
そして、金床氏が書かれているように、汎用的なホワイトリストを書くことは困難であるので、仕様上自由度の高い入力欄についてはブラックリスト(あるいはエスケープ)するということで、徳丸のやり方と実質的な差はない。強いえて言えば、徳丸方式は、ホワイトリスト検査が可能なパラメタについてもエスケープを要求しているので、より念がいっているわけだし、大垣さんがご本(P26など)に書かれている「多重のセキュリティ」という考えにもかなうだろう。
一方、大垣さんの主張はこうだ。
ホワイトリストの作り方にはケースバイケースで幾つもの指針が有りますが、最も重要と言える指針は
「許容する入力・出力は必要最低限だけ留め、許容した入力・出力は確実に安全である事を保証し、もし不正な入力があった場合は必ず記録し、必要であればプログラムの実行を停止する」
です。これは私の本でも書いている事です。稀に誤解されるので書きますが「プログラムの実行を停止する」とは入力間違いで実行を停止するのではなく、不正な入力で実行停止する事を意味します。
[ホワイトリストはどう作る?より引用]
「入力間違い」と「不正な入力」をどう区別をつけるのだろう。しかもホワイトリストで。大垣さん、私は大垣さんのご本も持っていますので、長文の説明が大変であれば、本の何ページ何行目に書かれているか、ご教授いただけませんか?
それと次の内容は気になったのだが、
私はいつも基本的に能動的なセキュリティ対策を選択するようにお勧めしています。能動的なセキュリティ対策とは全ての入力値の厳格なバリデーション処理であり、出力時に過剰とも言えるエスケープ処理です。いわゆるホワイトリスティングと呼ばれるような対策です。
[ホワイトリストはどう作る?より引用]
入力値のバリデーションはよいとして、出力時のエスケープ処理は普通ホワイトリストと呼ばないのではないか?金床さんの本が、エスケープをブラックリストと説明していることに私は賛成ではないのだが、強いて言えばブラックリスト寄りの考え方だと思う。大垣さんの本でもhtmlspecialcharsを使った対策を説明されている(P144)。これは、「安全な文字を列挙する」アプローチではない。
大垣さんは、セキュリティの専門家としてXSSのホワイトリストの実物を示すべきだと思う。書き下ろすのが大変であれば、書籍などの参照でもよいので示していただきたい。
2008-08-22 XSS
●今こそXSS対策についてまとめよう
沢出水(さわ いずみ)さんからトラックバックを頂戴した。
元々はホワイトリスト方式の優位は神話というエントリでホワイトリストはどう作る?を引用(批判)した事が発端の模様です。
一見真っ向対決しているようなので興味深く読ませていただいたのですが、正直、両者の主張の違いがわかりません。
どちらもXSS等インジェクション系の対策としてはアプリケーションで入力値が正しい形式の範囲内かチェックし、出力時に必要なエスケープ処理を行う、という結論に思えるんですけど…
[ホワイトリストとブラックリストより引用]
ご指摘の通りで、XSS対策は入り口でのバリデーションと表示(HTML組み立て)時のエスケープだ。しかし、元ブログの主題はホワイトリストとブラックリストの比較なので、「ただ、表面的に文章を追っただけでは『何をホワイトリストと呼ぶのか』という部分がだいぶ違う印象を受ける」というところこそが論点だったのだ。
しかし、本当に大切なことはXSS対策をどうするかなので、この機会にXSS対策についてまとめてみようと思う。
XSS対策の基本はバリデーション+エスケープ
「安全なウェブサイトの作り方改訂第3版」などでも指摘されているように、XSS対策の基本はバリデーションとエスケープだ。このうち、根本対策はエスケープの方で、バリデーション(検査)の方は保険対策となる。
入力値チェックはアプリケーションの仕様に従う
昨日の日記(プログラミングではホワイトリスティングが基本ではない)でも書いたように、入力値チェックはアプリケーションの仕様に従っておこなうしかない。その理由については以下のエントリで検討している。
当たり前のことなのだが、入力値検査を仕様よりも厳しく行うことはできない。そして、たいていのWebアプリケーションには、自由形式の入力欄がある(氏名や住所など)。だから、入力値検証だけでは、XSS対策として十分ではない。
XSS対策としてのエスケープが本質だがバリエーションが多い
入力値検査はXSS対策としては保険的な対策であるので、出力時のエスケープが本質的な対策となる。このテーマについては、過去さまざまな機会で取り上げてきた。
- 第2回 対策遅らせるHTMLエンコーディングの「神話」:ITpro
- 第3回 まだまだあるクロスサイト・スクリプティング攻撃法:ITpro
- XSS対策:どの文字をエスケープするべきなのか
- XSS対策:JavaScriptなどのエスケープ
- XSS対策:JavaScriptのエスケープ(その2)
- XSS対策:JavaScriptのエスケープ(その3)
- XSS対策:JavaScriptのエスケープ(その4)
- 画像ファイルによるクロスサイト・スクリプティング(XSS)傾向と対策
上記1.と2.では、エスケープがXSS対策の本質であることを説明したかったのだが、読者の便宜のためにXSS攻撃のバリエーションを多数紹介したところ、攻撃手法の方に関心が移ってしまったのではないかと懸念が生じた。それを補足するために、「XSS対策:…」で始まるエントリでは、XSSが発生する根本的な原理に立ち戻り、どの文字をどのようにエスケープするべきなのか、その考え方も含めて説明した。また、7.では文字エンコードに付随する問題に対する議論、8.では画像XSSに対する対策を説明を行っている。とくに8.は、とても手間の掛ったエントリなので、もっと広く読んでいただけたらなと思う(私はブログでも手抜きはしない流儀だ)。
ホワイトリストもプロアクティブも出てこない
過去のエントリを引用する形で、XSS対策の考え方を説明した。バリデーションは(ホワイトリストではなく)仕様に従って行う、エスケープはホワイトでもブラックでもない、ということで、プログラミング上のXSS対策にはホワイトリストもブラックリストも出てこない。プロアクティブという用語にしても、抽象的かつあいまいな用語で、セキュリティ業界ではバズワードと化しているし、そのような「怪しげな」用語を用いずに、具体的で意味の分かりやすい言葉を選んで説明すればすむことだ。
大切なことは原理から理論的に考えること
私が一貫して訴えていることは、脆弱性の根本原因を探り、そこを対策すべきということだ。ITproでの連載や、最近ではWASForumのカンファレンスでも説明したことだが、インジェクション系脆弱性の根本原因はリテラル(定数)が枠をはみ出し、命令として解釈されることだ。であれば、リテラルとしての正しいエスケープ方法を勉強して、リテラルの枠をはみ出さないようにすればよい。
大垣さんのブログには、「出力時に過剰とも言えるエスケープ処理」とあるが、多くの文字をエスケープすれは安全になるというものでもない。HTML上の場所に応じた適切なエスケープが必要だ
例を挙げよう。以下は、onloadイベントハンドラを動的生成している例である。
#!/usr/bin/perl
use strict;
use CGI;
my $query = CGI->new();
my $a = "abc');alert(document.cookie)//";
my $ae = $query->escapeHTML($a);
print <<END;
Content-Type: text/html; charset=UTF-8
<html>
<script>
function a(b) {}
</script>
<body onload="a('$ae')">
</body>
</html>
END
PerlのescapeHTMLメソッドでエスケープしているし、上記CGIが生成するソースは、
<body onload="a('abc');alert(document.cookie)//')">
と一見よさそうに見えるかもしれないが、上記はalert関数が実行され、Cookie値が表示される。その理由は、JavaScriptに処理が渡る前にHTMLデコードされ、以下のようなスクリプトが実行されるからだ。
a('abc');alert(document.cookie)//')
正解は、いったんJavaScriptの文字列リテラルとしてエスケープしてから、HTMLのエスケープを行うことだが、よりよいガイドラインは、イベントハンドラやSCRIPT要素など、JavaScriptの動的生成を行わないことだ。
上記は、JavaScriptとしてのエスケープを怠っていることが原因であるので、HTMLエスケープをいくら「過剰」に行っても意味はない。
原理から正しく学ぶということは、HTMLなりJavaScriptの文法をしっかり学ぶことでもある。またエスケープを正しく行わないと、セキュリティ上の問題以外に、表示がおかしくなるなどの不具合が生じる。これはプログラマとして必須の知識なのだ。
攻撃技術の進歩に備えるためにこそ、原理からの対策を
大垣さんの指摘のように、攻撃側の技術は年々進歩している。だからといってXSS Cheat Sheetなどの攻撃手法から逆算的に防御方法を学ぶことはできない。仮にそうすれば、どうしても対症療法的になるので、大垣さんの言われる「プロアクティブな」防御法にはならない。対症療法だからこそ、「技術革新の早いWebシステムで具体的なホワイトリストの作成方法を書いても無意味となる可能性がある」のだろう。
そうではなく、原理から対策方法を学ぶことで、攻撃技術の進歩に一喜一憂する必要はなくなるのだ。そして、XSS Cheat Sheetなどの攻撃手法のドキュメントは、原理にもとづく対策方法に抜けや勘違いがないかをチェックする目的で使用すればよいだろう。たとえば、文字エンコードの問題が考慮から漏れていたとして、攻撃方法を学ぶことは、そのような抜けを見つけるのに役立つ。しかし、考慮不足が判明したら、その際には再び原理に立ち戻って対策を考えるのだ。そうすれば、XSS対策:JavaScriptのエスケープ(その4)で指摘しているように、文字エンコードの問題に「過剰なエスケープ」などで対症療法するのではなく、文字エンコードの誤りそのものをチェックするという正しい対策が生まれるのだ。
| SQLインジェクション対策はおすみですか? 開発開始時点からのコンサルティングから、公開済みWebサイトの脆弱性検査、 脆弱性発見後の適切な対策まで |
★ yohgaki [入力値のバリデーションはよいとして、出力時のエスケープ処理は普通ホワイトリストと呼ばないのではないか?金床さんの本が..]