はじめに
インジェクションはプログラミングではDI(依存性注入)の文脈で使われることが多い単語で、「注入」という意味です。
SQLインジェクション脆弱性で注入されるのは「悪意のあるSQL文」となります。
SQLインジェクション脆弱性によって以下の攻撃が可能になります。
- 不正取得
- 改ざん
- 認証回避
- プログラム実行
- ファイルの参照、更新
与える被害は甚大です。アプリケーション開発ではSQLインジェクション脆弱性を許さないことが必須で求められます。
具体例
よくSQLインジェクションの例として挙げられるのは下のようなログイン画面です。
この画面のコードが以下のようになっているとしましょう。
<html>
(略)
<body>
<h1>Log in</h1>
<form action="login.php" method=POST>
<p>email</p>
<input type="text" name="id"><br>
<p>password</p>
<input type="text" name="password"><br>
<input type="submit" value="Log in">
</body>
</html>
<?php
header('Content-Type: text/html; charset=UTF-8');
$id = @$_POST['ID'];
$pwd = @$_POST['PWD'];
$db = new PDO("mysql:host=127.0.0.1;dbname=sample", "sample", "sample");
$sql = "SELECT * FROM users WHERE id = '$id' AND PWD = '$pwd'";
$ps = $db->query($sql);
?>
<html>
<body>
<?php
if ($ps->rowCount() > 0) {
$_SESSION['id'] = $id;
echo 'ログイン成功です';
} else {
echo 'ログイン失敗です';
}
?>
</body>
</html>
このとき、入力値を以下のようにすると認証が通ってしまいます。
email = test@gmail.com
password = ' OR 'a'='a
これはSQLインジェクションによってSQLが以下のように書き換えられているからです。
SELECT * FROM users WHERE id = 'test@gmail.com' and pwd = '' OR 'a'='a'
対策
問題は文字列リテラルを「'」で抜けて、SQLの構造改変をユーザーに許してしまっていることにあります。
対策は色々なやり方が考えられますが、根本的な対策としては静的プレースホルダを使用して、パラメータをユーザーの入力値から受け取る前にSQL文をコンパイルし、SQL構造を決めておく方法が挙げられます。
この根本的な対策に抜けがあったり、ミドルウェアに脆弱性があり、正常に静的プレースホルダが実行できなかったりした場合の保険的対策として以下の対策もとったほうが良いとされています。
- 詳細なエラーメッセージの抑止
SQLのエラーメッセージから情報が抜き取られることがあります。仮にユーザーによって機密情報が入ったエラーを出力するSQLが実行されたとしても、クライアント側にエラー内容をそのまま返さない実装が求められます。 - 入力値の妥当性検証(validation)
今回の例ではpasswordの文字列の中に「'」を含めることができたことによって、SQLインジェクションが実行されてしまいました。これを英数字に限定しておけば、SQLインジェクションの実行可能性を減らすことができます。 - データベースの権限設定
SQLインジェクションによって、データベースのドキュメントを全件削除処理を走らせるケースも存在します。その際にアプリケーションの機能として与える権限にDELETEが含まれていなければ、その操作は行われません。被害を最小限にする意味で、不要な権限をアプリケーションに与えないようにしましょう。
あくまで、上の3つは保険的対策であって、SQLインジェクションを完全に防げるということではありません。過信しないようにしましょう。
最後に
とりあえず、
SQLインジェクション脆弱性は文字列リテラルを「 ' 」を使って抜けること
と覚えておけばよいのではないでしょうか。(あくまで標語であって、正確ではありません。)
自分の書いたコードでドキュメント全件削除されてしまわないように気をつけて行きたいです。