SQLインジェクションとは?

SQLインジェクション(SQL injection / SQL注入)とは、パラメータを埋め込むSQL文を組み立てる時に、そのパラメータに特殊記号を含ませたSQLコマンドを与えることで、想定外のSQLが実行されてしまう問題です。

例えば、Webサイトへのログイン時にユーザーからIDとパスワードを受け取ります。この手続きを例にSQLインジェクションのサンプルをいくつか提示します。

SELECT uid FROM account WHERE uid='{$userId}' AND pw='{$password}';

非常に単純なSQL文ですが、ログイン画面よりユーザーIDとパスワードを受け取り、それを元にSQL文を組み立てて実行します。テーブル(account)を検索し、該当するレコードが存在すればそのユーザーIDを取得し、ログイン成功とします。

ここで、ユーザーIDを「hostingstock」、パスワードを 「' OR 'A'='A」 とすると以下の様なSQL文となります。

ユーザーID : hostingstock
パスワード : ' OR 'A'='A

SELECT uid FROM account WHERE uid='hostingstock' AND pw='' OR 'A'='A';

このSQL文が実行されると OR 以降がTRUE('A'='A')となり、パスワードがなくてもユーザーID「hostingstock」としてログインすることが可能となります。

他にも、セミコロンによる複文による攻撃も可能です。

ユーザーID : hostingstock
パスワード : '; DELETE FROM account WHERE 'A'='A

SELECT uid FROM account WHERE uid='hostingstock' AND pw=''; DELETE FROM account 'A'='A';

このようにすると、SELECT文は何も起こりませんが、DELETE文でテーブルaccountのレコードが全て削除されてしまいます。

これらは単純な例ですが、ユーザーが入力したデータの正当性を確認せずに利用すると、Webサイトに甚大な損害が発生することが分かります。

データベースを利用したWebサイトを運営していて、外部からの入力に応じたSQL操作が発生する機能があれば、この問題に対応しておく必要があります。

専門的な話となるので詳細は省きますが、PDO(PHP Data Objects)なら以下の様に対応することができます。

$userId  = $_GET['userId'];
$password = $_GET['password'];

$pdo = new PDO(/*引数*/);

$sql = "SELECT uid FROM account WHERE uid=:userId AND pw=:password";
$stmt = $pdo->prepare($sql);

$prepare->bindValue(":userId", $userId, PDO::PARAM_STR);
$prepare->bindValue(":password", $password, PDO::PARAM_STR);

$prepare->execute();

bindValueで与えられたデータは数値定数や文字列定数として扱われます。そのため、シングルクォートなど、SQL文に影響を与えそうな文字列(記号)であっても、ただのパラメータとして認識されます。また、パラメータが渡される前にコンパイルされるので、SQLインジェクションによってSQL文を改変されることがなくなります。

これは基本的な対処法なので、重要なデータを管理するのであればより高度な対処法も知っておく必要があります。