🚀 ニフティ’s Notion

【セキュリティ2025 #5】Webセキュリティ演習

🚨
本演習では、ローカルPC内の仮想環境に構築したWebアプリケーションを皆さんに攻撃してもらいます。
不正な目的で実行すると不正アクセス禁止法をはじめとする法律抵触する可能性があるものが含まれます。
絶対に、 他人のサーバーやサービスで試すことはしないでください

🎁 今回使う題材

  • 某SNSに似たWebサイト
  • 先程紹介した脆弱性が含まれるWebアプリケーションです。これに攻撃をすることで、脆弱性がどのようなものか体験することができます。PHPのソースコードを見ることが出来るため、どのようにして脆弱性が生まれているのかを見ることができたり、どのような対策をとればよいのかを理解したりすることができます。
🚨
脆弱性が含まれますので、デプロイするのは絶対にやめましょう。

👀どういうWebアプリケーションか見てみる

機能
  • アカウント作成
  • ログイン、ログアウト
  • コメントの投稿
  • ユーザー検索
  • DM

軽く触ってみよう
  1. アカウントをお持ちでないですか?新規登録からアカウントの作成
image block

  1. ユーザー名(ログイン時に必要なID)、表示名(公開される名前)、パスワードを入力して登録する
    実名とかを入れないように!全て適当な名前でOK
image block
  1. ホームからはコメントを投稿できる。この投稿は全ユーザーに見える。
    image block

  1. ユーザー検索からユーザーを検索しDMボタンを押すことでDMができる
image block

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

image block

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

Webアプリケーションでは、データベースに情報を蓄積したり取り出したりしています。このデータベースの操作を行う際に使う言語がSQLです。

簡単にいうとSQLを使ってデータベースに問い合わせをかけると、データを保存したり、取り出したりすることができます。詳しくはデータベース回でSQLについて解説しますので、ここでは詳しく立ち入りません。

SQLインジェクションとは、Webアプリケーションに実装されているSQL文を不正に書き換えることで、意図しない処理を走らせる攻撃をいいます。

SELECT文について
  • データベースから情報を取得するSQL文がSELECTです。
  • 基本的な文法は以下の通りです。
    SELECT [取得したい要素] FROM テーブルの名前 WHERE 条件;
  • テーブルは以下の構成になっています。
    • usersテーブル
      image block

⚔️SQLインジェクションをしてみよう

  • 検索フォームに以下のような入力を入れてみましょう。想定としては、 ' OR 'a'='a というIDを持つユーザーが検索に引っかかるはずです。
    ' OR 'a'='a
    結果

    ' OR 'a'='aというIDを持つユーザーが表示されるはずなのに、情報が全部出てきてしまった!

    image block
    何故こうなったのか?

    実際のコードを見てみよう。接続したCLI上で下記のコマンドを打ってみよう

    image block
    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%’が真となります。

    image block

    すると条件は「ユーザー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() -- 
結果
全てのテーブルを取得してくることができた
image block

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

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アプリケーションではデータベースに直接パスワードを保存することはなく、元々のパスワードの値がわからないようにハッシュ化して保存しています。
    image block
    ユーザー md5ハッシュ化されたパスワード
    user1 5f4dcc3b5aa765d61d8327deb882cf99
    admin 0f359740bd1cda994f8b55330c86d845

🔒パスワードを復号する

カラム一覧を取得した際にusernameとpassword_md5というカラムがあったなぁ。

md5の説明を追加する

image block
image block

あれ、md5だったら復号出来るのでは?

🤺パスワード解析
  • パスワードクラッカーで解析することもありますが、今回は簡易にハッシュ値から元の入力値を調べるWebサービスを使用します。このようなWebサービスはいくつかありますが、今回は以下を使用します。

  • 今回はuser11のハッシュ化されたパスワード(0f359740bd1cda994f8b55330c86d845)を戻してみます。
    結果は以下のようにuser11のパスワードは「p@ssw0rd」ということがわかりました。
image block

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

image block

💂 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);

抜粋すると以下のようにコードが変わっています。

このようにSQL文を直接書く場合には、SQLインジェクションが起きないよう、SQL文、変数の埋め込みには注意しましょう。
🚨
他にも、応答ページから情報を直接奪うのではなく、応答ページの違い(応答時間の差、データの違い)から情報を奪う攻撃である、ブラインドSQLインジェクションもあるので注意しましょう。