ユニークなSQLインジェクション対策
SQLインジェクションのブログ記事を読んでいて、対策として以下のスクリプトが紹介されていました。
現在は当該のスクリプトは削除されているので、当該エントリは参照せず、「昔々あるところにこのようなSQLインジェクション脆弱性対処スクリプトがあった」という想定で以下の議論を進めます。$_GET[id]=strip_tags(htmlspecialchars($_GET[id])); $_GET[id]=preg_replace(array(‘/[~;\'\"]/’,'/–/’),”,$_GET[id]);
問題点の説明
このスクリプトはいくつかの問題点があります。- $_GETを直接変更していて汎用性が乏しい
- idをクォートしていないなどいくつかの文法エラー
- 表示前提でないのにHTMLエスケープしている
- HTMLエスケープした後strip_tagsを呼んでいるが、この段階ではタグはエスケープ済みなので、strip_tagsは何もしない
- 「危険な文字」を削除するサニタイジング手法を用いている
この関数に、さまざまな文字を通した結果を下表に紹介します。function sanitize($str) { $str = strip_tags(htmlspecialchars($str)); return preg_replace(array('/[~;\'\"]/', '/-/'), '', $str); }
入力文字 | htmlspecialchars | strip_tags | preg_replace(最終) |
---|---|---|---|
A | A | A | A |
5 | 5 | 5 | 5 |
< | < | < | < |
> | > | > | > |
& | & | & | & |
" | " | " | " |
' | ' | ' | (空) |
\ | \ | \ | \ |
; | ; | ; | (空) |
- | - | - | (空) |
~ | ~ | ~ | (空) |
# | # | # | # |
HTMLエスケープの対象となる < > & " の4文字は、文字実体参照に変換された後、preg_replace関数でセミコロンを削除してしまうので、中途半端な妙な文字化けになりそうです。
一般的な原則としては、データベースにはHTMLの形ではなくプレーンテキストの形で保存しておき、HTMLとして表示する直前にHTMLエスケープする方法で統一することで、上記のような文字化けやエスケープ漏れをなくすことがよいでしょう。
脆弱性はないのか
このsanitize関数に脆弱性はないでしょうか。上表のように、バックスラッシュ(円記号)を素通ししているので、MySQLや、設定によってはPostgreSQLの場合に、問題が生じそうです。以下、それを説明します。以下の説明では、MySQLを使う想定とします。以下のように、ログイン処理を想定したSQL文組立があったとします。
$sql = sprintf("SELECT * FROM users WHERE id='%s' AND pwd='%s'", sanitize($id), sanitize($pwd));ここで、$idと$pwdが以下だったとします。
$sqlは下記となります。$id = '\\'; // \ 1文字 $pwd = 'password';
SELECT * FROM users WHERE id='\' AND pwd='password'ここで、id=の後の文字列リテラルはどこまででしょうか。\'は「シングルクォートを\でエスケープしたもの」、すなわちリテラル内の文字と解釈されるので、その次のシングルクォートまでが文字列リテラルになります。つまり、 AND pwd=まで(黄色でマークした部分)が文字列リテラルと解釈されます。その後の password' は「文字列リテラルの外」、すなわちSQL文の構文と解釈されますが、SQL文として正しくないので、このSQL文は構文エラーになります。
それでは、passwordのところをSQL文として正しくなるように変更したらどうでしょうか。これがSQLインジェクション攻撃です。その例を以下に示します。
組み立てられたSQL文は以下となります。$id = '\\'; // \ 1文字 $pwd = ' OR 1=1#';
SELECT * FROM users WHERE id='\' AND pwd=' OR 1=1#'passwordの代わりに、OR 1=1(青くマークした部分)が正しいSQL文(の一部)となります。#以降はMySQLのコメントとして無視されます。すなわち、これで認証回避が可能となりました。
「自己流の脆弱性対策」の危険性
以上に説明したように、htmlspecialchars、strip_tags、preg_replaceの三種類の関数を使った「独自の」でSQLインジェクション対策には抜けがあり、SQLインジェクション攻撃を許してしまう可能性があることがわかりました。SQLインジェクションを含めて、脆弱性対処の方法論は、世界中の専門家がさまざまな観点から研究しています。SQLインジェクション対策に関しては、数年前から「究極形」がわかっており、特別な事情のある場合を除いて、わざわざ別の方法を用いる必要はないでしょう。特別な場合とは以下の様なケースです。
- データベースアクセスを含むフレームワークを新たに開発する場合
- O/Rマッパーを自作する場合
- phpMyAdminのようなデータベース管理ツールを自作する場合
- 文字列連結(sprintf等を含む)を用いてSQL文を組み立てない
- 静的プレースホルダを用いてSQL文にパラメータを割り当てる
- データベース接続時に文字エンコーディングを指定する
また、SQLインジェクション対策の解説をこれから書こうとする人には、ぜひajiyoshiさんの素晴らしいブログ記事「Webアプリケーションとかの入門本みたいのを書く人への心からのお願い。」をお読み下さい。このような文章が、現場の開発者から出てくることは本当に素晴らしいことです。
こまかいtypoですが。。
返信削除「自己流の脆弱性対策」の危険性 の2行目あたり
s/わかりしまた/わかりました/
ご指摘ありがとうございます。修正しました。
返信削除PostgreSQLの場合に…と前振りがあるのに「MySQLのコメントとして無視されます」になっています。
返信削除コメントありがとうございます。MySQLの場合と、PostgreSQLの場合に問題が生じる「可能性」がありますが、実証はMySQLについて行ったという意味です。PostgreSQLの場合、#がコメントの開始とみなされず、--から始まる文字列あるいは /* で始まり */ で終わる形式のみ許されるので、攻撃は難しいかもしれませんが、絶対大丈夫とも言えないと考えます。
返信削除