먼저 아래는 서버 코드이다
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')
SQL
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;
코드를 대충 보니 ['union', 'select', 'from', 'and', 'or', 'admin', ' ', '*', '/']이 필터링 되고 있음을 알았다.
바로 떠오른 것은 reverse함수와 concat함수 그리고 대소문자 탐지 우회와 공백 탐지 우회이다.
먼저 아래처럼 admin도 필터링 되고 있다는 것을 보았고 GET메소드로 데이터를 받는 것을 알았다.
먼저 시도했던 것은 아래와 같은 코드이다.
*이 필터링 되는 것을 까먹고 공격했다.
당연히 실패했다.
그 다음에는 concat함수도 써보고 했지만 역시 실패했다.
그 이후 공백을 백쿼터로도 우회해보고 했지만 오류가 났다.
일단 Admin만을 입력하여 대소문자 필터링 우회가 확실히 되는지 보았다.
잘 되는 것을 볼 수 있다.
근데 왜 *로 모든 컬럼을 확인하는 과정에서 admin이라는 uid만 보일까?
그래서 코드를 유심히 보았다.
아래에 result[1]을 보아하니 row중에 2번째만을 출력해주고 있었다.
아래처럼 sql로 보니 두번째 요소는 uid였고 upw는 3번째 요소였다.
자 그럼 내가 해야할 것은 union을 사용하는 것이다.
union을 사용하면 전자의 select문 밑에 후자의 select문에 결과들이 딸려나온다.
그럼 전자의 select문 row를 0개로 하고 그 뒤에 uid,upw,idx 순으로 하게 되면 2번째 row인 upw가 나올 것이다.
컬럼 수를 3개로 맞춰주는 것은 당연한 일이다.
공백 우회는 주석이랑 백쿼터가 아닌 탭으로 하겠다.
허나 탭을 직접 입력할 수는 없으므로 GET 파라미터로 데이터를 받는 다는 점을 이용하여 탭을 url로 encoding한 %09를 이용하겠다.
아래처럼 하고 인코딩을 해주었다.
그리고 아래와 같이 파라미터로 넣어줬는데 실패했다.
그 이유는 앞에 Admin'을 사용하여서 총 6개의 row가 나오게 되었고 여전히 2번째 row는 uid였기 떄문이다.
그래서 아래와 같이 바꾸어 주었고
아래처럼 플래그를 얻을 수 있었다.