[PR]小規模ECサイトに最適なWAF、SiteGuard Lite

徳丸浩の日記


2008年10月29日

_書籍「はじめてのPHPプログラミング基本編5.3対応」にSQLインジェクション脆弱性

id:hasegawayosuke氏にそそのかされるような格好で、「はじめてのPHPプログラミング基本編5.3対応」という書籍を購入した。

本書は、ウノウ株式会社下岡秀幸氏、中村悟氏の共著なので、現役バリバリのPHP開発者が執筆しているということ、下記のようにセキュリティのことも少しは記述されているらしいという期待から購入したものだ。

目次から抜粋引用
07-07 Webアプリケーションのセキュリティ [セキュリティ]
08-04 データベースのセキュリティ [SQLインジェクション]
09-13 セキュリティ対策 [セキュリティ]

本書をざっと眺めた印象は、「ゆるいなぁ」というものであるが、その「ゆるさ」のゆえんはおいおい報告するとして、その経過で致命的な脆弱性を発見したので報告する。

問題の報告

それは、本書P280に登場する「SQLインジェクション対策用の関数(dbescape)」だ。この関数を本書から引用する。

// SQLインジェクション対策用の関数
function dbescape($sql, array $params)
{
    foreach ($params as $param) {
        // パラメータの型によって埋め込み型を変える
        switch (gettype($param)) {
        case "integer":
        case "double":
            $replacement = $param;
            break;
        case "string":
            // 文字列の場合はエスケープ処理をおこなう
            $replacement = sprintf("'%s'", sqlite_escape_string($param));
            break;
        default:
            die("パラメータの型が正しくありません");
        }

        // SQLを置換し、パラメータを埋め込む
        $sql = substr_replace($sql, $replacement, strpos($sql, "?"), 1);
    }

    // すべてパラメータを埋め込んだSQLを返す
    return $sql;
}

この関数は、「穴埋め形式のSQL文字列と、埋め込むパラメータの配列を受け取り、必要なエスケープ処理を施したSQL文字列を返します」とのことで、以下のように用いる。

$sql = dbescape("SELECT COUNT(id) FROM friend WHERE from_name = ? AND to_name = ?",
                array($from_name, $to_name)); 

これに対して、$from_name = "Johnson"、$to_name = "M'Intosh" として上記を実行すると、以下のような文字列が返る

SELECT COUNT(id) FROM friend WHERE from_name = 'Johnson' AND to_name = 'M''Intosh'

すなわち、バインド機構を自前で実現したようなインターフェースである。

この実装を見た瞬間、違和感を感じた。これは書式文字列の処理に属するものであるので、通常は書式文字列($sql)を左から調べて、書式記号(?)が出てくるたびに、対応するパラメータの処理を行うのが定石的な実装だと思う。そうでないと(上記の場合は出てこないが)書式などのエスケープを上手く処理できない。しかるに、引用した関数では、パラメータの方を調べながら、対応する書式記号(?)を探している。しかも、未処理の部分と処理済の部分がごちゃまぜになっているので、まずいことが起こりそうである。

そう、この関数にはバグがある。パラメータとして"?"を含む文字列を与えた場合、元の穴埋め式SQLに存在した"?"と、新たに埋め込まれた"?"がごっちゃになる。試してみよう。先の例に、第一パラメータとして"?"、第二パラメータとして"AAA"を与えた場合の処理の流れは以下のようになる

0:SELECT COUNT(id) FROM friend WHERE from_name = ? AND to_name = ?
                                                 ↑ '?' に置き換え
1:SELECT COUNT(id) FROM friend WHERE from_name = '?' AND to_name = ?
                                                  ↑ 'AAA' に置き換え
2:SELECT COUNT(id) FROM friend WHERE from_name = ''AAA'' AND to_name = ?

ご覧のように、第一パラメータの変換結果である '?' から、さらに?部分が'AAA'に置き換わることから、意図した結果を得られない。しかも、右側の"?"があまってしまい、SQLの文法違反となる。

それだけならまだよいのだが、AAAの部分に着目いただきたい。この部分は外部から与えた文字列なので、シングルクォートで囲まれてなければならないのだが、上記の過程で、文字列リテラルからはみ出した、すなわちSQL文の式の一部として解釈される状態となった。この時点でSQLインジェクション脆弱性といえる(ライオン(=外部からの文字列)が檻(=文字列リテラル)から抜け出した状態)。

従って、AAAの代わりにSQL文をセットしてやれば、任意のSQLが実行できることになる。やってみよう。今度は第二パラメータとして"or 1=1--"をセットしてやる。変換後のSQLはこうなる。

SELECT COUNT(id) FROM friend WHERE from_name = ''or 1=1--'' AND to_name = ?

本書で想定しているRDB(SQLite)では、--は標準SQL同様コメントとなるので、--から先は無視される。すなわち、SQLの意味が書き換えられた。SQLiteではUNIONをサポートしているし、更新系SQLでは複文をサポートするようなので、様々な悪用が可能となる。

教訓

あらゆるバグは脆弱性になり得る

どうすべきだったか

汎用的なsqlエスケープ関数を用意して、対策をこの関数にカプセル化しようという心意気はよかったのだが、あいにくこの関数にバグがあって、意図がかえって仇となる結果となった。では、どうすればよかったか。

思うに、バインド機構に似た機能を自作しようというのが間違いで、そんなに簡単にできるものではない。PHPのsqlite_xxxx系の関数にはバインド機構が用意されていないようだが、PDOを利用することで、SQLiteでもバインド機構が利用できる。

あるいは、SQLiteの使用をあきらめ、MySQLを使ってもよかった。本書のカバーには「MySQL(データベース)」とある(これはCD-ROMにMySQLが添付されているということらしい)。WindowsでもMySQLは動作するし、実務でも利用機会はMySQLの方がずっと多いだろう。MySQLであれば、mysqli系の関数でバインド機構が利用できる。さらに大切なこととして、「SQLインジェクション対策は原則としてバインド機構を用いるべし」という原則を教えることもできるのだから。


2008年10月14日

_書籍「PHP×携帯サイト デベロッパーズバイブル」の脆弱性

「PHP×携帯サイト デベロッパーズバイブル」という携帯サイト開発のノウハウを解説した書籍が今月初頭に発売され、話題になっている。Amazonの「インターネット・Web開発」カテゴリで1位ということで、たいしたものだ。私も発売前から予約して購入した。

私がこの書籍を購入した動機は大きく二つある。一つは、携帯サイトの最新の開発ノウハウをまとめた書籍に対する期待をしていたということ。もう一つは、セキュリティに対する記述がどの程度あるのかを見てみたいというものだった。

このうち、前者については、期待は叶えられた。非常に盛りだくさんのテーマが手際よくまとめられていて、かつ読みやすい。あまり原理・理屈のことは書いていないが、開発現場では、コピペの情報源として重宝されることだろう。

しかし、問題はセキュリティについての記述である。

本社のサンプルをざっと眺めた限りでは、クロスサイトスクリプティング(XSS)やSQLインジェクションが問題になりそうなサンプルは見あたらなかった(DBは使用しておらず、表示は固定的なものが多いため)。一方、携帯向けWebアプリケーションのセキュリティ問題の定番である認証とセッション管理については問題がある。具体的には、本書の7章である。

著者のブログから、本書の7章の目次を引用する。

Chapter.7 ログイン状態の管理
7-1 本章のゴール

7-2 携帯を使ったログイン
7-2-1 IDとパスワードのログイン
7-2-2 個体識別情報によるログイン

7-3 個体識別情報を理解する
7-3-1 個体識別情報の種類
7-3-2 docomoの個体識別情報
7-3-3 auの個体識別情報
7-3-4 SoftBankの個体識別情報

7-4 ログイン状態を維持する方法
7-4-1 ログイン状態を維持するとは
7-4-2 携帯サイトでのセッション管理

7-5 かんたんログインを実装する

このうち、「7-4-2 携帯サイトでのセッション管理」と「7-5 かんたんログインを実装する」が問題である。

携帯サイトでのセッション管理については、Cookieを実装していないdocomoやSoftbankの一部の端末を考慮して、URLにセッションIDを付与するように説明している。これ自体は正しいのだが、URLにセッションIDを付与した場合のセキュリティ上の注意点が抜けている。既によく知られたことだが、セッションIDをURL上に保持した場合、HTTP Refererにより、他のドメインにセッションIDが漏洩する。また、ユーザの不注意により、セッションID付きのURLをソーシャルブックマークなどで公開したり、メールで知人に送信した結果、セッション情報が外部に漏洩するような事故が現実に発生している。

「かんたんログインを実装する」については、PCで個体識別番号やユーザエージェントを偽装することによる「なりすまし」対策について記述されていない。すなわち、PCからだと自ら収集するなどした個体識別情報により簡単になりすましができるので、ケータイのゲートウェイからのIPアドレスのみアクセスできるように限定しなければならないのだが、その説明が抜けている。

いや、厳密には、以下のように、7章の冒頭の図(本書P250から引用)を見ると、キャリアゲートウェイの「IPアドレスチェック」をせよと指示しているようにも受け取れるのだが、3章「キャリア/機種の判別」を読むと、キャリアの識別をする理由は、HTMLや画像の規格の違いを吸収して適切な表示を行うことが目的と説明されており、セキュリティに関する記述はない。そのため、IPアドレスのチェックが必須だとも書いていない。これでは、本書の読者の多くが、IPアドレス制限をかけないまま「かんたんログイン」機能を実装・公開する懸念がある。


「かんたんログイン」機能そのものは高木浩光氏によりセキュリティ上の問題が疑問視されている(無責任なキャリア様に群がるIDクレクレ乞食 ―― 退化してゆく日本のWeb開発者)のだが、そこで議論されている問題は、キャリアゲートウェイのIPアドレス情報が正しくサイト運営者に行き渡るのかという問題であって、本書のように、キャリアのゲートウェイの確認をしていないのは問題外といえよう。

本書が価値の高い情報を満載していて全体として価値の高い書籍であること、冒頭で述べたように多くの読者が購入しているだろうことを考えると、セキュリティに対する配慮の欠如を惜しむとともに、本書のコピペによる脆弱なサイトが乱造されることを強く懸念する次第である。




参考文献:
WASForum Conference 2008: 携帯電話向けWebのセキュリティ
携帯電話向けWebアプリの脆弱性事情はどうなっているのか
携帯電話向けWebアプリケーションのセッション管理手法
プログラミング解説書籍の脆弱性をどうするか

本日のツッコミ(全1件) [ツッコミを入れる]

_ りゅう [以下のように全く書いていないという訳ではないですが、書いてある位置が実際に影響のある機能の章とかけ離れていたり、問題..]


2008年08月22日 XSS

_今こそXSS対策についてまとめよう

沢出水(さわ いずみ)さんからトラックバックを頂戴した。

元々はホワイトリスト方式の優位は神話というエントリでホワイトリストはどう作る?を引用(批判)した事が発端の模様です。

 一見真っ向対決しているようなので興味深く読ませていただいたのですが、正直、両者の主張の違いがわかりません。

 どちらもXSS等インジェクション系の対策としてはアプリケーションで入力値が正しい形式の範囲内かチェックし、出力時に必要なエスケープ処理を行う、という結論に思えるんですけど…

[ホワイトリストとブラックリストより引用]

ご指摘の通りで、XSS対策は入り口でのバリデーションと表示(HTML組み立て)時のエスケープだ。しかし、元ブログの主題はホワイトリストとブラックリストの比較なので、「ただ、表面的に文章を追っただけでは『何をホワイトリストと呼ぶのか』という部分がだいぶ違う印象を受ける」というところこそが論点だったのだ。

しかし、本当に大切なことはXSS対策をどうするかなので、この機会にXSS対策についてまとめてみようと思う。

XSS対策の基本はバリデーション+エスケープ

安全なウェブサイトの作り方改訂第3版」などでも指摘されているように、XSS対策の基本はバリデーションとエスケープだ。このうち、根本対策はエスケープの方で、バリデーション(検査)の方は保険対策となる。

入力値チェックはアプリケーションの仕様に従う

昨日の日記(プログラミングではホワイトリスティングが基本ではない)でも書いたように、入力値チェックはアプリケーションの仕様に従っておこなうしかない。その理由については以下のエントリで検討している。

  1. Webアプリケーション脆弱性対策としての入力値検証について
  2. アプリケーションの先頭で行う入力値検証は業務要件により行うべし

当たり前のことなのだが、入力値検査を仕様よりも厳しく行うことはできない。そして、たいていのWebアプリケーションには、自由形式の入力欄がある(氏名や住所など)。だから、入力値検証だけでは、XSS対策として十分ではない。

XSS対策としてのエスケープが本質だがバリエーションが多い

入力値検査はXSS対策としては保険的な対策であるので、出力時のエスケープが本質的な対策となる。このテーマについては、過去さまざまな機会で取り上げてきた。

  1. 第2回 対策遅らせるHTMLエンコーディングの「神話」:ITpro
  2. 第3回 まだまだあるクロスサイト・スクリプティング攻撃法:ITpro
  3. XSS対策:どの文字をエスケープするべきなのか
  4. XSS対策:JavaScriptなどのエスケープ
  5. XSS対策:JavaScriptのエスケープ(その2)
  6. XSS対策:JavaScriptのエスケープ(その3)
  7. XSS対策:JavaScriptのエスケープ(その4)
  8. 画像ファイルによるクロスサイト・スクリプティング(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&#39;);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)で指摘しているように、文字エンコードの問題に「過剰なエスケープ」などで対症療法するのではなく、文字エンコードの誤りそのものをチェックするという正しい対策が生まれるのだ。


2008年08月20日 ホワイトリスト

_プログラミングではホワイトリスティングが基本ではない

大垣さんのブログにて反論をいただいた。

長文のエントリで、「暇な方だけお付き合いください」と書かれている。あいにく締め切りを抱えていて暇ではないのだが、名指しで反論されている以上、放置するのも失礼なので、簡潔にコメントしたい。

私は、一応セキュリティの専門家の端くれのつもりで、XSS Cheat Sheetをはじめ、大垣さんや金床さんの本にも目を通している。しかし、大垣さんの上記2エントリは、私にはさっぱり理解できない。以下、プログラミングの局面で、XSS向けのホワイトリストが作成可能なのかというポイントに絞って議論する。

プログラミングの基本は仕様に忠実であること

大垣さんが具体的な「ホワイトリストの作り方」を提示してくれないので、どのようなホワイトリストをイメージしてよいのか分からない。私の主張は、以前ブログに書いている内容(アプリケーションの先頭で行う入力値検証は業務要件により行うべし)の通りだ。つまり、仕様に忠実に入力値のバリデーションを行えというだけであって、まさかこれが理解できないソフトウェア・エンジニアはいないと思う。それだけだと、XSSの対策にならないという指摘には、はいその通りと答えるしかない。それゆえ、入力値のバリデーションはXSSの保険的対策にはなっても、根本対策にはなりえない。XSS対策としては、それぞれの局面で最適なエスケープなどにより行うのが正解と考える。

大垣さんと金床さんの主張はまったく異なる

次に、大垣さんは、(私がかつて引用した)金床さんの本と大垣さんの主張は同じだという意味のことを書いておられるが、本当にそうだろうか?

金床本の3章XSS ウェプアプリケーション側の対策(P61)には、以下のような記述がある。

【前略】まずはホワイトリスト形式でのアプローチを検討するべきである。

 電話番号や年齢、メールアドレスなどのように、使用可能な文字を限定できる項目について考える。これらの項目ではホワイトリスト形式でデータの確認を行った上でHTML中に埋め込むことで、確実なXSS対策を行うことが可能になる。

 一方、名前や住所に代表されるような、やや自由度が高い項目も存在する。これらの項目についてはホワイトリスト形式を採ることは難しいため、ブラックリスト形式でのアプローチ、つまりエスケープ処理を行う必要がある。

これならよく分かる。しかも、表現方法は違っていても、現実的なプログラミングレベルでは、徳丸方式と金床方式は実質的な差はない。アプリケーションの仕様として数値や英字などに限定できる場合は、その文字種であることをチェックする(=業務要件チェック)が、たまたまホワイトリスト検査にもなっているというだけのことである。

そして、金床氏が書かれているように、汎用的なホワイトリストを書くことは困難であるので、仕様上自由度の高い入力欄についてはブラックリスト(あるいはエスケープ)するということで、徳丸のやり方と実質的な差はない。強いえて言えば、徳丸方式は、ホワイトリスト検査が可能なパラメタについてもエスケープを要求しているので、より念がいっているわけだし、大垣さんがご本(P26など)に書かれている「多重のセキュリティ」という考えにもかなうだろう。

一方、大垣さんの主張はこうだ。

ホワイトリストの作り方にはケースバイケースで幾つもの指針が有りますが、最も重要と言える指針は

「許容する入力・出力は必要最低限だけ留め、許容した入力・出力は確実に安全である事を保証し、もし不正な入力があった場合は必ず記録し、必要であればプログラムの実行を停止する」

です。これは私の本でも書いている事です。稀に誤解されるので書きますが「プログラムの実行を停止する」とは入力間違いで実行を停止するのではなく、不正な入力で実行停止する事を意味します。

[ホワイトリストはどう作る?より引用]

「入力間違い」と「不正な入力」をどう区別をつけるのだろう。しかもホワイトリストで。大垣さん、私は大垣さんのご本も持っていますので、長文の説明が大変であれば、本の何ページ何行目に書かれているか、ご教授いただけませんか?

それと次の内容は気になったのだが、

私はいつも基本的に能動的なセキュリティ対策を選択するようにお勧めしています。能動的なセキュリティ対策とは全ての入力値の厳格なバリデーション処理であり、出力時に過剰とも言えるエスケープ処理です。いわゆるホワイトリスティングと呼ばれるような対策です。

[ホワイトリストはどう作る?より引用]

入力値のバリデーションはよいとして、出力時のエスケープ処理は普通ホワイトリストと呼ばないのではないか?金床さんの本が、エスケープをブラックリストと説明していることに私は賛成ではないのだが、強いて言えばブラックリスト寄りの考え方だと思う。大垣さんの本でもhtmlspecialcharsを使った対策を説明されている(P144)。これは、「安全な文字を列挙する」アプローチではない。

大垣さんは、セキュリティの専門家としてXSSのホワイトリストの実物を示すべきだと思う。書き下ろすのが大変であれば、書籍などの参照でもよいので示していただきたい。

本日のツッコミ(全1件) [ツッコミを入れる]

_ yohgaki [入力値のバリデーションはよいとして、出力時のエスケープ処理は普通ホワイトリストと呼ばないのではないか?金床さんの本が..]


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月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で検証しました。

続く(session_set_save_handlerのパストラバーサルで任意コマンドの実行が可能)


2008年07月22日 そろそろWAFに関して一言いっとくか

_三重苦を乗り越えてWAFが普及するための条件とは

PCIデータセキュリティ基準(PCIDSS)がWAF(Web Application Firewall)について言及していることなどから、最近再びWAFへの関心が高まっている。一方、WAFは、一部のユーザや専門家に非常に評判が悪い。なぜ、そのようなことになるのか。本稿では、WAFの基本機能を説明した上で、その限界と運用上の問題を指摘し、今後のWAFの使い方について私見を述べる。

今回とりあげるWAFの基本機能は、以下の三種類である。

  1. 入力値検査
  2. 画面遷移のチェック
  3. hiddenフィールド操作の防止

WAFの機能(1)入力値検査

すべてのWAFの備える基本機能は入力値検査である。これは、パラメタ(クエリストリング、POSTパラメタ、Cookieなど)に対するホワイトリストあるいはブラックリストによる検査を行うものだ *1。SQLインジェクションやXSSなどインジェクション系の脆弱性対策は、この入力値検査が基本となる。

入力値検査-ホワイトリスト検査

IPS(Intrusion Prevention System)が基本的にブラックリスト検査のみであるのに対して、WAFがホワイトリスト検査もできることは、WAFの特徴といえるだろう。ホワイトリスト検査は、Webアプリケーションの入力パラメタ一つ一つに対して、取りうる値の集合を正規表現などで定義し、検査の結果取り得る値から外れていたらリクエストをブロックするというものだ。前述のIPSに対するWAFの「優位」としてベンダーが大々的に宣伝することが多いのがこれだが、実際のサイトに適用するとなると以下のように問題が多い。

  1. ホワイトリスト検査が可能なパラメタは限られている
  2. ホワイトリスト検査の対象パラメタと検査内容は個別に指定する必要があり煩雑である
  3. ホワイトリスト検査は本来アプリケーションで行うべきもの
  4. ユーザビリティの低下を招く可能性

以下、順に説明しよう

ホワイトリスト検査が可能なパラメタは限られている

ホワイトリスト検査が可能なパラメタは、数値や英字など特定の文字種のみを受け入れるパラメータに限られる。具体的には郵便番号、電話番号、クレジットカード番号、ユーザID、メールアドレスなどだ。これら以外の自由記述形式のパラメタに対してはホワイトリスト検査はできない。

ホワイトリスト検査の対象パラメタと検査内容は個別に指定する必要があり煩雑である

前のところで説明したようにホワイトリスト検査可能なパラメタは限られており、かつパラメタ毎に文字種や文字列長などが異なっている。このため、WAFの機能として、URLごとのパラメタを列挙した上で、パラメタ毎に検査方法を定義できるようになっている場合が多い。中規模以上のWebアプリケーションには膨大な数のパラメタがあるため、この設定はかなり煩雑となる。

また、パラメタ毎の文字種などの仕様書があればよいが、なければアプリケーションをリバースエンジニアリングの手法で調べた上でWAFの設定を行う必要がある。文字種の制限が仕様書に記載されていることは期待できない。なぜなら、仕様書に書いてあるくらいならアプリケーション側で文字種チェックが実装されているはずであり、わざわざ手間を掛けてWAF側で設定する必要はないからだ。

最近の高機能WAFは「学習機能」と称してホワイトリストの自動設定ができるとうたっている場合が多いが、私の経験および見聞きした範囲では、学習機能がうまく動いて設定が自動化できたという話は聞いたことがない。最終的には人手によるチェックと修正が必要と考えた方がよいだろう。

ホワイトリスト検査は本来アプリケーションで行うべきもの

先にも少し触れたように、ホワイトリスト検査は本来アプリケーション側で行うべきものだ。郵便番号欄を例に説明すると、3桁および4桁の入力欄を用意して、数値以外の文字が入力されていればエラー表示して再入力を促すことになる。それくらいはアプリケーション側でやるべきだし、WAFでチェックすると後述のようにユーザビリティ的に問題となる可能性が高い

このように書くと、いやWAFで検査するのはラジオボタンなど選択式の入力欄に想定外の文字が入っている場合が主であって、郵便番号はアプリケーション側の検査でよく、この使い方であればユーザビリティ上の問題はないという反論がくるかもしれない。しかし今度は、ホワイトリスト検査が可能なパラメタのうち、どれがアプリケーションでの検査、どれがWAFでの検査とするかを詳細に調べ上げなければならない。これを学習機能で自動することは相当難しいだろう。というわけで、人手で設定するのは面倒だし、自動化には困難が伴う。

ユーザビリティの低下を招く可能性

前に書いたことと関連するが、ユーザの誤入力がWAFのホワイトリスト検査に引っかかった場合、ユーザに適切なナビゲーションを提供することは難しいだろう。そのためにユーザビリティが低下する可能性がある。

入力値検査-ブラックリスト検査

Webアプリケーションのパラメタのうち、ホワイトリスト検査ができない(しない)パラメタについてはブラックリスト検査をすることになる。この場合のブラックリスト検査の目的は、「アプリケーション要件としては禁止されていないが、脆弱性に対する攻撃(SQLインジェクションなど)が疑わしい入力をブロックする」ことになる。このため、ブラックリスト検査は、過剰検知と検知漏れの両方のリスクがある。

過剰検知の問題

ここでいう過剰検知(False Positive)とは、実際には攻撃目的の入力ではないのに、WAF攻撃とみなしてリクエストをブロックする場合を指す。Webアプリケーションの場合、アプリケーション要件的に禁止されていないものをブロックする*2わけだがら、ユーザにとっては不満の原因になる可能性が高い。

検知漏れの場合

検知漏れ(False Negative)とは、実際には攻撃を受けているにもかかわらず、見逃してリクエストを受け付ける場合を指す。すべての攻撃パターンを正規表現などで表現することは元々不可能であるし、検知漏れを少なくしようとすると、今度は過剰検知が増えるという結果になる。

ネガティブか、ポジティブか……それが問題だ

というわけで、LAC川口氏のコラムを引用させていただくことになるわけだが、脆弱性スキャナやIDSなど検査・監視系ツールの場合と異なり、過剰検知はユーザの実行を妨げ、ユーザビリティの低下に直結するため、少し緩めの(ブラック側に倒した)ルールにせざるを得ない。

そもそも入力時点検査での対応では無理がある - サニタイズ言うな

ここまで、ホワイトリスト検査とブラックリスト検査の特徴を説明してきた。ホワイトリスト検査は設定の手間が掛かることと、すべてのパラメタに適用可能ではない。ブラックリスト検査は過剰検知や検知漏れの可能性があり、総合的に判断して、入力値検査で完全な脆弱性対策とするのは無理がある。これは、セキュアWebアプリケーション開発の方法論としては既に結論が出た内容であって、XSSにせよ、SQLインジェクションにせよ、入力時ではなく、出力時(その値を使う時)に適切なエスケープなどにより対策すべき問題であるのに対して、WAFは出力時対応ができないからだ。言い換えれば、WAFはサニタイズ方法論で対応するものであって、元々限界があるのだ。

WAFの機能(2)画面遷移のチェック

すべてのWAFが備えているわけではないが、商用WAFの多くが、なんらかの画面遷移のチェック機能を持っている。これは、大きく分けて二種類あり、(1)外部からの入り口となるページをチェックする、(2)Webアプリケーション内の全ての遷移をチェックする、という方法がある。

まず、(1)の外部からの入り口のチェックについて説明する。右図は、Webで実装された業務システムを想定した画面遷移図である。入り口のページにログイン画面があり、その後は画面遷移に従って、業務メニューから各種の機能に遷移するという典型的な業務システムである。このようなアプリケーションの場合、外部からの入り口はログインページに限定すべきであり、その他のページに外部からダイレクトに遷移することは禁止するべきである。WAFによっては、この遷移制限の機能がある。

一見よさそうだが、このようなアプリケーションの場合、そもそも認証機能が正しく動いている限り、ログインページ以外には外部から遷移できないのであり、もしログインしていないユーザが遷移できたとすればそれは認証機能の重大な脆弱性である。脆弱性があるからWAFを導入するんだろうという突っ込みは予想できるが、こんな重要かつ基本的なところでWAFに頼らないと認証が正しく動かないのは問題であって、もし脆弱性があるならばWAFに頼らずに修正するしかないだろう。

一方、XSSやCSRFなど受動的攻撃への対策として、既にログインしている正規ユーザが外部から強制的に遷移させられるのを防止するという観点であれば、この機能に意味がある


次に、右の図は、ECサイトをモデル化したものである。この場合は、商品を選んでいる最中ではまだログインしておらず、かつ検索サイトなどからの遷移はむしろ歓迎するべきものであるので、WAFにより禁止するわけにはいかない。決済画面など一部のページは外部からの直接遷移を禁止してよい(禁止すべき)であるが、どのページは外部からの遷移を許可し、どのページは禁止するかという、一種のホワイトリストを定義することが煩雑となる。したがって、サイトの作りによっては、この「外部からの直接リンク禁止」機能を使える場合もあるが、多くのサイト(典型的にはECサイト)では、この機能を運用することはかなり労力が必要となる。


内部画面遷移をチェックするのも多大な労力が必要

画面遷移のチェック機能として、Webアプリケーション内部の遷移(狭義の画面遷移)をチェックできるものがある。そのためには、まず正しい画面遷移をホワイトリストとしてWAFに定義する必要がある。私は、コンサルタントとして非常に多くのWebサイトの検査に携わってきたが、画面遷移図がドキュメントとして完備しているサイトがそもそも少なく、タイムリーにアップデートされているサイトは皆無といってよかった。しかも、アップデートされた画面遷移図があったとしても、通常全ての遷移を網羅しているわけではなく、例外的な遷移(エラー時など)までは記述していないと思う。その理由は、例外的な遷移まで画面遷移図に書いていくと、非常に読みにくいものになってしまうからだ。仮に完璧な画面遷移図があっとしても、それをWAFに設定することは大変な労力であるし、Webアプリケーションの保守作業のたびにWAF側の設定をアップデートしないと、更新された機能が動かなくなる。

このような理由から、画面遷移のチェック機能は、現実には使われないケースが多い*3

WAFの機能(3)hiddenフィールド操作の防止

これもすべてのWAFが備えているわけではないが、商用WAFの多くが備えている機能として、いわゆる「hiddenフィード改ざん」の検知とブロックがある。すなわち、hiddenフィールドやCookie、ラジオボタンなどの選択肢の値がクライアント側で改変されてないかチェックし、改変されていたらエラーとするものである。

この機能の実装方式としては、私の知る限り二種類ある。一つは、hiddenフィールドやCookieの値を暗号化して改変できなくする、あるいはハッシュ技術により、改変されたことを検知・ブロックすると言うものだ。この実装方式は、クライアント側のJavaScriptと相性が悪いことが多く、暗号化あるいはハッシュ値が付与された値をJavaScriptで正しく読み込みできなくなる場合がある。

もう一つの実装方式は、WAF側でセッション管理を行い、hiddenの元の値を覚えておき、ブラウザからのリクエストとWAF側で記録した値とを比較するというものだ。一見よさそうな機能だが、ユーザが戻るボタンを操作すると、WAFで覚えている値とhiddenの値の食い違いが発生しそうだ。前述の画面遷移チェックと組み合わせて使うとよいのかもしれないが、設定が大変なためお勧めできない。

また、最近のWebアプリケーションでは、JavaScriptによりHTMLの内容をダイナミックに書き換えることが普通になっているわけだから、hiddenの書き換えをブロックこと自体が、Webアプリケーションの進化の方向とは衝突するものであるように思う。

WAFの三重苦とはなにか

ここで、WAFの三重苦とは何かを説明しよう。

大半の商用WAFがホワイトリスト方式を標榜しているが、そもそもホワイトリストというものは、予め正しい内容を設定しておく必要があり、その元となる正しい仕様書がないからアプリケーション側の作りこみがもれるのだ。その肝心なところを直視しないでWAFに頼ろうとするユーザは、WAF購入後にたちまち現実に直面するし、WAF(最近はワフというらしいが)に対する不満を募らせることになるのだ。WAFが安いものであればまだしも、非常に高価な製品であるだけになおさらだ。というわけで、WAFの三重苦とは、こうだ。

割高で あてにならぬも 負担増

なんだか、はてなハイクのようになってしまったが、高価で、設定に手間が掛る割には、これで万全と言うものではないということだ。

三重苦を乗り越えWAFが普及するための私見

ここで、まとめとしてWAFに関する私見を述べよう。

まず、WAFはホワイトリスト方式であるという主張はやめた方がよい。そんな主張をするから誤解を招くのだ。ホワイトリストの元となる正しい仕様は、仕様書に書いてないといけないし、仕様書に書いてあることはアプリケーションとして実装しなければならないのだ。だとすれば、WAFとアプリケーションでホワイトリスト検査の二度手間をやることになってしまう。

一方、WAFのブラックリスト検査については、私は元々懐疑的だったが、その考えが少し変わってきた。「サニタイズ言うな」に基づく現代的なセキュアWebアプリケーション開発では、アプリケーション側ではブラックリスト検査をしない。だから、WAFとアプリケーションとで検査の重複はなくなるのだ。実運用に影響を与えない程度の控えめなブラックリストをチューニングして、それを全パラメータ共通で使用する。そうすれば、手間も掛らないし、機械化されたSQLインジェクション攻撃程度であれば十分な効力がある。それでも運用に影響が出るパラメタもある(ブログや掲示板など)が、そのパラメタだけはブラックリスト検査を除外して、その代わりしっかり脆弱性検査をして対策もアプリ側でとっておけばよい。

Webアプリケーションの脆弱性対策は、アプリケーションの作りこみで行うべきであるが、アプリケーションが大規模になるほど対策漏れの可能性は大きくなる。上記のようなWAFの運用であれば、アプリ側の対策漏れに対するセーフティネットとして働くことが期待できる。その理由は、WAFの細かい設定をしないからであり、WAFの細かい設定をすればするほど、WAF自体の設定漏れを心配しなければならなくなる。それは本末転倒であって、WAFに対して割り切った使い方をした方がかえって安全ともいえる。

また、WAFの低価格化は必須だ・・・というより、ホワイトリスト機能を使わないのであれば、安いWAFで十分だ。そうすると、割高、負担増の二重苦が消え、残りは「あてにならない」だけが残るが、セキュリティ製品なんてファイアウォール、IPS、ウィルス対策ソフト・・・いずれをとっても「あてになる」ものはないのだから、WAFにだけ完璧を期するわけにもいくまい。ブラックリストでの防御という点ではIPSと機能がかぶるが、ことWebアプリケーションの脆弱性に関しては、ブラックリスト機能だけ使ってもWAFのほうが検知能力が高い。ここに、WAFの存在価値が見出せると私は考える。

最後に控えめな宣伝を。WAFの選定・導入に関する相談はこちらまで

参考:ITproに書いたWAFの評価・比較レポートです WAFでWebアプリの脆弱性を守れるか:ITpro

2009/10/26追記

KCCSにて、10月29日(木曜)、11月26日(木曜)、12月17日(木曜)の3回にわたり、WAFのセミナーを実施することになりましたので、興味のある方は、こちらのリンクから内容確認の上お申し込みください。

*1 ホワイトリストとブラックリストについては、ホワイトリスト方式の優位は神話を参照されたい

*2 アプリケーション要件として禁止されているならホワイトリスト検査が可能だ

*3 これについては、WAFベンダーからも次のような証言がある。

APC Phase 2が最もセキュリティの高いものであることはいうまでもないが、すべてのページ遷移パターンまでもWAFに登録せねばならない。そうなると初期設定に相当の時間がかかり、運用開始後のコンテンツ変更時にも相当の労力と時間を要する。事実、BIG-IP ASMを採用している顧客の中でも、APC Phase 2を利用している顧客は、極めてコンテンツ変更の頻度が少ない数社しか存在しない。
WAFのセキュリティレベルとパラメータ設定より引用

本日のツッコミ(全6件) [ツッコミを入れる]

Before...

_ AzureStone [F-Secure Site Guardは、買収ではなく業務移管するようです。]

_ まっちゃだいふく [誰がWAFのお金を出すか、そこも合わせて考えないといけない時期ですね。 ■新規構築時  開発にて設計上必要と考慮して..]

_ トムヤン [最近、WAF製品の販売をしてまして、このサイト大変勉強になりました。おっしゃられたブラックリスト・ホワイトリストは運..]


2008年07月16日 ホワイトリストとブラックリスト

_ホワイトリスト方式の優位は神話

近々WAF(Web Application Firewall)の話題を取り上げたいと思っている(→WAFの話題はこちら)。WAFの説明には決まってホワイトリストとブラックリストという用語が出てくる。しかし、WAFの宣伝やブログなどのエントリを読んでいると、ホワイトリストやブラックリストという言葉に対する誤解があるように見受けられる。そのため、WAFの話題の前に、この二つの用語の説明をしておきたいと思う。

ごく大雑把に言って、ホワイトリストは「怪しくない人・モノ」を列挙したもの、ブラックリストは「怪しい人・モノ」を列挙したものだ。これらのうち、日常生活でなじみのある用語はブラックリストだろう。クレジットカードの支払いを延滞すると「ブラックリスト」に名前が載り以降しばらくカードが作れなくなるとか、テロ組織のメンバーの名前が書かれた一覧表も「ブラックリスト」と呼ばれる。

最近話題の携帯コンテンツのフィルタリングについても「ホワイトリスト」方式と「ブラックリスト」方式の是非が議論された。この場合は、青少年の閲覧に問題ないサイトの一覧が「ホワイトリスト」、問題が想定されるサイトの一覧が「ブラックリスト」となる。

さて、問題はこの二つの方法論の使い分けだ。冒頭に述べたようにセキュリティ業界ではなにかとホワイトリストの人気が高いようだが、この傾向はWAF分野において顕著だ。以下は、とあるWAFの宣伝文句であるが、ホワイトリスト方式の利点が高らかにうたわれている。

・・・のWAF機能は、ポジティブ・セキュリティと呼ばれるホワイトリスト方式を採用している。これはポリシーによって正しいと定義されたトラフィックのみに、Webアプリケーションへのアクセスを許可する方式。不正アクセスをブラックリスト方式で識別するIDC(不正侵入検知システム)やIDP(不正侵入防御システム)では常にリストを更新する必要があるが、ホワイトリスト方式なら頻繁な更新は不要で、新しい攻撃に対する防御能力も高い。 引用元

一例のみ紹介したが、このような題材を探すには苦労しない。そして、そのような説明の多くが、ブラックリスト=古くて劣ったもの、ホワイトリスト=新しくて優れたもの、という調子だ。引用した文もそうなっているが、ホワイトリスト方式=ポジティブ、ブラックリスト方式=ネガティブという用語も(こう呼ぶ意味はあるのだが)ホワイトリスト方式の優位を印象付ける。

しかし、である。本当にホワイトリストが優れていて、ブラックリストが劣っているのであれば、法律かなにかでブラックリスト方式を禁止し、今後はホワイトリスト方式のみを採用するようにすべてのセキュリティベンダに強制すべきではないのか。現実にはそんなことはできないのであって、例えばウィルス対策ソフトをホワイトリスト方式で実装することは不可能だ。ホワイトリストとブラックリストにはそれぞれ長所と短所があって使い分けをすべきものであり、どちらが優れているとか劣っているというものではないのだ。

画像の説明

右の図はホワイトリストとブラックリストの位置づけを概念的に示したものだ。図のように、ホワイトリスト(WL)は「まず安全と考えられるもの」を列挙したもの、「ブラックリスト(BL)」は「安全でない可能性がかなりあるもの」を列挙したものとなる。そして、ホワイトリストとブラックリストのどちらにも載ってない中間部分が、「白黒はっきりしない中間領域」すなわちグレイゾーンとなる。

この図からもわかるようにWLとBLで示すことのできる領域は全体として一部であり、どうしてもグレイゾーンが大きくなる。すなわち、WLは判断を安全サイドに倒して「安全という保証のないものは全て排除する」もの、BLはカバー範囲を広くとることを重視して「明らかに怪しいもの以外は受け入れる」方法ということになる。この関係を以下に表として示した。

ホワイトリスト方式 ブラックリスト方式
カバー範囲 狭い 広い
安全性 高い 危険なものを受け入れる可能性あり

これだけのことだ。ホワイトリスト方式の方が安全なことは確かだが、世の中全てホワイトリストで回せるはずがない。セキュリティの世界では守るべき対象の性質によってはホワイトリストが使える場合があって、その場合はぜひホワイトリストにしなさいというだけのことである。前述のように、ホワイトリストが使えない場合が現実には大半なので、(問題があるとは分かっていて仕方なく)ブラックリスト方式を使う。それだけのことだ。特に方法論自体の優劣とは関係ない。

ついでのように紹介して恐縮だが、大垣さんの書かれたホワイトリストはどう作る?は、ホワイトリスト神話の悪しき例と言わざるを得ない。

スクリプトインジェクション(XSS)防止にブラックリストが機能しない事は明らかです。ホワイトリストはどう作れば良いか参考となるリンクです。どう作るか書いておいても古くなる可能性が高いので、どこを参考に作れば良いか参考URLを書いておきます。

以下のリンクの情報からスクリプトのインジェクションがどのように行えるかを参考にホワイトリストを作れば概ね間違いないと思います。

Follow up:

XSS Cheat Sheet

http://ha.ckers.org/xss.html

スクリプトインジェクション手法の中でも有名な手法を集めているサイトです。XSSロケータと呼ばれている文字列はスクリプトインジェクション脆弱性検出に重宝します。よくある脆弱性であればこの文字列で簡単に検出できます。

[ホワイトリストはどう作る?より引用]

大垣さん、これではホワイトリストではなくて、ブラックリストそのものです。

一方、興味深いことに、金床さんの書かれたウェブアプリケーションセキュリティには、同じ題材を取り扱っているが、その記述はまるで異なる。

WAFを使用しブラックリスト方式のシグネチャマッチングによってXSS対策を行う場合、攻撃を完全に防ぐことは不可能である。これはXSSを引き起こす可能性のある文字列が非常に多岐にわたるためだ。このことは非常によく知られたドキュメントであるXSS Cheat Sheetを見るとよくわかる。【中略】望ましい対策として、パラメータごとにホワイトリスト式のチェックを行う方法が考えられるが、残念ながら多くのウェブアプリケーションではホワイトリストをきちんと定義することが難しい【中略】従って、WAFを使ってXSS攻撃を完璧に防ぐことは期待できない(P92~P94)。

[ウェブアプリケーションセキュリティより引用]

同書の書評でも述べたが、オープンソースのWAFの開発者としてホワイトリストとブラックリストの両方に真剣に向き合ってきたからこそ書ける、正確かつ誠実な記述である。

金床さんのWAFの話題が出たところで、次回はWAFの説明に続く・・・徳丸浩の日記 - そろそろWAFに関して一言いっとくか - 三重苦を乗り越えてWAFが普及するための条件とは

本日のツッコミ(全1件) [ツッコミを入れる]

_ yohgaki [大垣です。手抜きエントリを見事に誤解されたので追記しました。 http://blog.ohgaki.net/-7 ..]


2008年06月27日 複文が利用できるデータベースの調査(2)

_SQL Serverが狙われるにはまだまだ理由がある

徳丸浩の日記 - 複文が利用できるデータベースの調査 - SQL Serverが狙われるには理由があるにおいて、SQLインジェクションで別のSQL文を注入するためには、複文のサポートが必要で、SQLインジェクションの文脈でこれが可能であるDBMSはMS SQL ServerとPostgreSQLであることを示した。

ここで、複文を記述するには、セミコロン「;」で複数のSQL文を区切ると説明していたが、マイクロソフトの公表している文書によると、SQL Serverにおいては、複文の区切りにセミコロンは必要ないことが分かったので報告する

注意 複数の SQL ステートメントを区切るのに、セミコロンは必ずしも必要ありません。必要かどうかは、ベンダまたはインプリメンテーションによって異なりますが、Microsoft SQL server では不要です。たとえば、以下は SQL Server では 2 つの別々のステートメントとして解析されます。

SELECT * FROM MyTable DELETE FROM MyTable

[How To: ASP.NET で SQL 注入から保護する方法より引用]

なんてこったい。20年近くSQLを使ってきたが、複文の区切りでセミコロンが要らない(場合がある)なんて初めて知った。さっそく試してみた。

rs = st.executeQuery("select * from test update test set num2=7777");

見ての通りJavaで試したのだが、すんなり動いてしまった

同じようにPostgreSQLでも試してみた(こちらはPHP)が、エラーとなった。ベンダー依存ということだが、メジャーなDBでは、セミコロンなしで複文が書ける(プログラム中で)のはMS SQL Serverだけらしい。

こんなことを知っても実用的な意味はほとんどないのだが、私にとっては重大な成果があった。SQLインジェクション対策としてセミコロンを削除するよう説明している解説がある(例えばこれ)が、頻発するSQL Serverに対するSQLインジェクション対策としてのセミコロン削除はまったく意味がないことがはっきりしたことになる。


2008年06月02日 SQLインジェクション対策

_SQLエスケープにおける「\」の取り扱い

昨日のエントリ(徳丸浩の日記 - そろそろSQLエスケープに関して一言いっとくか - SQLのエスケープ再考)は思いがけず多くの方に読んでいただいた。ありがとうございます。その中で高木浩光氏からブクマコメントを頂戴した

\がescape用文字のDBで\のescapeが必須になる理由が明確に書かれてない。\'が与えられたとき'だけescapeすると…。自作escapeは危うい。「安全な…作り方」3版で追加の「3.失敗例」ではDBで用意されたescape機能しか推奨していない

このうち、まず「\」のエスケープが必須となる(MySQLやPostgreSQLで)理由を説明しよう。

「\」をエスケープしないと処理がおかしくなる

MySQLにおいて、文字列「\n」は改行を意味する。その他、\に続く文字によって、様々な制御文字などを表現できるようになっている。

このため、ユーザがたまたま入力欄などから「\n」と入力した際に、エスケープしないままだと、ユーザの意図に反して「\n」が改行に化けることになる。また、エスケープシーケンスとして定義されていない場合、たとえば「\x」は単に「x」を表すと規定されているので、「tokumaru.org大安売り¥100-」が「tokumaru.org大安売り100-」となり、「¥」が欠落してしまう。これはユーザの意図ではない。

このため、「\」を「\\」エスケープすることにより、上記のような文字化けを防ぐ必要がある。これが、そもそも「\」のエスケープが必須となる理由で、セキュリティ上の要求がなくても、必要な処理である。

「\」のエスケープもれによるSQLインジェクション

上記に加えて、「\」のエスケープが必要な状況でそれがもれている場合、SQLインジェクション脆弱性の原因となる。高木氏が指摘しておられるように、「\'」という入力に対して「'」のみエスケープすると、「\''」という文字列になる。前半の「\'」で「'」を表すので、末尾の「'」がエスケープされないで残ってしまう。つまり、文字列リテラルを終端できる。前回指摘したように、これによりSQL断片を埋め込むことが可能となる。この例だと分かりにくいので、もう少し現実的な攻撃パターンで説明しよう。

SELECT * FROM XXX WHERE NN='$id'

$id として \'or 1=1# が入力されると

\'or 1=1#
  ↓ エスケープ
\''or 1=1#

元のSQLに適用すると、

SELECT * FROM XXX WHERE NN='\'' or 1=1#'

すなわち、SQLの構文が改変された

上記でデータベースとしてはMySQLを想定している。「#」はMySQLでコメントを表すので、行末の「#'」は無視される。

Shift_JISでの問題

ここまでならまだよい。データベースの種類によっては「\」のエスケープを忘れないようにしようで済む。ところが、文字「\」を表す文字コード0x5cがShift_JISの2バイト目にも現れうることから話がややこしくなった(一方、「'」を表す0x27の方はShift_JISの二バイト目に現れない)。0x5cを二バイト目に含む文字は多数あるが、例として以下を紹介する。

ソ(835c)
能(945c)
表(955c)
予(975c)

上記のように出現頻度の高い文字が含まれている。言語処理系やデータベースエンジン、APIなどに日本語処理の不完全な部分があると、SQLインジェクションの可能性が出てくる。

データベースエンジンの日本語処理が不完全な場合

この場合は、「表'」のような組み合わせによりSQLインジェクションができる可能性がある。

'
0x95 0x5c 0x27
 ↓フロント側でのエスケープ処理
' '
0x95 0x5c 0x27 0x27
 ↓データベース側の解釈
0x95 0x5c 0x27 0x27
0x95 \' で'一文字 ' がエスケープされずに余る

このように、末尾の「'」がエスケープされない状態となり、SQLインジェクション脆弱性が生まれる。

フロント側の日本語処理が不完全な場合

フロント側(言語処理系)の日本語処理が不完全な場合も「表'」の処理において「'」のエスケープ抜けが発生する

'
0x95 0x5c 0x27
 ↓フロント側でのエスケープ処理(0x5cと0x27をそれぞれエスケープ)
0x95 0x5c 0x5c 0x27 0x27
 ↓データベース側の解釈
0x95 0x5c 0x5c 0x27 0x27
「表」一文字 \'で一文字 ' がエスケープされずに余る

上記はShift_JIS固有の現象であるので、できるだけShift_JIS以外の文字エンコード、例えばUTF-8を使うとよい。しかし、ケータイブラウザのようにShift_JISのみ受け付けるものや、エンタープライズ系の応用では文字化けを避ける目的でShift_JISを要求される場合もある(入出力時に文字コード変換して処理はUnicodeに統一する手もあるが、わずらわしい場合もあるだろう)。

PostgreSQLの対応

PostgreSQLでは上記の問題に対応するために、バージョン8.1.4(2006年5月24日リリース)では、以下のような変更が行われた

  • 常にサーバ側で無効なコードのマルチバイト文字を拒否するように修正された
  • 文字列リテラル中の安全でない「\'」を拒否する機能が追加された
  • 以下略

上記については、ITproへの石井達夫氏による寄稿(【PostgreSQLウォッチ】第27回 SQLインジェクション脆弱性を修正,日本語ユーザーに大きな影響)が詳しい。簡単に要約すると、「\'」形式のエスケープを禁止・エラーにし、「''」方式(ISO標準)に限定できるようにした。これにより、Shift_JISの二バイト目の0x5cにまつわる「'」のエスケープ抜けを防止するというものである。但し、後方互換性の確保のため、backslash_quoteという設定パラメタが用意され、これが on の場合には、従来どおり「\'」形式のエスケープを許容する。

pg_escape_stringの挙動調査

冒頭に紹介した高木浩光氏のブクマコメントの後半は、自作のエスケープではなくDBで用意されたエスケープ機能を利用するようにという指摘であった。まことにその通りで、私のブログを読むと、自作のエスケープを推奨しているようにも読めるが、それは良くない。

それでは、PHPで用意されているpg_escape_stringは期待通り動作するのだろうか。簡単なスクリプトで検証してみた。

検証用スクリプト(PHP)
$cn = pg_connect("host=localhost user=xxxx password=xxx";
echo pg_escape_string($cn, "表\\'");

実行結果1 (standard_conforming_strings = on の場合)
表\''

実行結果2 (standard_conforming_strings = off の場合)
表\\''

PHP言語側のエスケープの都合で紛らわしいが、入力文字列は「表\'」である。前回紹介したstandard_conforming_stringsの設定を正しく反映して、onの場合は「表\''」(「\」のエスケープをしない)、offの場合には「表\\''」(「\」、「'」ともエスケープする)結果となっている。いずれの場合にも「'」は「''」とエスケープされるので、backslash_quoteの設定には依存しない。素晴らしい。

DBD::PgPPの場合

こんどはPerlでの例。PerlからPostgreSQLを利用する場合には、DBIとDBD::Pgの組み合わせが利用される・・・と思うのだが、筆者の環境では中々DBD::Pgがインストールできなかったので、代わりにDBD::PgPPを使って検証してみた。PgPPはピュアPerlで記述されたPostgreSQL用インターフェースである。DBD::PgPP中のquote()のソースを見ると、文字の変換部は以下のようになっていた(バージョン0.05)。

$s =~ s/(?=[\\\'])/\\/g;
return "'$s'";

正規表現の「?=」はゼロ文字の先読み表明というやつで、後ろに「\」か「'」が続くゼロ文字にマッチする。すなわち、「\」と「'」はそれぞれ「\\」と「\'」にエスケープされる。これはいただけない。standard_conforming_stringsもbackslash_quoteも無視されている。

これは恐らくDBD::PgPPの完成度があまり高くないということなのだろう。従って、自作のエスケープをせずにDBで用意されたエスケープ機構を使えというガイドラインは一般論として正しいと思うが、上記のような例もあるので、初めて使う前に簡単なテストをしておけば安心できる。

まとめ

  • 「\」のエスケープを要求するデータベースは日本語処理に特に注意
  • 例えば、Shift_JISを避ける
  • 自作のエスケープを避け、DBにて用意されたものを使う
  • その場合でも過信は禁物で、できるならチェックしてから使うとよい

参考:WASForum Conference 2008講演資料「SQLインジェクション対策再考」



[PR]小規模ECサイトに最適なWAF、SiteGuard Lite

ockeghem(徳丸浩)の日記はこちら
HASHコンサルティング株式会社

最近の記事

最近のツッコミ

  1. Nkzn (05-27)
  2. momo (05-06)
  3. トムヤン (03-19)
Google