はじめに
Webセキュリティについて学習をするため、OWASP TOP10 2017でアプリケーションリスク 第1位となったインジェクションの一つSQLインジェクションについて改めて概要、脅威、原因、対策についてまとめます。
SQLインジェクションとは
SQLインジェクションとはアプリケーションの不備をついて、想定していないSQL文が実行されることにより、データベースを不正に操作される攻撃手法です。
情報漏えい、データ改ざん、サービスの継続不可など影響度の大きさから危険度が特に高く、緊急で対応が必要な脆弱性です。
SQLインジェクションの脅威を知る
環境
以下の環境で確認しました
- PHP 7.3.8
- MySQL 5.7.22
ログイン機能のサンプル
以下は簡単なユーザーログイン機能のサンプルです。データベースの users テーブルでログインIDとパスワードを管理。PHPのログインフォームから送信された該当のログインIDとパスワードが usersテーブル内にあれば、正しいログイン情報としてログイン処理を進めます。
CREATE TABLE `users` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`login_id` tinytext,
`password` tinytext,
`created` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
mysql> select * from users;
+----+----------+----------+---------------------+---------------------+
| id | login_id | password | created | updated |
+----+----------+----------+---------------------+---------------------+
| 1 | taro | password | 2019-08-13 13:43:31 | 2019-08-13 13:43:31 |
+----+----------+----------+---------------------+---------------------+
1 row in set (0.00 sec)
<?php
// $dsn, $username, $passwordは宣言済みとする
$db = new PDO($dsn, $username, $password);
session_start();
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
$login_id = $_POST['login_id'];
$password = $_POST['password'];
$sql = "SELECT * FROM users WHERE login_id = '{$login_id}' and password = '{$password}';";
$prepare = $db->prepare($sql);
$prepare->execute();
$row = $prepare->fetch(PDO::FETCH_ASSOC);
if ($row) {
$_SESSION['login_id'] = $row['login_id'];
}
}
?>
<html>
<body>
<?php if ($_SESSION['login_id']): ?>
<p>ログイン中: <?= $_SESSION['login_id']?></p>
<?php endif; ?>
<form method="post" action="login.php">
<input type="text" name="login_id">
<input type="password" name="password">
<input type="submit" value="送信">
</form>
</body>
</html>
上記のコードをブラウザで表示すると以下のログインフォーム画面が表示されます。このログインフォームにログインID「taro」、パスワード「password」を入力するとログインに成功する簡単なサンプルです。
しかし、サンプルコードにはSQLインジェクションの脆弱性があるため、プログラムの不備を突いた攻撃をすると不正ログインやデータの改ざんが行えます。
注意事項
- 絶対に本番サーバー、稼働サービスでは実行しないでください。必ず安全なローカル環境などでお試しください
- 本検証はあくまでSQLインジェクションの脅威を知ってもらうための検証です
login_id
に以下の値を与えると
' or 1 = 1 ; --
以下のクエリーが生成されます。この場合、パスワード部はコメントアウトに無視され、かつ 1 = 1
により正となり不正ログインが成り立ちます。
SELECT * FROM users WHERE login_id = '' or 1 = 1 ; --' and password = '';
また次の事例として login_id
に以下を与えると
’; delete from users; --
以下のクエリーが生成されます。今度は delete from users;
が実行されてしまい、usersテーブルの中身が削除される結果となります。サービスの影響は計り知れません。
SELECT * FROM users WHERE login_id = ''; delete from users; --' and password = '';
リテラル
まずSQLのリテラルについて知る必要があります。
SELECT * FROM users WHERE name = 'yamada';
SQLは以下の要素で構成されます。
キーワード(予約語) | SELECT FROM WHERE |
---|---|
演算子 | = |
識別子 | name * |
リテラル | 'yamada' |
SQL文中の 'yamada'
の部分をリテラルといい、文字列の場合は文字列リテラル、数字の場合は数値リテラルと呼びます。 'yamada'
は文字列リテラルです。
SQLインジェクションの原因
文字列リテラルのSQLインジェクション
ログイン機能サンプルは文字列リテラルが原因でした。
$sql = "SELECT * FROM users WHERE login_id = '{$login_id}' and password = '{$password}';";
とPOSTパラメーターをそのままSQL文と文字列結合をしているため、パラメーターとしてシングルコーテーション '
、SQLのコメントアウト --
を利用することで、攻撃が有効になります。
数値リテラルのSQLインジェクション
数値リテラルの場合はシングルコーテーションを必要しませんので、
SELECT * FROM users where age >= $age;
$age
に 1; delete from users
の値が与えられた場合は、
SELECT * FROM users where age >= 1; delete from users;
のSQLが成立してしまい、ユーザー情報を全て削除されてしまいます。
対策
「安全なSQLの呼び出し方」に安全なSQLの呼び出し要件は以下のように定義されています。
・ 文字列リテラルに対しては、エスケープすべき文字をエスケープすること
・ 数値リテラルに対しては、数値以外の文字を混入させないこと
これらの解決のためプレースホルダーの利用が強く推奨されています。
$sql = "
SELECT
*
FROM
users
WHERE
login_id = ?
and
password = ?
;
";
$prepare = $db->prepare($sql);
$prepare->execute([$login_id, $password]);
パラメーターの ?
をプレースホルダー、そこに実際の値を割り当てることをバインドと呼びます。プレースホルダーにバインドする方法は2種類あります。
- 静的プレースホルダー
- 動的プレースホルダー
静的プレースホルダー
プレースホルダーのままのSQL文と差し替え用の値両方をデータベースに送信し、データベース側でプレースホルダーへの割当を行うことを静的プレースホルダーといいます。セキュリティ上の観点では最も安全です。
動的プレースホルダー
アプリケーションのライブラリ側でプレースホルダーへの値の割当を行うのを動的プレースホルダーといいます。ただしライブラリ側で問題がある場合に脆弱性に発展してしまう可能性も否定できません。
特に理由がない限りでは静的プレースホルダーを利用するのが最適です。
まとめ
SQLインジェクションの脅威を知ってもらい、その上で原因と対策方法についてまとめました。その影響度合いは甚大であることから、Webセキュリティの基礎として覚えていく必要があります。今後Webセキュリティについて学ぶ方の参考になれば幸いです。
参考
- 体系的に学ぶ安全なWebアプリケーションの作り方
- 安全なウェブサイトの作り方
- PHPでデータベースに接続するときのまとめ