Edited at

第20章 SQL インジェクション

More than 3 years have passed since last update.

クエリ構文が改変されルウうううう!!


目的 : 動的なSQLを記述する

<?php

$sql = "SELECT * FROM Bugs WHERE bug_id = $bug_id";
$stmt = $pdo->query($sql);

この時に$bug_idが整数などの意図通りの値なら良いが、

意図していないコードになりえる場合、

場合によってはインジェクションの攻撃対象となる。


アンチパターン : 未検証の入力をコードとして実行する

動的に挿入された文字列がクエリの構文を改変してしまう可能性がある。

ex)

先ほどの$bug_idの中身が1234; DELETE FROM Bugs;だった場合

クエリは以下のようになる

SELECT * FROM Bugs WHERE bug_id = 1234; DELETE FROM Bugs;

こういうのはアカン・・・・

以下具体例の列挙


アクシデントが起きる

<?php

$project_name = $_REQUEST["name"];
$sql = "SELECT * FROM Projects WHERE project_name = '$project_name'";

もし$project_name = "O'Hare"などの値が入ってくると以下のSQLが発行される

SELECT * FROM Projects WHERE project_name = 'O'Hare';`

syntax err発生

これくらいならまだかわいい方


ウェブ最大のセキュリティ脅威

先ほどの例に様な無意味な文字列ではなく、

新たな別のSQLが発行されると脅威は飛躍的に大きくなる

<?php

$password = $_REQUEST["passeord"];
$user_id = $_REQUEST["user_id"];
$sql = "UPDATE Accounts SET password_hash = SHA2('$password', 256) WHERE account_id = $user_id";

ここで

$user_id = 132675ではなく

$user_id = "132675 OR TRUE"が入ってしまうと以下のSQLが発行される

UPDATE Accounts SET password_hash = SHA2('hogehoge', 256) WHERE account_id = 132675 OR TRUE;

アカン・・・


対処法の追求


値のエスケープ

\でエスケープはできるが・・・

SELECT * FROM Projects WHERE project_name = 'O\'Hare';`                                                                                                                                                                        

エスケープすべき文字を見つけて自動的にエスケープかましてくれる関数があると思うけど

アプリケーション依存だし付け焼き刃臭がすごい

もっといい方法があるはず!!


プリペアドステートメント

単純に?でリテラル値を置換する

本書曰く 最も協力な防衛手段

以下の制約があるので致命的なインジェクションは起きづらい分柔軟性に欠ける


  • 値のリストを一つのパラメタにすることが出来ない

$stmt = $pdo->prepare("SELECT * FROM Bugs WHERE bug_id IN (?)");

$stmt->bindValue(1,"1234, 2345, 4536");


  • テーブル識別子もパラメタとして扱えない

$stmt = $pdo->prepare("SELECT * FROM ? WHERE bug_id = 1234");


  • 列名も扱えない

$stmt = $pdo->prepare("SELECT * FROM Bugs ORDER BY ?");


  • SQL予約語もパラメタにならない

$stmt = $pdo->prepare("SELECT * FROM Bugs ORDER BY device ?");


ストアドプロシージャ

CREATE PROCEDURE UpdatePassword(input_password VARCHAR(20), input_userid VARCHAR(20))

BEGIN
SET @sql = CONCAT('UPDATE Accounts
SET password_hash = SHA2('
, QUOTE(input_password),',256)
WHERE account_id = '
, input_userid);
PREPARE stmt FROM @sql;
EXECUTE stmt;

なんだかんだ動的SQLを扱っており、これもアプリ側の実装次第で脆弱性が決まる


データアクセスフレームワーク

ORM的な

「コレ使っていればインジェクションされない」なんていう迷信を信じている人がいるらしいが、それは違うよという話。

※ インジェクションを回避できるようなバリデーションを含んでいるものもあるけどね!


アンチパターンの見つけ方


SQLインジェクション脆弱性は極めて一般的であるため、SQLインジェクション専門のコードレビューを実施して

・・・略・・・

ほぼ全てのアプリケーションが多かれ少なかれ、このアンチパターンに該当する可能性があると想定すべき。


今や動的なSQL発行が一般的すぎるのでもはやなにもかもがアンチパターンになり得る可能性がある


アンチパターンを用いても良い場合 : ありません


このアンチパターンの性質は、本書の他のアンチパターンとは異なります。

SQLインジェクションのようなセキュリティ脆弱性を正当化する理由はありません。



解決策 : 誰も信用してはならない


SQLコードの安全性を保証する唯一絶対な方法などというものは存在しません。

以下の技法全てを習得し、状況に応じて使い分けるようにしましょう。



入力のフィルタリング

クエリ発行前にユーザー入力から不要な値を取り除く

もはやSQLどうこうの話ではない


  • 正規表現

  • validation


動的値のパラメータ化

プリペアドステートメントを使う

SQLのステートメントを解析した後に?の置換を行うので致命的なインジェクションは基本的には避けられる


動的値を引用符で囲む

こんなシチュエーションだと有効

mysql > SELECT * FROM test;

+--+----+
| i| col|
+--+----+
| 1| red|
+--+----+
| 2| red|
+--+----+
| 3| red|
+--+----+
| 4|blue|
+--+----+
| 5| red|
+--+----+
| 6|blue|
+--+----+
| 7| red|
+--+----+
| 8| red|
+--+----+

この時colにindexが張られていると

redで検索する時とblueで検索する時とでは大分差がある

この時

$stmt1 = $pdo->prepare("SELECT * FROM Bugs WHERE col = (?)");

$stmt2 = $pdo->prepare("SELECT * FROM Bugs WHERE col = '$color'");

だとstmt2はステートメント解析の時点でオプティマイザーが有効なインデックスを判断できるので効果的だったりする


ユーザーの入力をコードから隔離する


他の開発者にレビューしてもらう