SQLインジェクションの危険性とその対策について


SQLインジェクションとは

Webアプリケーションでは様々な情報をデータベースに保存して管理します。このデータベースを操作するための言語としてSQLが使用されます。
SQLインジェクションは悪意あるユーザがサイトに不正な内容を入力し、データベース上で意図しないSQL文が実行されてしまうことです。
このままではイマイチわかりにくいので次に具体例を紹介します。

SQLインジェクションの具体例


userテーブルの中から名前が'tarou'というレコード取得するとき以下のSQL文を作成します。
SELCT * FROM user WHERE name = 'tarou';

'tarou'の部分はユーザからの入力値とします。
名前の入力欄に『tarou』と入力されたためこのようなSQL文が作成されたわけです。
では悪意あるユーザが名前の入力欄に『' OR 'A'='A'--』と入力したらどうなるでしょうか?

SELCT * FROM user WHERE name = '' OR 'A'='A'--';

当然このようなSQL文が作られます。

WHERE以降の条件をOR演算子で無理やり条件をTRUE(真)にすることで、なんとuserテーブルの全てのレコードを取得できてしまいます!

これはまずいですね。
ちなみに--はコメントアウトで以降の文字はコメントとして扱われます。
最後のシングルクォートが邪魔なのでコメントで消しているというわけです。

SQLインジェクションの対策

ユーザIDとパスワードを受け取ってログイン処理を行うとします。

まずダメな例ですがこのようなSQL文は絶対に書いてはいけません。

// ユーザが入力した値を直接SQL文に組み込んでいる
$sqlStr = "SELECT * FROM user WHERE id = '". $param["id"] ."' AND password = '". $param["password"] ."'";

$stmt = $db->prepare($sqlStr);
$stmt->execute(); // SQLを実行

ユーザが入力した値を文字列連結して直接SQLを組み立てていますが、これだとSQLインジェクションの標的になります。
悪い人がパスワードに『' OR 'A'='A'--』を入力すればログインされてしまいます。
ユーザが入力した値で直接SQLを組み立てることは絶対にしてはなりません。

ではどうしたら良いのかといいますとプレースホルダを使用します。
プレースホルダを使用したSQL文は以下のようなコードです。

// プレースホルダを利用
$sqlStr = "SELECT * FROM user WHERE id = ? AND password = ?";

ユーザの入力値が入る予定の場所に?が入っていることが確認できます。
この?がプレースホルダです。
プレースホルダによるSQL文の組み立てはパラメータ部分をこの?のような記号で示しておき、後からデータベース上で実際の値を機械的な処理で割り当てます。
プレースホルダを使用してSQLを実行するには以下のコードのようにします。

// プレースホルダを利用
$sqlStr = "SELECT * FROM user WHERE id = ? AND password = ?";

$stmt = $db->prepare($sqlStr);
$stmt->execute(array($param["id"], $param["password"])); // 値の配列を渡してSQLを実行

この割り当てる処理を「バインドする」と呼び、プレースホルダのことを「プレースホルダ変数」と呼ぶことがあります。

ちなみにプレースホルダには静的プレースホルダ動的プレースホルダがあります。
それぞれ以下の違いがあります。

  • 静的プレースホルダ(プリペアドステートメント)
    一般的には静的プレースホルダがよく使われ、プリペアドステートメントと呼ばれます。 プレースホルダのままのSQL文をデータベース側にあらかじめ送信して、実行前にSQL文の構造解析などの準備をしておく方式です。
    SQL実行の段階で実際のパラメータの値をデータベースに送信し、データベース側がバインド処理します。
    先にSQL文を確定させることで入力データによってSQL文が変更されません。

  • 動的プレースホルダ
    動的プレースホルダは静的プレースホルダと違い値のバインド処理をウェブアプリケーションのライブラリ内で実装する方式です。

この記事で紹介しているのは静的プレースホルダです。
動的プレースホルダはライブラリの実装に問題があるとSQLインジェクションを許してしまう可能性があるため、データベースが静的プレースホルダをサポートしているのであれば静的プレースホルダを使いましょう!
静的プレースホルダはSQLを準備する段階でSQL文の構文が確定し、後から構文が変化することがないので安全です。

【おまけ】SQLインジェクションの脆弱性を持っているかの判定方法(悪用厳禁)

判定方法としてサイトのSQLに組み込まれるであろう入力欄にシングルクォーテーション『'』を入力して送信します。もし脆弱性があるとデータベースエラーが起こります。

例えば名前の欄に『'』を入力するとこのようなSQL文ができると予想されます。

SELECT * FROM user WHERE id = ''';

SQL文は文字列をシングルクォーテーションで囲むので、もしプリペアドステートメントを使用していない脆弱性のあるサイトだと入力した『'』がSQL文として認識されます。
つまりSQL文として『'』が1つ余分になりSQLの構文が誤りになりデータベースエラーが発生します。
しかし脆弱性がないと文字列として認識されるため『'』という文字を入力しただけとして扱われデータベースエラーは起こりません。

なお、当然ですが間違っても悪用はしないでください。