不正な目的で実行すると不正アクセス禁止法をはじめとする法律抵触する可能性があるものが含まれます。
絶対に、 他人のサーバーやサービスで試すことはしないでください 。
🎁 今回使う題材
- 某SNSに似たWebサイト
- 先程紹介した脆弱性が含まれるWebアプリケーションです。これに攻撃をすることで、脆弱性がどのようなものか体験することができます。PHPのソースコードを見ることが出来るため、どのようにして脆弱性が生まれているのかを見ることができたり、どのような対策をとればよいのかを理解したりすることができます。
👀どういうWebアプリケーションか見てみる
機能
- アカウント作成
- ログイン、ログアウト
- コメントの投稿
- ユーザー検索
- DM
軽く触ってみよう
- アカウントをお持ちでないですか?新規登録からアカウントの作成

-
ユーザー名(ログイン時に必要なID)、表示名(公開される名前)、パスワードを入力して登録する
実名とかを入れないように!全て適当な名前でOK

-
ホームからはコメントを投稿できる。この投稿は全ユーザーに見える。
- ユーザー検索からユーザーを検索しDMボタンを押すことでDMができる

5.DMの履歴はメッセージタブから見れる

SQLインジェクションとは?
Webアプリケーションでは、データベースに情報を蓄積したり取り出したりしています。このデータベースの操作を行う際に使う言語がSQLです。
簡単にいうとSQLを使ってデータベースに問い合わせをかけると、データを保存したり、取り出したりすることができます。詳しくはデータベース回でSQLについて解説しますので、ここでは詳しく立ち入りません。
SQLインジェクションとは、Webアプリケーションに実装されているSQL文を不正に書き換えることで、意図しない処理を走らせる攻撃をいいます。
SELECT文について
- データベースから情報を取得するSQL文がSELECTです。
-
基本的な文法は以下の通りです。
SELECT [取得したい要素] FROM テーブルの名前 WHERE 条件;
-
テーブルは以下の構成になっています。
-
usersテーブル
-
usersテーブル
⚔️SQLインジェクションをしてみよう
-
検索フォームに以下のような入力を入れてみましょう。想定としては、
' OR 'a'='a
というIDを持つユーザーが検索に引っかかるはずです。
' OR 'a'='a
結果
' OR 'a'='aというIDを持つユーザーが表示されるはずなのに、情報が全部出てきてしまった!
何故こうなったのか?
実際のコードを見てみよう。接続したCLI上で下記のコマンドを打ってみよう
cat /var/www/html/search_users.php
表示されるソースコード(5~25行目)
$pageTitle = "ユーザー検索"; $keyword = $_GET['keyword'] ?? ''; $searchResults = []; $searchError = ''; // 検索キーワードがある場合のみ検索実行 if (!empty($keyword)) { try { $db = get_db(); // !!!!! VULNERABILITY POINT: SQL Injection (Union-based) !!!!! $sql = "SELECT user_id, username, display_name FROM users WHERE username LIKE '%" . $keyword . "%' OR display_name LIKE '%" . $keyword . "%' LIMIT 20"; // ★脆弱性箇所★ // echo "<pre>DEBUG SQL: " . h($sql) . "</pre>"; // デバッグ用 // ★脆弱なクエリを実行★ $stmt = $db->query($sql); // query() を使うことでインジェクションの影響を受けやすくする例
このWebアプリケーションでは以下のようなSELECT文が実行されています。$keywordにテキストボックスに入力した値がそのまま入ります。
SELECT user_id, username, display_name FROM users WHERE username LIKE '%" . $keyword . "%' OR display_name LIKE '%" . $keyword . "%' LIMIT 20";
$keywordに OR 'a'='aを入力した場合
SELECT user_id, username, display_name FROM users WHERE username LIKE '%' or '1' = '1%' OR display_name LIKE '%' or '1' = '1%' LIMIT 20
これは '%' or '1' = '1%' は%がワイルドカードで0文字以上の任意の文字列という意味なので’1’=’1%’が真となります。
すると条件は「ユーザーIDが1に一致する」か「’a’と’a’が同じ」に合致するものを取り出してくださいということになります。しかし「’a’と’a’が同じ」は常に成り立ってしまうので、user_id = '1'は無視され、ユーザーIDに関わらず全てのデータを取り出すことになってしまいます。これでは、開発者の意図したSQL文になりません。SQL文の一部に文字列を注入して、意図しないSQL文を実行させる攻撃がSQLインジェクションです。
演習
あなたが攻撃者でSQLインジェクションの脆弱性があると分かった場合、どうやって攻撃しますか?
ゴール:adminのDMにあるflag:~~~~という文字列を探そう
ヒント1
adminのDMを見るためには何の情報が必要ですか?
ヒント2
始まりはこれ
' UNION select
ヒント3
パスワードはMD5で暗号化されている
回答までの流れ
- tableのカラム名を確認する
-
usernameとpassword_md5という明らかに怪しいカラムがある
- 参照するSQL文を考える
- password_md5ということなのでMD5で暗号化されている可能性があるので復号してみる
- 取得したIDとパスワードでアカウントにログインしてみる
DB内のすべてのテーブルを取得
' UNION SELECT null, table_name, null FROM information_schema.tables WHERE table_schema = database() --
結果
全てのテーブルを取得してくることができた

DB内のすべてのカラムを取得
' UNION SELECT null, column_name, null FROM information_schema.columns WHERE table_schema = database() --
結果

SQL文の解説
UNION
- 2つのSELECT分の結果を結合する
SELECT null, table_name, null
-
table_name
は、次のinformation_schema.tables
から取得されるテーブル名です。
FROM information_schema.tables
-
information_schema
は、MySQLやMariaDBなどのデータベースシステムに存在する標準的なスキーマ(データベース)です。 -
information_schema.tables
は、現在アクセス可能なすべてのテーブルに関する情報が格納されているビュー(仮想テーブル)です。ここからテーブル名を取得できます。 - よって意味は「カラム情報をもつテーブルから取得する」になります
WHERE table_schema = database()
-
ここがポイントです。
table_name
による絞り込みを行わず、table_schema
が現在のデータベース (database()
で取得される名前) であるもの、という条件のみを指定します。これにより、現在のデータベースに属する 全てのテーブル のカラムが対象となります。 - よってこの意味は「現在接続しているデータベースに属するテーブルのみを抽出するという条件になる
パスワードとユーザー名を出力する
' UNION SELECT null, password_md5, username FROM users --
-
出力結果は以下のようになります。この結果からpasswordに格納されたデータはパスワードのmd5ハッシュ値と推測できます。基本的にWebアプリケーションではデータベースに直接パスワードを保存することはなく、元々のパスワードの値がわからないようにハッシュ化して保存しています。
ユーザー md5ハッシュ化されたパスワード user1 5f4dcc3b5aa765d61d8327deb882cf99 admin 0f359740bd1cda994f8b55330c86d845
🔒パスワードを復号する
カラム一覧を取得した際にusernameとpassword_md5というカラムがあったなぁ。
md5の説明を追加する


あれ、md5だったら復号出来るのでは?
🤺パスワード解析
- パスワードクラッカーで解析することもありますが、今回は簡易にハッシュ値から元の入力値を調べるWebサービスを使用します。このようなWebサービスはいくつかありますが、今回は以下を使用します。
-
今回はuser11のハッシュ化されたパスワード(0f359740bd1cda994f8b55330c86d845)を戻してみます。
結果は以下のようにuser11のパスワードは「p@ssw0rd」ということがわかりました。

取得してきたIDとパスワードを使ってログインし、DMを見に行くと答えが!!

💂 SQLインジェクションに対する対策例
🤔どうして今までの攻撃が効かなくなったのか?
- どうして効かなくなったのでしょうか?画面下部のView Sourceでコードを見てみると以下のような実装になっています。
脆弱性がある時のソースコード
$sql = "SELECT user_id, username, display_name
FROM users
WHERE username LIKE '%" . $keyword . "%' OR display_name LIKE '%" . $keyword . "%'
LIMIT 20"; // ★脆弱性箇所★
// echo "<pre>DEBUG SQL: " . h($sql) . "</pre>"; // デバッグ用
// ★脆弱なクエリを実行★
$stmt = $db->query($sql);
脆弱性がないときのソースコード
// SQLインジェクション対策: プレースホルダを使用したSQLテンプレート
$sql = "SELECT user_id, username, display_name
FROM users
WHERE username LIKE :keyword OR display_name LIKE :keyword
LIMIT 20";
// プリペアドステートメントを準備
$stmt = $db->prepare($sql);
抜粋すると以下のようにコードが変わっています。
-
以下のようにプレースホルダーで変数を埋め込んでいます。
$data = $db->prepare( 'SELECT user_id, username, display_name FROM users WHERE username LIKE :keyword OR display_name LIKE :keyword LIMIT 20";' );
-
「安全なウェブサイトの作り方」で1番目に紹介されている手法です。
SQLには通常、プレースホルダを用いてSQL文を組み立てる仕組みがあります。SQL文の雛形の中に変数の場所を示す記号(プレースホルダ)を置いて、後に、そこに実際の値を機械的な処理で割り当てるものです。ウェブアプリケーションで直接、文字列連結処理によってSQL文を組み立てる方法に比べて、プレースホルダでは、機械的な処理でSQL文が組み立てられるので、SQLインジェクションの脆弱性を解消できます。
-
「安全なウェブサイトの作り方」で1番目に紹介されている手法です。
-
他にも、IDが数値かどうかバリデーションをかけるのも有効です。
if(is_numeric( $id ))