この記事ではセキュリティ攻撃の1つであるSQLインジェクションについて、その対策方法も含めて解説をしていきます。
- SQLインジェクション攻撃の概要
- SQLインジェクション攻撃の被害・影響
- SQLインジェクション攻撃の流れ
- SQLインジェクション攻撃の根本的原因
- SQLインジェクション攻撃の対策方法(PHPでの対策コードあり)
SQLインジェクション対策をしないと最悪の場合、DB内のデータをすべて抜き取られたり削除されるなど非常に大きな被害が発生します。
この記事を読んでいただき、理解の一助となれば幸いです。
セキュリティ対策についてしっかり学ばれたい方にはこちらの書籍がおすすめです。
(通称:徳丸本と呼ばれる「Webアプリ開発者必読」とまで言われている書籍です)
体系的に学ぶ 安全なWebアプリケーションの作り方 第2版 脆弱性が生まれる原理と対策の実践
徳丸 浩/著 SBクリエイティブ/出版│Amazon
SQLインジェクションとは?
Webアプリの脆弱性に付け込み、不正なSQL文を注入(インジェクション)することでDB内のデータを盗み出したり削除したりするセキュリティ攻撃のこと
被害・影響
SQLインジェクションの被害や影響は主に以下の通りです。
- 情報漏洩
- 情報の改ざん
- 情報の削除
- 認証回避
- ファイルの読出し・書出しなど
また、漏洩した情報が原因となりクレジットカード被害やWebサイトの改ざんによるマルウェア拡散、アカウントの乗っ取りなどの二次被害へ派生する可能性があります。
攻撃の流れ
SQLインジェクションが実行される場所としては、主に以下の箇所が挙げられます。
- 検索画面
- ログイン画面
Webアプリにおいて、上記の画面の共通点として、「ユーザが入力した任意の文字列を元に、システムがDB内から該当データを取得したりチェックを行う」という処理があります。
このとき、システム上では予め用意されたSQL文が実行されるのですが、攻撃者はそのSQL文に悪意のあるSQL文を無理やり注入(インジェクション)することで、DB内のデータを不正に操作します。
【具体例】検索画面を通じた情報の盗み出し
それではここで、「パソコン関連機器を検索する機能」を例として、SQLインジェクションが実際にどのようにして実行されるのかをお見せします。
以下のSQL文はユーザが検索フォームに入力した内容($item_name)を元に、DBから該当する「機器名(name)」と「価格(price)」を表示するものです。
SELECT name, price FROM item_list WHERE name = $item_name;
攻撃者は、上記の内、$item_nameに不正な入力内容を含めることで、システム側が意図していないSQL文を実行させます。
例えば、攻撃者によって以下のような入力内容があったとします。
'; UPDATE item_list SET price = '0
これにより、システム側で組み立てられるSQL文は以下のようになります。
※分かりやすくするために攻撃者の入力内容を【】で囲んでいます。
SELECT name, price FROM item_list WHERE name = '【'; UPDATE item_list SET price = '0】';
ここで注目して頂きたいのが以下の2か所です。
- WHERE name = ”;
- UPDATE item_list SET price = ‘0’;
1では「’;」が文末に注入されたことで、無理やり正常のSQL文が終了させられています。
2では、UPDATE文が注入されていますが、WHERE文による条件指定が含まれていないため、更新対象はすべてのデータということになります。
つまり、上記のSQL文が実行されることにより、item_listテーブル内の価格情報(price)がすべて0円に変更されてしまうということです。
SQLインジェクションの根本的原因
結論から言うと、SQLインジェクションの根本的原因は
正規のSQL文にユーザの入力内容をそのまま埋め込む仕様になっている
ことにあります。
上記の例をご覧いただいたように、SQLインジェクションでは正規のSQL文からはみ出させる形で不正のSQL文を実行させています。
攻撃者は何らかの工夫を施すことで、この「はみ出す部分」を作り出し、そこに悪意あるSQL文を埋め込み、不正を行うのです。(例ではUPDATE文を「はみ出させる」ことで実行させています。)
これが可能となってしまう原因は、システム側でSQL文を組み立てる際、ユーザの入力内容をそのままSQL文に埋め込んでいるためです。
つまり、ユーザの入力内容をそのまま埋め込まないことがSQLインジェクション対策になるということです。
対策方法
繰り返しになりますが、SQLインジェクションの原因は、ユーザの入力内容をSQL文にそのまま埋め込んでいることにあります。
この状態を回避する方法は複数ありますが、ここでは最も確実となるプレースホルダーを利用する方法をご紹介します。
プレースホルダーとは?
プレースホルダーって何…?
という方のために簡単にご説明をすると
プレースホルダーとは、後から任意の値をセットするために予め用意された仮のスペースのこと
分かりやすく言うと、プレースホルダーを用いることで、後からSQL文の構造を変更することが理論的に不可となります。
つまり、攻撃者による不正なSQL文の実行を防ぐことが可能になるということです。
プレースホルダーの実装【PHPコードあり】
実際の実装例は以下の通りになります。(★が付いている処理が今回の対策と関連している部分です。)
<?php
/*
* 今回はPDOを使用した実装としています。
*/
// ユーザの入力内容を取得
$item_name = $_GET['item_name'];
try {
// DBへ接続の準備
$dsn = 'mysql:dbname=sample_db;host=localhost;charset=utf8';
$user = 'ユーザ名';
$password = 'パスワード';
// オプション指定
$options = array(
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, // SQL実行失敗時には例外をスロー
PDO::MYSQL_ATTR_MULTI_STATEMENTS => false, // ★複文の実行を不可にする
PDO::ATTR_EMULATE_PREPARES => false, // ★静的プレースホルダーを使用する
);
// PDOオブジェクトを生成してDBへ接続
$dbh = new PDO($dsn, $user, $password, $options);
// SQL文の作成
// ★プレースホルダー名として「:item_name」を指定
$sql = 'SELECT name, price FROM item_list WHERE name = :item_name';
// SQLへ接続準備
$stmt = $dbh->prepare($sql);
// ★プレースホルダ(:item_name)に値($item_name)をセット
$stmt->bindValue(':item_name', $item_name, PDO::PARAM_STR);
} catch (Exception $e) {
// 例外処理
}
?>
以下より、★が付いている処理について順番に解説をしていきます。
【解説】複文の実行を不可にする
以下の部分でSQL文の複文の実行を不可としています。
PDO::MYSQL_ATTR_MULTI_STATEMENTS => false, // ★複文の実行を不可にする
「複文」とは、例えば以下のように、同時に複数のSQL文が実行される構成となっているものを指します。
SELECT name, price FROM item_list WHERE name = ''; UPDATE item_list SET price = '0';
前述したように、SQLインジェクションとは正規のSQL文から「はみ出す」形で不正なSQLを差し込む攻撃です。
つまり、このように複文の実行を不可にすることでSQLインジェクションの抑制になるのです。
【解説】静的プレースホルダーを指定
次に静的プレースホルダーについてですが、こちらは以下の部分で指定をしています。
PDO::ATTR_EMULATE_PREPARES => false, // ★静的プレースホルダーを使用する
プレースホルダーには実は
- 静的プレースホルダー
- 動的プレースホルダー
の2種類があります。正しく処理が記述できていればどちらを使用してもSQLインジェクション対策となりますが、静的プレースホルダーの方が原理的に対策が確実なため、仕様的に問題が無ければこちらを採用するのが無難です。
【解説】プレースホルダーの利用
プレースホルダーを実際に使用している箇所がこちらです。
// SQL文の作成
// ★プレースホルダー名として「:item_name」を指定
$sql = 'SELECT name, price FROM item_list WHERE name = :item_name';
~中略~
// ★プレースホルダ(:item_name)に値($item_name)をセット
$stmt->bindValue(':item_name', $item_name, PDO::PARAM_STR);
処理の流れは以下のようになります。
- 「:item_name」という任意のプレースホルダー名を設定
- bindValueメソッドでプレースホルダーに実際の入力内容をセット
また、bindValueメソッドの第3引数には、入力内容に応じた型を指定します。例えば今回は入力内容として文字列型での入力を想定しているため「PARAM_STR」を指定しました。
以上がSQLインジェクションの対策コードの解説でした。
対策内容を整理すると以下のようになります。
- SQLインジェクション対策にはプレースホルダーを使用する
- オプション指定により複文の実行を不可にできる ※完全な対策とはならない
- 静的プレースホルダーなら原理的にSQLインジェクションの実行が不可となる
- プレースホルダーを使用する手順は、①プレースホルダーの設定 → ②プレースホルダーに値をセット(バインド)
まとめ
ここまでSQLインジェクションについてお伝えをしてきました。
SQLインジェクションが成功してしまった場合、情報漏洩や改ざんなどが発生し、2次被害としてアカウントの乗っ取りなどに繋がってしまう恐れがあります。
プレースホルダーを使用することで対策が可能なため、Webアプリを開発する際には必ず実装するようにしましょう。
最後に、僕がセキュリティ対策を学ぶ際に利用した書籍をご紹介させていただきます。
体系的に学ぶ 安全なWebアプリケーションの作り方 第2版 脆弱性が生まれる原理と対策の実践
徳丸 浩/著 SBクリエイティブ/出版│Amazon
こちらの書籍は通称徳丸本と呼ばれており、Webセキュリティの第一人者である徳丸浩さんが手がけられている書籍です。Webアプリを開発する方やWebアプリケーションのセキュリティ対策を徹底的に学びたい方にとてもオススメできる一冊です。
Webアプリケーション開発において、ある意味セキュリティ対策は技術力以上に重要です。
こちらの書籍を読むことでWebアプリケーションにおいて必要なセキュリティ要件も明確となるため、Webアプリ開発に携わる方は必ず目を通しておきたい一冊です。
最後までお読みいただきありがとうございました!
また別の記事でお会いできれば光栄です!