狙われるWebアプリケーション【最終回】


京セラコミュニケーションシステム
セキュリティ事業部 副事業部長
徳丸 浩(とくまる ひろし)

 前回までは,主にクロスサイト・スクリプティングのぜい弱性とその対策について解説してきた。最終回となる今回は,クロスサイト・スクリプティング以外の「インジェクション系」ぜい弱性について解説する。具体的には,SQLインジェクション,OSコマンド・インジェクション,HTTPヘッダー・インジェクション,そしてメールの第三者中継である。

SQLインジェクション対策にはバインド変数の利用が最適

 まず,SQLインジェクションから見ていこう。対策には二つの方法がある。一つは,SQLの「バインド変数(注1)」を使う方法である。バインド変数の書式はプログラミング言語によって異なるが,一例として,Perlを使った場合に,パスワード認証のSQLをバインド変数で書き換えた例を示す(図1)。

(注1) 「準備された文(Prepared Statement)」というのがJIS SQLでの用語だがあまり普及していない。バインド変数、プレースホルダーなどの呼び方が一般的である。

図1 バインド変数によるSQL記述例
    1:my $sql = ’SELECT COUNT(*) FROM T_USER WHERE ID= ? AND PASSWD = ?’;
    2:my $sth = $db->prepare($sql);
    3:my $rt = $sth->execute($id, $passwd);

 図の1行目がバインド変数を利用した「準備された文」と言われるものである。2カ所に出てくるクエスチョンマーク「?」はプレースホルダーと呼ばれるもので,SQLの「変数」に相当する。3行目のexecuteでこれら変数の値を指定する。バインド変数を利用すると,SQLの式が確定したものになるため変更することができないし,シングルクォート「'」も必要ない。このため,原理的にSQLインジェクションの危険性はなくなる。

 これ以外にも,バインド変数の利用には以下のようなメリットがある。特別な事情がない限り,SQL呼び出しにはバインド変数を利用するべきである。

  • SQL文のコンパイル結果のキャッシュが有効利用され,実行が高速になる
  • 文字列式によりSQL組み立てを行う必要がないため,プログラムが読みやすく保守しやすくなる

    バインド変数が利用できない場合は,メタ文字のエスケープにより対策する

     SQLインジェクション対策のもう一つの方法は,クロスサイト・スクリプティング攻撃への対策でも解説したメタ文字のエスケープ処理である。インジェクション系脆弱性対策の一般的な手法であり,SQLインジェクションにも適用できる。SQLの場合,エスケープの必要なメタ文字はシングルクォート「'」(「''」あるいは「\'」と変換)および「\」(「\\」と変換)である。その際には,以下の原則に従う。
  • SQLを構成するすべての変数や式をエスケープ処理する
  • SQLを組み立てる直前でエスケープ処理する

     これらの方法により,SQLインジェクション対策を確実に行うことができる。いわゆる「セカンドオーダーSQLインジェクション(注2)」についても,すべての変数や式をエスケープ処理するという原則で素直に対応できる。

    (注2)セカンドオーダーSQLインジェクションとは、入力文字列による直接の攻撃ではなく、いったんDBやファイルに格納された文字列を別のSQLにパラメータとして利用する際のインジェクション攻撃をいう。

     ただ,データベース・エンジンによって特別な意味を持つ記号が異なる。また,以下に述べる注意点があるので,可能であれば先に紹介したバインド変数による対策を推奨する。

    • 変数に型のない言語で,数値データを扱う場合
    •  注意点の一つは,シングルクオートを使わずにSQLインジェクションを実現できる場合があることだ。SQLのデータ型には文字型のほか,数値型などがある。数値の場合,SQL式中の定数はクォートなどで囲まない。これを悪用するのである。

       実装例を挙げて説明しよう。以下のSQL は,掲示板の投稿文書テーブルから,投稿ID(整数型)を指定して文書を削除するものである。言語は,PerlあるいはPHPを想定している。

      $sql = "DELETE FROM DOCUMENTS WHERE ID = $id";
      変数$idをシングルクォートで囲っていないことが分かるだろう。ここで,PerlやPHPでは変数に型がないために,$idに以下のような文字列を入力することも可能である。
      $id = '0 or TRUE'; # 実際には入力欄に「0 or TRUE」を入力
      こうすると,組み立てられるSQLは以下のようになり,すべての投稿を削除できることになる。
      DELETE FROM DOCUMENTS WHERE ID = 0 or TRUE

       数値型を想定した変数に文字列を入力することが前提だから,攻撃が成り立つのは,プログラミング言語としてPerlやPHPのように変数に型がないものを使っている場合に限られる。ただ,PerlやPHPはWebアプリケーション開発に広く利用されているので決して軽視はできない。

       原因が数値を想定した変数に文字列を入力することにあることから,対策は入力値の妥当性検証である。SQLインジェクション対策以前の問題として,アプリケーション要件を満足する意味でも妥当性検証は必要である。

       このケースは色々と悩ましいところがあって,セキュリティ業界でも定見がないように見受ける。ネット上には,数値型の場合でもシングルクォートで囲ってしまうことを推奨するような解説を見かけるが,筆者は賛同できない。SQL実行のたびに文字型から数値型への暗黙の型変換が行われるのは,性能的にも不利だし,プログラミング書法という点でも望ましくないと考えるからである。

       また,前述したようにインジェクション系脆弱性対策は「出力時」に実施することが望ましいが,数値項目の検証を出力時に行うとプログラミングが煩雑になる可能性がある。このような煩わしさを避けるためにも,SQLインジェクション対策にはバインド変数の利用が理想的である。PerlであれPHPであれバインド変数を利用できるので,新規開発のタイミングでバインド変数に移行することをお勧めする。

    • Webアプリケーション実行環境の日本語対応が不十分な場合
    •  さらに,アプリケーション実行環境にてシフトJISを用いる場合には別の注意が必要である。「\」(0x5C)はシフトJISコードの2バイト目になり得る。Webアプリケーション実行環境の日本語処理が不十分な場合は,エスケープ処理が誤動作する場合がある。

       具体的には,シフトJISによる「ソ」(0x835C),「噂」(0x895C),「予」(0x975C)などは,2バイト目に0x5Cが含まれるので,誤動作を起こす可能性がある。ただし,これらはアプリケーションのSQLインジェクションのぜい弱性というより,言語やデータベース・エンジンの日本語化不備の問題なので,推奨する対策は下記の二つである。

    • 日本語処理が完全なツールを選択する
    • シフトJISをできるだけ避ける(EUC,または可能であればUNICODE)

    OSコマンド・インジェクション

     次にOSコマンド・インジェクションについて見てみよう。OSコマンド・インジェクションとは,外部からのパラメータ改変などにより,Webサーバー上でOSコマンドを不正に実行することを言う。元のアプリケーションが外部コマンドを呼び出すようになっていて,その呼び出し方を悪用される場合があるほか,プログラマが意識しないところで利用している外部コマンドを呼び出せる関数を悪用される場合がある。

     後者の例として有名なものが,Perlのopen関数である。この関数は,ファイル名の指定時にパイプ「|」を指定して,外部コマンドとのインタフェースに利用できる。このため,アプリケーション開発者が単にファイルをオープンするためにopen関数を利用している場合でも,それを悪用して外部コマンドを実行できるケースがある。

     OSコマンド・インジェクションのぜい弱性を持つプログラム例を示そう。図2はPerlで記述された掲示板CGIの一部であり,投稿IDで指定された書き込みを表示するCGIの骨子を示した。

    図2 掲示板のCGI骨子
    #!perl
    use encoding 'euc-jp';
    use CGI;
    my $query = CGI->new();
    my $id = $query->param('ID');

    print <<END1;
    Content-Type: text/html; charset=EUC-JP

    <HTML><HEAD><TITLE>掲示板</TITLE></HEAD>
    <body>
    <PRE>
    END1

    open FL, "../data/$id";
    while (<FL>) {
     print $query->escapeHTML($_); # HTMLエンコードして表示
    }
    close FL;
    print <<END2;
    </PRE>
    </body>
    </html>
    END2

    このCGIのopen関数利用部分にOSコマンド・インジェクションのぜい弱性がある。パラメータIDに以下のような文字列を指定すると外部コマンド(この場合はnet.exe)が起動される。

    ID=../../../../windows/system32/net+start|

     このコマンドを実行すると,図3のようにWebサーバー上で稼働しているサービスの一覧が表示される。このような情報は外部からの攻撃に役立つ。さらに,任意のOSコマンドが実行できるのだから,ファイルの改ざん,漏えい,ユーザー・アカウントの追加,ウイルス類の感染,他サーバーに対する攻撃の踏み台作成など,あらゆる攻撃が可能になる。対策は次のようになる。

    図3 OSコマンド・インジェクションによるサービス一覧表示

    • 外部コマンド実行機能のない関数を利用する
    •  サンプルCGIでは,open関数をファイル・オープンのためだけに利用しており,コマンド実行機能は利用していない。こういう場合は,Perlのsysopenという関数を利用することができる。sysopenには外部コマンド実行の機能がないため,コマンド・インジェクションのぜい弱性が混入する危険性はない。(注3)

      (注3)元のCGIにはディレクトリ・トラバーサルのぜい弱性もあり,こちらはなくならない。

    • 外部コマンドを起動できる関数を利用する際は,ホワイトリスト方式によるパラメータ・チェックを行う
    •  これまで述べたように,インジェクション系ぜい弱性への対策の原則は,特殊文字のエスケープ処理である。しかしOSコマンド・インジェクションの場合,エスケープ処理は大変複雑になり,エスケープ漏れなどのミスが混入する可能性が高い。このため,外部コマンドを起動できる関数を利用する場合は,ホワイトリスト方式によるパラメータ・チェックを行うのがよいだろう。さらに言えば,ホワイトリストによる検証は,外部コマンド実行の直前に行うことが望ましい。検証が確実に行われていることを確認しやすいからだ。

       先ほどの掲示板の例で言えば,書き込みIDは英数字のみで構成する仕様としておき,これら以外の文字があればエラーとする。これは,簡単かつ安全にチェックできる方法である。一般に,外部コマンドに指定するパラメータは英数字のみに限定するのが安全であり,多くの場合それは実施可能である。

       英数字以外の文字を使わなければならない場合は,中間ファイル経由として,ファイル名のみをわたしてやる仕組みにすればよい。ファイル経由というとオーバーヘッドを気にする人もいるだろうが,外部コマンドを起動するオーバーヘッドに比べればはかるに小さいので問題にはならない。

       ただし,OSコマンド・インジェクション対策以前の問題として,可能であれば外部コマンド実行そのものを避けることが望ましい。PerlのCGIなどでは,Perl自体の機能でできることでも,外部コマンド実行により実装するケースがよくある。これは効率面でもセキュリティ面でも好ましくない。

    HTTPヘッダー・インジェクション

     HTTPヘッダー・インジェクションとは,サーバーからブラウザに送信される「HTTPレスポンス・ヘッダー」の中に意図しないフィールドを注入する手法である。Webアプリケーションの中には,ユーザーの入力値などをレスポンス・ヘッダーに「そのまま」返すものがある。その代表例が,Cookieの設定(Set-Cookie)やリダイレクション(Location)である。ここではまずHTTPヘッダー・インジェクションの典型例として,リダイレクションを行うWebアプリケーションを使って説明しよう。

     図4は,「url」というパラメータ(GETまたはPOST)としてURLを受け取り,そのURLにリダイレクトするだけの簡単なCGI(リダイレクタと呼ばれる)である。このような簡単なアプリケーションにもぜい弱性がある。

    図4 リダイレクタの実装例
    #!perl
    use CGI;
    my $cgi = new CGI;
    my $url = $cgi->param('url');

    print "Location: $url\n\n";

    ここで使用例として,url=http://www.kccs.co.jp/というパラメータによりこのCGIを起動してみよう。結果は図5のようになる。

    図5 リダイレクタの実行結果

    ご覧のように,指定したURLにリダイレクトされている。次に,指定するパラメータを以下のように変更する。

    url=http://www.kccs.co.jp/%0D%0ASet-Cookie:SID=12345

    この場合,CGIにて発行されるHTTPレスポンス・ヘッダーは以下のようになる。

    Location: http://www.kccs.co.jp/
    Set-Cookie:SID=12345

    すなわち,意図しないCookieが発行される。実行結果は図6の通り,確かにCookieが発行されている様子が分かるだろう。

    図6 リダイレクタのぜい弱性によるCookie設定

     このぜい弱性は「セッションIDの固定化(Session Fixation)」などに悪用することができる。同様にして,外部からのパラメータなどをそのままCookieとして設定している場合に,意図しない別のCookieの設定や,他サイトへのリダイレクトが行える場合がある。

    「改行」のエスケープ処理あるいは検査で対策

     HTTPヘッダー・インジェクションのぜい弱性の根本的な原因は,HTTPにおいて特別な意味を持つ改行コードへの処理がなされていないところにある。そこで,インジェクション系ぜい弱性対策の原則にしたがって,改行コードのエスケープ処理を検討してみよう。

     CookieについてはURLエンコード(一種のエスケープ処理)することになっているので,改行も含めてエスケープが可能である。一方,LocationフィールドのURLは改行するわけにはいかないので,改行コードのチェックを行い,あればエラーにする。

     こうしたチェックやエスケープは,HTTPやCGIといったWebサーバーの実行環境の内部仕様に依存した内容であり,本来はアプリケーション開発者の責任とすべきものではない。あるべき姿としては,Web実行環境側でCookie設定やリダイレクトなどのAPIを用意し,その中でこれらの「細かい」処理を行うべきである。しかしながら,これらAPIの機能が不十分である場合や,機能は存在してもぜい弱性がある可能性もある。このような場合に備えて,以下のような方針で実装するのがよいであろう。

    • HTTPレスポンス・ヘッダーは,できるだけアプリケーションから直接送出せず,高機能のAPIやライブラリを利用する
    • APIの利用有無にかかわらず,Cookie値やURLなどの改行コード・チェックを行い,改行があればエラーとする
    • CookieをAPIにて発行する場合は,URLエンコードされていることを確認する。アプリケーションから直接Set-Cookieレスポンス・ヘッダーを送出する場合は,Cookie値のURLエンコードを実施する

     これらの実装においては,リダイレクトやCookie設定などのライブラリを作っておくとよいだろう。ライブラリ内部でAPIを利用するにせよ,レスポンス・ヘッダーを書き出すにせよ,改行コード・チェックやURLエンコードが保証されるようにする。これにより,入力値検証の有無にかかわりなく,「出力段階で」対策を確実に行うことが可能となる。

    メールの第三者中継

     HTTPヘッダー・インジェクションと非常によく似たぜい弱性パターンとして,「メールの第三者中継(注4)」がある。これは,HTTPレスポンス・ヘッダーではなく,メール・ヘッダーにおいて改行によるメール改変が可能となるものである。

    (注4)CrLfインジェクションなどと呼ばれる場合もあるが、「安全なウェブサイトの作り方改定第2版」の表記に従った。

     Perl言語では,メール送信には伝統的にsendmailコマンドを呼び出す実装が多く用いられる。sendmailコマンドを利用したメール送信の骨子を図7に示す。

    図7 sendmailを利用したメール送信CGI
    # sendmail コマンドの起動
    open MAIL, "| /usr/sbin/sendmail -t -i";
    # メール・ヘッダー
    print MAIL "From: $from\n";
    print MAIL "To: $to\n";
    print MAIL "Cc: $cc\n";
    print MAIL "Subject: $subject\n";
    # その他のメール・ヘッダー出力
    print MAIL "\n"; # 空行=ヘッダーの終了
    # メール本文
    print MAIL $body;
    #
    close MAIL;

     このサンプルは,OSコマンド・インジェクションのパートで説明した「open関数によるコマンド起動」のサンプルにもなっている。sendmailコマンドの前の縦棒(パイプ)は,このファイルに出力した内容がそのままsendmailコマンドの標準入力となることを意味している。この場合,sendmailコマンドの入力は図8のようになる(注5)

    (注5)実際のメールでは、さらに多くのヘッダーが入る。

    図8 sendmailコマンドの入力イメージ
    From: xxxxxxxx@kccs.co.jp
    To: yyyyyyyy@nikkeibp.co.jp
    Cc: zzzzzzzz@kccs.co.jp
    Subject: 寄稿の件

    貴社ますますご清栄のこととお慶び申し上げます。
    日ごろは・・・

     ここで,Fromアドレスを入力できるフォームを想定して,この欄に,図9のような内容を指定した場合を考えてみよう。

    図9 Fromアドレスの操作例
    From: xxxxxxxx@kccs.co.jp
    To: xxxx@hogehoge.co.jp
    Subject: パスワード確認のお願い

    ○△ポータルでは○月○日にシステムメンテナンスを実施しましたので,お手数ですが,以下のURLからログインいただき,動作確認をお願いいたします。

     ブラウザのテキスト入力フィールドからは改行を含む上記のような内容は入力できないが,ツールを使えば簡単に入力できる。上記の例では,本来Fromアドレスのみを指定できるフォームにおいて,送信先やタイトル,本文の内容までも変更できたことになる。

     このような手法により,任意の内容の迷惑メールを任意アドレスに送信できる。また,バイナリ・ファイルを添付することも可能なので,ウイルスやスパイウエアの送信に利用できる場合もある。メール自体は「本物」のメール・サーバーから送られてくるので,相手先で迷惑メール対策などがなされていても,チェックをくぐり抜けて受信される可能性が高い。

    対策は改行文字のチェックで

     SMTPにおいては,改行コードのエスケープ処理が可能である。その方法は,改行直後の文字をスペースあるいは水平タブとすることである。この行は,直前の行に継続しているとみなされる(継続行)。

     ただ,万が一改行コードがすり抜けた場合のインパクトが大きいこと,メール送信のAPIが継続行をサポートするとは限らないこと,Webアプリケーションを想定した場合,本来テキスト入力フィールドには改行は入らないはずであることを考慮すると,メール・ヘッダーの値に改行が混入することは異常事態であって,アプリケーションとしてはエラーにするのが正しい処理である。上記の考察から,メール送信については以下のガイドラインに従うのがよいであろう。

    • メール送信にはできるだけsendmailコマンドを使用せず,高機能のAPIやライブラリを利用する
    • APIの利用有無にかかわらず,メール・ヘッダーの内容(「To」「Cc」「Bcc」「Subject」など)の改行コード・チェックを行い,あればエラーとして処理を中断する
    • 上記チェックは,メール送信用の関数ライブラリを作成して,その中で行う
    ■□■ まとめ ■□■

     インジェクション系ぜい弱性について,4週にわたって原因と対策を解説してきた。SQLインジェクションなど,○○インジェクションという名前のついているものもあるが,クロスサイト・スクリプティングやメールの第三者中継など,名称に「インジェクション」とつかないものもある。いずれの場合でも,これらのぜい弱性はテキスト形式インタフェースにおけるエスケープ漏れが原因で発生することを示した。

     対策は,個別に異なるものもあるが,以下のような原則としてまとめられる。

    • 高機能APIやライブラリを活用することで出力時の対策を確実に行う
    • テキスト・インタフェースごとの適切なエスケープ処理を行う
    • エスケープ処理が難しい「改行」については,出力時のチェックによりエラーにする
    • 保険的対策として入力値検証をアプリケーション要件にしたがって行う
     インジェクション系ぜい弱性は,アプリケーションの脆弱性としてはもっともよく知られているにもかかわらず対策が進んでいない。本稿を参考に,みなさんの開発現場での対策を見直していただければ幸いである。