SQLインジェクション対策はおすみですか?
開発開始時点からのコンサルティングから、公開済みWebサイトの脆弱性検査、
脆弱性発見後の適切な対策まで
トップ 追記
過去の日記

2009-03-28

IPAは脆弱性の呼び方を統一して欲しい

昨日の日記で、安全なウェブサイトの作り方セキュア・プログラミング講座とでは使っている用語が異なることを指摘した。
違いを理解いただくために、簡単に一覧表にまとめてみた。

安全なウェブサイトの作り方 セキュア・プログラミング講座
SQL インジェクション SQL注入
OS コマンド・インジェクション コマンド注入攻撃
パス名パラメータの未チェック/ディレクトリ・トラバーサル ディレクトリトラバーサル攻撃
セッションIDの固定化 セッションIDお膳立て
クロスサイト・スクリプティング スクリプト注入(XSS)
CSRF(クロスサイト・リクエスト・フォージェリ) リクエスト強要(CSRF)
HTTPレスポンス分割とキャッシュ汚染 HTTPレスポンスによるキャッシュ偽造
メールの第三者中継 メールの第三者中継
アクセス制御や認可制御の欠落 ユーザ認証(本人認証)とアクセス認可

黄色に塗ったところが用語が異なる部分だ。安全なウェブサイトの作り方が概ね世間で使用されている用語に従っているのに対して、セキュア・プログラミング講座の方は独自用語を用いる傾向にある。私見としては、この種の用語はむやみに発明せずに、一般に使用されている用語に従うのがよいと考えているが、せめてIPAで発刊するコンテンツの中では用語を統一していだきたい。技術的に正確な議論をするには、まず正しい用語を使うことが出発点であるし、IPAには正しい用語の統一に関して、指導的な立場を期待されていると考える。

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

とおりすがり [IPAが公募で丸投げしているからなんじゃないでしょうか? 作ったところが違うから用語も違うと。 IPAが用語辞書..]

徳丸 浩 [とおりすがりさん、コメントありがとうございます。 確かに、この旧版はセントラルコンピュータサービスが委託されていま..]


2009-03-27

IPAの新版「セキュア・プログラミング講座」がイマイチだ

2002年3月にIPAから公開されたセキュア・プログラミング講座は、その後2007年に新版が公開された。旧版は今から7年前のコンテンツがベースになっているので現在の目から見ると色々突っ込みどころがあるが、2002年という時期にこれだけのコンテンツを揃えたというのは立派な仕事だったと考えている。

一方、2007年の新版はどうか。部分的に見れば「開発工程と脆弱性対策」の関連について言及するなど意欲的な内容もあるが、全体としては物足りない。新版が出た当時も「例えば、PHPを避ける」という表現が話題になったくらいで、同じIPAから公開されている「安全なウェブサイトの作り方」に比べれば、あまり影響力がないように思える。

しかし、「安全なウェブサイトの作り方」の第一版が2006年1月に出ているのであるから、その後に「セキュア・プログラミング講座」の新版を公開するからには、「安全なウェブサイトの作り方」の内容を包含し、その詳細版という位置づけであって欲しいと思うのだが、そのような内容にはなっていないのだ。大は、脆弱性に対する考え方から、小は、脆弱性の呼び方に至るまで、まったく異なるコンテンツになっている。これでは、両方のコンテンツを利用するユーザは混乱するだろう。

SQLインジェクション対策はどうか

これ以降は、「セキュア・プログラミング講座」の中から、現在緊急の課題であるSQLインジェクション対策の内容を吟味したい。その項立ては以下のようになっている。


(1) 入力値のチェック
(2) 特殊記号のエスケープ
(3) プリペアドステートメントの使用

これらはいずれもSQLインジェクション対策として機能するものだが、この(1)と(2)と(3)の関係は、ANDすなわち「全部やれ」なのだろうか、ORすなわち「いずれかをやれ」なのだろうか。これらは並列に並んでいるだけなので分からない。

一方、安全なウェブサイトの作り方の方は、以下のようになっている。

■ 根本的解決
1)-1 SQL 文の組み立てにバインド機構を使用する
【中略】
1)-2 バインド機構を利用できない場合は、SQL 文を構成する全ての変数に対しエスケープ処理を行う
解説 これは、根本的解決 1) のバインド機構を利用した実装ができない場合に実施すべき実装です。

これなら、バインド機構とエスケープ処理が「どちらか一方」であること、バインド機構を優先して検討すべきことがよく分かる。

実は旧版のセキュア・プログラミング講座の内容を読むと、先の謎が解ける。旧版の方では以下のようになっているのだ

入力値チェックを徹底しよう

任意のSQL文を混入されないためには,入力値チェックを徹底する必要がある。たとえば,

【中略】

しかし人名などの漢字文字コードを扱う場合,本節で紹介した手法では正しい形式かどうかを判断するのは難しい。次節では正しい形式かどうかの判断が困難な入力値を扱う場合について触れる。

入力文字列はエスケープしよう

人名など任意文字を許可する入力文字列を扱う場合,これが任意のSQL文として機能しないようにエスケープする必要がある。

【中略】

以上,SQL文の組み立てにおいて,エスケープ処理の必要性およびその手法について説明した。実はもっと手軽で便利なバインドメカニズムがある。次節ではこれについて説明する。

バインドメカニズムを活用しよう

[SQL組み立て時の引数チェックより引用]

これなら一応分かる。すなわち、第一選択肢としては「入力値チェック」だが「漢字文字コード」の場合はエスケープ、そしてエスケープの簡易便利版として「バインドメカニズム」がある、ということだろう。「安全なウェブサイトの作り方」の方には入力値チェックは対策として示されていないので、両者の推奨内容および優先順位が異なることにはなるが、意図は一応伝わる。

しかるに、新版の方は意図すら伝わらないことから、「駄目な技術文書の見分け方」という意味でよくない。これでは新版は改悪ではないのか。

というわけで、「セキュア・プログラミング講座」に関してIPAにお願いしたいのは以下の3点だ。

  • 技術文書として読んで分かるようにして欲しい
  • 「安全なウェブサイトの作り方」と方法論を統一して欲しい
  • せめて用語だけでも統一して欲しい

続く

JETエンジンにおいてパイプ記号「|」は今でも「危険」なのか

SQLインジェクション対策の続きで、セキュア・プログラミング講座には、2種類の文字を不受理にせよと書いてある。「;」(セミコロン)と「|」(パイプ記号)だ。

このうち、セミコロンについては不受理にする必要はなく、はっきり間違いといってよいだろう。『3)セミコロン「;」の拒否』の項ではセミコロンを用いた攻撃例も出ているが、脆弱性の原因はセミコロンではなく、シングルクォートをエスケープしていないところにある。バインド機構を用いるか、シングルクォートをエスケープすれば、セミコロンを恐れる必要は全くない。それに、複文を用いた攻撃をもっとも受けやすいMS SQL Serverの場合は、セミコロンなしでも複文を記述できることは前述した。すなわち、原理的にも、現実的にもセミコロンの拒否は意味がない。

一方パイプ記号の方はどうだろうか。少し長いが該当箇所を引用する。

5) その他の特殊記号への対処(Microsoft Jetエンジン)

またMicrosoftのJetエンジンでは、次の文字も機能をもつ特殊記号として扱われる。

  |  VBAステートメント実行文字

Jetエンジンは,与えられたSQL文の文字列の中に「|...|」で囲まれた部分があると、それをVBA(アプリケーション用のVisual Basicサブセット言語)のステートメントとして解釈し実行する。
 これは、SQL 文の中の「'...'」で囲まれた文字列の中に書かれていても起こる。
 これを悪用すると外部からの任意のシステムコマンドの投入が可能となり、最悪の場合、システムが乗っ取られるおそれがある。

このパイプ記号「|」をエスケープする方法は提供されていないため、パイプ記号「|」が含まれている入力パラメータは受理しないようにする必要がある。

  | → 受理しない

Jetエンジンは、Microsoft Accessのデータベースエンジンであるが、直接Access を利用しているつもりがなくても、拡張子「.MDB」をもつデータベースファイルにアクセスする際に使われることになるので注意が必要である。

[SQL組み立て時の引数チェックより引用]

この内容は他の文書類ではあまり見かけない。また、私はACCESSおよびJetエンジンの実務での開発経験がなく、また脆弱性診断などでもお目に掛からないので、今までこの問題を検証しないできたのだが、念のため確認してみた。

この問題に言及している文書としては、2000年2月に塩月誠人氏が公開された「セキュリティ勧告 - NTサーバ上におけるJetセキュリティ問題」がある。

Windows NT上で稼動する、MS Accessデータベース(mdbファイル)にアクセスするようなサーバプログラムは、Jetセキュリティ問題(いわゆる Office ODBCドライバ問題)の影響を受ける可能性がある。このセキュリティ問題の影響を受けるサーバプログラムに対し、悪意を持ったユーザが不正な入力を行なうことにより、サーバマシン上で任意のコマンドが起動する危険性が生じる。

[セキュリティ勧告 - NTサーバ上におけるJetセキュリティ問題より引用]

脆弱なサンプルと検証用文字列は以下のようになっている。

脆弱なサンプル:
  set db=Server.CreateObject("ADODB.Connection")
  db.Open "btcustmr"
  sql="select * from Customers where City='" & word & "'"
  set rs=db.Execute(sql)

検証用文字列:
  |shell("cmd /c echo aaa > c:\test.txt")|   (注: "|" は縦棒文字)

手元のWindows Server 2003を用いて、上記を検証してみたが再現しなかった。パイプ記号は特に不都合なく挿入も検索もできる。塩月氏のレポートから9年も経っているので、Microsoft社が対策したのだろうか。試しにAccess 2003を当該Windows Serverに導入してみたが、現象は変わらなかった(TechNet Plus サブスクリプションを使用)。また、Access 2000 および Access 2002 で、安全でない関数が実行されないように Jet 4.0 を構成する方法などを参考にレジストリをいじったりしたが、やはり現象は再現しない。

このように、現在の環境では「Jetセキュリティ問題」を再現するのは難しいようだが、過去このような問題があったことは確実なので、現在においても「いかなる環境でも絶対に安全」とは断言できない。このような状況下でセキュリティコンサルタントとしてどのようにアドバイスすべきだろうか。

私がJetエンジンを今まで無視してきた理由は、同時に多数が利用するWebアプリケーション構築にJetのようなファイル共有タイプのデータベースエンジンを使用するのは好ましくないと考えるからだ。その方向で技術資料を探してみると、ぴったりそのままの文書が見つかった。

しかし Access ODBC ドライバ、および OLE DB Provider for Jet は、Web システムなどの多くのユーザーからのアクセスによる同時実行や高負荷の対応はされておらず、また、終日稼動で運用されるような高い信頼性を要求されるサーバー アプリケーションで使用されることを考慮して設計されていません。そのため、この様なシステムの場合、弊社では IIS/ASP と共に Microsoft SQL Server、または Microsoft Desktop Engine (以下 MSDE) 等のセキュリティ、常時運用の可用性・信頼性、および拡張性を備えたデータベースの使用を推奨しています。

[IIS 上での Jet データベース エンジンの使用について より引用]

上記のようにMicrosoft自身がWebシステムでのJetの使用を推奨していないのだ。そして、以下の内容が続く。

しかし、Access ODBC ドライバはスレッド セーフではないため、複数のユーザーが同時に MDB ファイル (Access データベース) に要求を行うと、予期せぬ動作が引き起こされる場合があり、システムの安定性に影響を及ぼす可能性があります。そのため、安定性、パフォーマンス、およびスレッド プーリングに対する修正および機能強化を含んでいる OLE DB Provider for Jet の使用を弊社では推奨しています。

注意 : OLE DB Provider for Jet はスレッド セーフですが、Jet がスレッドセーフでないため、完全なマルチ スレッド環境を実装することはできません。そのため、OLE DB Provider for Jet を使用した場合でも、応答がなくなるなど予期せぬ動作が引き起こされる可能性があります。

[IIS 上での Jet データベース エンジンの使用について より引用]

色々書いてあるが、「Jet がスレッドセーフでない」という箇所が重要だ。このことから、Jetを使用したWebアプリケーションでは「別人問題」のように、他ユーザの情報が漏洩する可能性などが考えられる。

まとめよう。「Jetセキュリティ問題」は過去の問題とは言い切れないかもしれないが、少なくとも現在では非常にトリビア的な問題だ。しかも、JetをWebシステムでは使うべきでないとMicrosoft自身が明記しているのだ。であれば、セキュア・プログラミング講座で説明すべきことは、「パイプ記号を受理しない」ではなく、こうだろう。

  例えば、Jetエンジンを避ける

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

2009年6月16日追記

本エントリを書いた後、セキュアプログラミング講座の内容は大きく改訂されたようで、ここで指摘した問題はすべて解消されている。関係者の皆様、ありがとうございました。


2009-03-11 文字コードのセキュリティ問題はどう対策すべきか

U+00A5を用いたXSSの可能性

前回の日記では、昨年のBlack Hat Japanにおける長谷川陽介氏の講演に「趣味と実益の文字コード攻撃(講演資料)」に刺激される形で、Unicodeの円記号U+00A5によるSQLインジェクションの可能性について指摘した。

はせがわ氏の元資料ではパストラバーサルの可能性を指摘しておられるので、残る脆弱性パターンとしてクロスサイト・スクリプティング(XSS)の可能性があるかどうかがずっと気になっていた。独自の調査により、XSS攻撃の起点となる「<」や「"」、「'」などについて「多対一の変換」がされる文字を探してきたが、現実的なWebアプリケーションで出現しそうな組み合わせは見つけられていない。

一方、U+00A5が処理系によっては0x5C「\」に変換されることに起因してXSSが発生する可能性はある。JavaScriptがからむ場合がそれだ。しかし、実際にXSS脆弱性が発生するには、次のような状況を想定する必要がある。

  • 入力(HTTPリクエスト)はUnicodeで受け取る
  • 内部の処理もUnicodeで行われる
  • 出力(HTTPレスポンス)のエンコーディングはShift_JISあるいはEUC-JP

すなわち、入力(リクエスト)と出力(レスポンス)で異なる文字エンコーディングを想定しなければならない。これ自体は現実性が薄い。

この問題については、既に佐名木氏らの研究がある。佐名木氏は、Apache Tomcatに着目して以下のように記述している。

実験のポイントは、Tomcat の仕様変更である。Tomcat4 系からTomcat5 系へのバージョンアップによって、クエリー文字列は常にUTF-8 として受け取るように仕様が変更された。

この仕様変更に着目することで、ANSI の世界でデータ処理、そしてデータ出力を行っているJavaServlet に対してUTF-8 の世界の文字を与えることができる。

[Unicodeとサニタイジング回避テクニック ver1.6より引用]

Tomcatのクエリ文字列の文字化け問題はFAQであって、現実には「常にUTF-8として」受け取られるわけではなく、server.xmlの設定により、useBodyEncodingForURI="true" (クエリ文字列の文字エンコーディングをPOSTのエンコーディングと一致させる)を指定することができる。従って、佐名木氏の指摘しておられる状況もなくはないだろうが、もう少し現実的に *ありそうな* 可能性を検討したい。

そこで私は、HTTPリクエストの文字エンコーディングを「自動認識」させている場合に注目して調査を行った。各処理系に対する考察を以下に述べる。

Javaの場合

Javaは前述のように、U+00A5が\x5Cに変換されるので有力な候補だが、J2SEの文字エンコーディング自動判定機能は、JIS系の文字エンコーディングの範囲で行われる(JISAutoDetect)ため、上記の条件を満たすことができない。文字エンコーディングの自動判定を自作することは可能だが、検討の対象からは外すことにした。

PHPの場合

PHPは文字エンコーディングの自動判定が柔軟だが、一方、U+00A5が全角の円記号「¥」に変換されるため、XSSには利用できない。

Perlの場合

PerlにはJcode.pmやEncode.pmに文字エンコーディングの自動判定機能がある。しかし、UnicodeからShift_JISなどへの変換に際してU+00A5が「?」に変換されるため、やはりXSSには利用できない…と思っていた。最近までは。

しかし、私がITproに連載している連載中の記事「第6回■異なる文字集合への変換がぜい弱性につながる 」に対して、id:nihenさんからブックマークコメントを頂戴した。

【Perl(Encode.pm).(略).では発生しない】cp932では発生するです。http://cpansearch.perl.org/src/DANKOGAI/Encode-2.31/ucm/cp932.ucm

すなわち、UTF-8からShift_JISへの変換だとU+00A5は「?」に変換されるが、cp932(Windowsの機種依存文字を考慮したShift_JIS)への変換の場合は「\」(\x5C)に変換されるというのだ。確認したところ、たしかにそうなる。これで、U+00A5によるXSSの可能性が出てきた。さっそく試してみよう。

以下にサンプルコードを示す。

#!/usr/bin/perl
use CGI;
use utf8;
use Encode;
use Encode::Guess qw/utf8 shiftjis euc-jp/;

my $query = CGI->new;
my $p = decode 'Guess', $query->param('p');
# 制御文字のチェック…省略
# 次の行はJavaScript文字列リテラルのエスケープ
$p =~ s/(?=[\\\'\"])/\\/g;     # \ → \\  ' → \' " → \"
$p = $query->escapeHTML($p);   # HTMLエスケープ

print encode 'cp932', <<EOT;
Content-Type: text/html; charset=Shift_JIS

<html>
<body onload="alert('$p');">
テスト
</body></html>
EOT

この簡単なスクリプトは、クエリストリングpの値をalertダイアログに表示するだけの簡単なものだ。処理の流れは以下のようになる。

  • 文字エンコーディングの自動判定候補として、UTF-8、Shift_JIS、EUC-JPを指定
  • クエリストリングpを読み込み、文字エンコーディング自動判定でUTF-8に変換
  • JavaScript文字列リテラルとしてのエスケープ
  • HTMLエスケープ
  • 文字エンコーディングcp932を指定してHTML生成
  • body要素のonloadイベントハンドラにalert関数を生成

JavaScriptの動的生成に対して必要なエスケープ処理については、過去にXSS対策:JavaScriptのエスケープ(その2)などで説明した通りである。

私はそもそもJavaScriptの動的生成を推奨していないが、イベントハンドラにJavaScriptを置く場合は比較的シンプルに考えられる。上記のように、JavaScriptとしてのエスケープとHTMLのエスケープを2段階で行えばよい。面倒ではあるが、SCRIPT要素に置く場合のようにデータの途中に</SCRIPT>が出てくるような特殊ケースは考えなくて良い。

このスクリプトに対して、以下のような入力を与える(U+00A5は赤色全角の円記号で記述)。

');alert(document.cookie);//

この文字列は以下のように処理される。まずJavaScriptのエスケープ処理(' → \')。

\');alert(document.cookie);//

次にHTMLエスケープ(' → &#39;)

\&#39;);alert(document.cookie);//

そしてcp932に変換( → \)

a\\&#39;);alert(document.cookie);//
この文字列はJavaScriptの実行に際して、HTMLデコードされ以下のようになる。
alert('a\'');alert(document.cookie);//');"

すなわち、JavaScriptの文字列リテラルが突破され、第二のalertが追加された。XSSの成功である。

対策

この問題の根本原因は、UTF-8→cp932(Shift_JIS)の変換に伴う多対一変換にある。従って、文字エンコーディングを全てUTF-8に統一することで根本対策となる。しかし、一般的には、携帯電話や電子メールなど、JIS系文字集合を使わざるを得ない場合もあり、ブラウザとのやりとりはShift_JIS(あるいはEUC-JP)、プログラム内部ではUTF-16やUTF-8というケースは多いだろう。

このような場合は、プログラムの実行環境はUnicodeだが、処理対象となる文字集合をJIS系文字集合(マイクロソフト標準キャラクタセットなど)に限定することで対策が可能だ。具体的には、入力時の文字エンコーディング自動判定をやめ、エンコーディングを明示することだ。だが、もっとよい方法があるかもしれない。

一つの可能性として、長谷川氏の講演資料に指摘されているような「検査後には変換しない」すなわち、変換してから検査(エスケープ)する方法もある。しかし、PerlはShift_JISの文字列処理には対応していないので、cp932(Shift_JIS)に変換してからのエスケープも容易ではない。

また、「もっとよい方法」とは、従来言われてきた「過剰エスケープ」を指すわけではない。上記の例で言えば、スラッシュ「/」を「\/」にエスケープすれば、JavaScriptのコメント「//」が有効でなくなり、JavaScriptの実行エラーになるので攻撃は成立しない。しかし、そのような対策はアドホックで、理論的な裏付けに乏しいものだ。

また、今回は取り扱わないが、文字エンコーディングを利用した攻撃についても同様のことが言える。PerlのEncode::decodeは文字エンコーディングのチェックを行うので、そもそも不正な文字エンコーディングについては除去してくれる。エラーにしたければ(そうするべきだが)、オプションの第3パラメータCHECKにEncode::FB_CROAKを指定すればよい。それでも残る問題は処理系のバグ(脆弱性)なのだ。処理系のバグに対してアプリケーション側で対策しなければならない場合もあるだろうが、それは原因の所在を明確にした上での話だ。

文字コードのセキュリティ問題に関しては、公開されている情報が非常に少ない。とくに文字集合をアプリケーション開発の際にどのように取り組むべきかという議論はほとんどなされていないように思う。私は、ITproの連載の中で私見を述べているが、先行する研究がほとんどないので原理から手探りで検討している状態だ。この問題に対する活発な議論が行われることを期待する。


2008-12-22

JavaとMySQLの組み合わせでUnicodeのU+00A5を用いたSQLインジェクションの可能性

今年のBlack Hat Japanには、はせがわようすけ氏が「趣味と実益の文字コード攻撃」と題して講演され話題となった。その講演資料が公開されているので、私は講演は聞き逃したが、資料は興味深く拝見した。その講演資料のP20以降には、「多対一の変換」と題して、UnicodeのU+00A5(通貨記号としての¥)が、他の文字コードに変換される際にバックスラッシュ「\」(日本語環境では通貨記号)の0x5Cに変換されることから、パストラバーサルが発生する例が紹介されている。

しかし、バックスラッシュと言えばSQLインジェクションの可能性も見逃すことができない。そこで、本資料をきっかけとして、U+00A5を使ったSQLインジェクションの可能性について調査し、Java(JDBC)とMySQLの組み合わせにおいて、発生する場合があることを確認したので報告する。

U+00A5を用いたSQLインジェクションとは

ここで、U+00A5を用いたSQLインジェクションとはどのようなものかを説明しよう。UnicodeのU+00A5はバックスラッシュとは独立に扱える日本円の通貨記号として割り当てられている。この文字をShift_JISやEUC-JPなどに変換する際に、ASCIIの0x5Cに変換される(場合がある)。すると、バックスラッシュをSQLのエスケープに使用するデータベース、具体的にはMySQLとPostgreSQLにおいて、SQLインジェクションが発生する場合がある

具体例を用いて説明しよう。検査パターンとして以下の文字列を使用する。以下、U+00A5を表記する場合には赤色全角の通貨記号「」を用いる

'OR 1=1#

先頭の文字がU+00A5である。これをMySQLのルールでエスケープすると、シングルクォートが「\'」と変換され、以下のようになる。

\'OR 1=1#

ややこしいが、最初の通貨記号がU+00A5、二番目の通貨記号が0x5Cである。これをShift_JISあるいはEUC-JPに変換すると以下のように、二文字とも0x5Cになる。

\\'OR 1=1#

これをSQLとして解釈すると、最初の「\\」が「\」をエスケープしたものと見なされ、「'」はエスケープされない状態となる。すなわち、SQLインジェクションされたことになる。

どのような場合に問題になるか

このタイプのSQLインジェクションが発生するのは、以下のようなケースが典型的な場合であろう。

  • 外部とのインターフェースにUnicode(典型的にはUTF-8)を用いていて、U+00A5を入力することができる
  • アプリケーションの内部でもUnicode(UCS-2、UTF-16、UTF-8など)を用いている
  • SQLのエスケープはUnicodeの状態で実行している
  • アプリケーションからデータベースのクエリ実行までのどこかで、Unicode以外の文字コード(典型的には、Shift_JISかEUC-JP)に変換されている

内部コードとしてUnicodeを用いる言語は現在では数多いが、筆者はJavaとPerl(use utf8;)を用いて検証した。その結果、JavaとMySQLの組み合わせの場合にSQLインジェクションが発生する場合があることを確認した

検証コードの説明

以下のような検証コードを用いてテストした。

import java.sql.*;
public class MyA5Injection {
  public static void main(String[] args) {
    try {
      String charEncoding = "sjis";    // or "utf8"
      Class.forName("com.mysql.jdbc.Driver");
      Connection con = DriverManager.getConnection(
        "jdbc:mysql://localhost/tokumaru?user=xxx&password=xxxx&useUnicode=true&characterEncoding=" + charEncoding);
      Statement stmt = con.createStatement();

      String param = "\u00a5'or 1=1#";

      // MySQL用のエスケープ
      String e_param = param.replaceAll("\\\\", "\\\\\\\\");    // \ → \\
      e_param = e_param.replaceAll("'", "\\\\'");               // ' → \'

      String sql = "SELECT * FROM test WHERE name='" + e_param + "'";
      System.out.println("sql = " + sql);
      ResultSet rs = stmt.executeQuery(sql);
      while(rs.next()){
        int id = rs.getInt("id");
        String name = rs.getString("name");
        System.err.println(id + " " + name);
      }
      stmt.close();
      con.close();
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}

実行結果は以下の通り

C:\HOME\Java>java MyA5Injection
sql = SELECT * FROM test WHERE name='\\'or 1=1#'
〜 検索結果の表示 〜

テスト結果

U+00A5を用いたSQLインジェクションは、JDBCのgetConnectionメソッドに指定するオプションパラメータcharacterEncodingに依存するようだ。このパラメタがUTF-8の場合はSQLインジェクションは発生しない。一方、Shift_JISやEUC-JPの場合はSQLインジェクションが発生する。create tableのdefault charset設定には依存しないようだ。これらを下表にまとめた。

UTF-8のテーブル Shift_JISのテーブル
characterEncoding=utf8 正常処理 エラー(*1)
characterEncoding=sjis SQLインジェクション SQLインジェクション
検証に用いた環境
MySQL 5.0 および 5.1
MySQL Connector/J 5.1.7
JDK6 Update11
Windows XP Professional 

(*1) java.sql.SQLException: Illegal mix of collations (sjis_japanese_ci,IMPLICIT) and (utf8_general_ci,COERCIBLE) for operation '='

現実的に脆弱となる組み合わせはどの程度使用されているか

現実にSQLインジェクションが発生するは、JavaとMySQLの組み合わせすべではなく、characterEncodingの指定が明示的あるいは暗黙にutf8以外の値になっている場合と考えられる。筆者が試した範囲では、MySQLのコンフィグレーション・ウィザードで「Best Support for Multilingualism MySQL」を指定した場合にはUTF-8が利用されるが、それ以外の場合はlatin1、あるいはユーザが指定した文字エンコーディング(Shift_JISなど)が設定される。また、GoogleでgetConnectionを検索すると、characterEncoding=sjisと記述した例が多数ヒットしている。そのような状況では、characterEncodingとしてUTF-8以外が指定されている比率は割合に多いのではないかと予想する。

その他の言語とDBの組み合わせの場合はどうか

筆者が他の組み合わせで試した範囲では、Java+PostgreSQLやPerl+MySQLではSQLインジェクションにはならなかった。Java+PostgreSQLの場合はエラーになり、Perlの場合はU+00A5が「?」に変換されるようで、やはりSQLインジェクションにはならなかった。しかし、筆者が試したものと別の条件ではSQLインジェクションが発生する可能性はゼロではない。

対策

はせがわようすけ氏の講演資料には以下のような対策が推奨されている

  • Unicodeのまま文字列を扱い、変換しない
  • (変換するとしても)検査後には変換しない

SQLインジェクション対策としても「変換しない」というガイドラインは有効である。すなわち、以下を推奨する。

  • characterEncoding=utf8を明示する(必須)
  • create tableの際のdefault charsetにもutf8を設定する(推奨)

追記(2008/12/22 14:00)

金床氏から「例のコードがPreparedStatementじゃないのは何故だろう」という指摘を受けた。原理を示すためにはエスケープの方が分かりやすいと思ったからだが、PreparedStatementでも試してみた。主なコードの変更点は以下の通り(エスケープ処理は必要なくなる)。

      String sql = "SELECT * FROM test where name=?";
      PreparedStatement stmt = con.prepareStatement(sql);
      stmt.setString(1, param);
      ResultSet rs = stmt.executeQuery();

結果は、エスケープの時とまったく同じであった。MySQL 5.1でも直っていない…というか、これは仕様かもしれない。やはり、文字エンコーディングはアプリからDBまでそろえよう。

追記(2008/12/24 00:00)

へぼへぼCTO日記さんからトラックバックを頂戴した。Connector/JでサーバーサイドのpreparedStatementを使用するには、オプションuseServerPrepStmts=trueを指定しなければならないとのこと。手元の環境でテストしたところ、同オプションを指定したところU+00A5によるSQLインジェクションは再現しなくなった。ご指摘ありがとうございます。


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であれば、mysql_xxxx系の関数でバインド機構が利用できる。さらに大切なこととして、「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件) [ツッコミを入れる]

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


SQLインジェクション対策はおすみですか?
開発開始時点からのコンサルティングから、公開済みWebサイトの脆弱性検査、
脆弱性発見後の適切な対策まで
トップ 追記