2026. 3. 20. 14:00ㆍ보안/DreamHACK

우선 코드를 분석해보자!
from flask import Flask, request, render_template, g
import sqlite3
import os
import binascii
app = Flask(__name__)
app.secret_key = os.urandom(32)
Flask 웹앱을 생성한다.
try:
FLAG = open('./flag.txt', 'r').read()
except:
FLAG = '[**FLAG**]'
현재 폴더의 flag.txt.를 읽어서 FLAG 변수에 저장한다. 없으면 임시값 [**FLAG**] 을 넣는다.
DATABASE = "database.db"
if os.path.exists(DATABASE) == False:
db = sqlite3.connect(DATABASE)
db.execute('create table users(userid char(100), userpassword char(100), userlevel integer);')
db.execute(f'insert into users(userid, userpassword, userlevel) values ("guest", "guest", 0), ("admin", "{binascii.hexlify(os.urandom(16)).decode("utf8")}", 0);')
db.commit()
db.close()
DB가 없으면 새로 만들고 users 테이블을 생성한다.
컬럼은 userid, userpasswor, userlevel 총 3개를 생성한다.
초기 데이터는 ("guest", "guest",0), ("admin", 랜덤비밀번호, 0) 총 2개를 넣는다.
admin 비밀번호는 랜덤이라 정상 로그인으로는 맞출 수 없다.
그런데 admin의 userlevel도 0인 것을 보아 SQL Injection으로 admin 행을 끌어오게 만드는 문제일 가능성이 높다.
def query_db(query, one=True):
cur = get_db().execute(query)
rv = cur.fetchall()
cur.close()
return (rv[0] if rv else None) if one else rv
쿼리 실행 함수이다.
전달받은 query 문자열을 그대로 실행한다. 파라미터 바인딩이 없기에
사용자 입력이 SQL문 안으로 그대로 들어가면 SQL Injection이 가능하다.
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'GET':
return render_template('login.html')
else:
userlevel = request.form.get('userlevel')
res = query_db(f"select * from users where userlevel='{userlevel}'")
if res:
userid = res[0]
userlevel = res[2]
print(userid, userlevel)
if userid == 'admin' and userlevel == 0:
return f'hello {userid} flag is {FLAG}'
return f'<script>alert("hello {userid}");history.go(-1);</script>'
return '<script>alert("wrong");history.go(-1);</script>'
request.form.get() 는 사용자가 웹에서 입력한 값을 가져오는 함수다.
⭐ 사용자가 POST로 보낸값은 userlevel = request.form.get('userlevel') 뿐이다.
이 값을 SQL에 그대로 넣는다.
보통 로그인이라면 아이디, 비밀번호도 받아야 하는데 해당 문제는 userlevel만 받는다.
이를 통해 이 문제는 특정 조건을 만족하는 행을 DB에서 뽑아오게 하는 문제임을 알 수 있다.
⭐ 취약점
res = query_db(f"select * from users where userlevel='{userlevel}'")
사용자 입력을 SQL 문자열에 직접 이어붙였기 때문에 취약하다.
예를 들어 입력값에 따옴표를 끊고 SQL 문법을 섞으면 원래 개발자가 의도한 조건을 바꿀 수 있다.
해당 구조가 전형적인 SQL Injection 이다.
⭐ 플래그가 나오는 조건
if userid == 'admin' and userlevel == 0:
return f'hello {userid} flag is {FLAG}'
플래그를 얻으려면 res에 들어온 첫번째 행이 userid == 'admin', userlevel == 0 이어야 한다.
DB에 이미 ("admin", 랜덤비밀번호, 0) 행이 존재하므로
쿼리 결과의 첫번째 행을 admin으로 만들면 된다!

주어진 주소에 들어간다.

코드를 해석했던대로 입력값은 userlevel만 존재한다.


정상값인 0 을 입력할 경우, guest로 정상 로그인된다.
우리는 플래그 조건을 참고하여 admin 행이 나오게 해야 한다.
⭐ SQL Injection 공격
select * from users whrere userlevel='입력값'
원래 쿼리에서 입력값으로 WHERE 조건을 바꿔야한다.
0' AND userid='admin' --
우리의 목표는 admin이 첫번째로 출력되게 하는 것이다.
해당값을 입력하면 select * from users wherer userlevel='0' AND userid='admin' -- 쿼리가 실행된다.
0'을 입력하여 문자열을 강제로 닫고 AND 조건을 추가하여
"userlevel이 0이면서 admin" 인 데이터만 가져오게 된다.
기존에 '가 남아있으므로 -- 을 통해 뒤 코드를 다 무시하게 하여 문법 에러를 방지한다.
--을 빼면 Ineternal Server Error가 뜨는 것을 확인할 수 있다.



플래그값을 확인할 수 있다. 😁

문제 해결 성공!
'보안 > DreamHACK' 카테고리의 다른 글
| 1401 - Bypass (0) | 2026.03.20 |
|---|---|
| 768 - Command Injection (0) | 2026.03.20 |