문제 설명은 아래와 같다.
이상하게 이 문제를 푼 줄 알았는데 안풀려 있어서 sql injection 복습용으로 문제를 풀어보기로 했다.
우선 문제 페이지에 접속하면 아래와 같은 화면을 볼 수 있다.
다음은 상단에 있는 각각의 카테고리 내용을 확인해보기 위해 모두 접속한 화면이다. 아래 이미지를 통해 알 수 있듯이, Home, About, Contact 카테고리가 우선 모두 동일한 화면임을 확인할 수 있다.
다음은 로그인 페이지이다. 확인할 수 있는 것처럼 userlevel를 입력할 수 있도록 되어있다. 홈페이지에서는 이 외에 추가적으로 확인할 수 있는 정보는 없으므로 문제 파일로 제공된 소스 코드를 확인해보자.
📌 전체 소스 코드
#!/usr/bin/python3
from flask import Flask, request, render_template, g
import sqlite3
import os
import binascii
app = Flask(__name__)
app.secret_key = os.urandom(32)
try:
FLAG = open('./flag.txt', 'r').read()
except:
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()
def get_db():
db = getattr(g, '_database', None)
if db is None:
db = g._database = sqlite3.connect(DATABASE)
db.row_factory = sqlite3.Row
return db
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
@app.teardown_appcontext
def close_connection(exception):
db = getattr(g, '_database', None)
if db is not None:
db.close()
@app.route('/')
def index():
return render_template('index.html')
@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>'
app.run(host='0.0.0.0', port=8000)
우선 위 코드들 중에서는 로그인 페이지의 코드를 먼저 보아야 하기 때문에 해당 부분을 자세히 살펴보자.
📌 로그인 페이지 코드
@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>'
해당 페이지의 코드를 확인해보면 우리가 입력한 값이 쿼리문에 삽입되고, query_db로 이동하는 것을 확인할 수 있다.
📌 데이터베이스 관련 함수 코드
def get_db():
db = getattr(g, '_database', None)
if db is None:
db = g._database = sqlite3.connect(DATABASE)
db.row_factory = sqlite3.Row
return db
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
조금 더 잘 이해하고자 데이터베이스 관련 코드를 확인해보면 우리가 입력한 쿼리문을 배열로 입력해서 리턴해주고 있는 것을 볼 수 있다.
따라서, sql 인젝션 구문을 활용하여 해당 취약점을 우회할 수 있다고 생각될 수 있지만, 이때 데이터베이스 부분의 코드를 유의해서 보아야 한다. 아래 데이터베이스 코드를 확인해보면 users 테이블에 guest를 먼저 넣고 admin을 넣고 있기 때문에 { 'or'1'='1 } 이 구문으로 우회하는 것은 어려워 보인다.
📌 데이터베이스 코드
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()
따라서 다른 sql injection 구문을 활용하여 다음과 같이 where과 and를 활용한 구문을 아래와 같이 작성해주었고, 입력결과 플래그 값을 확인할 수 있었다.
우선, 해당 구문으로 플래그 값을 확인할 수 있었으나, 이 외에도 union select를 이용하여 아래와 같이 구문을 작성할 수 있다.
{ ' union select * from users;-- - }
문제 해결-!
'CTF' 카테고리의 다른 글
[Webhacking.kr] old-39 (0) | 2024.05.29 |
---|---|
[Dreamhack] out of money (0) | 2024.05.22 |
[Dreamhack] Addition calculator (0) | 2024.05.08 |
[CTFLearn] My Blog (0) | 2024.05.01 |
[CTFLearn] Basic Injection (0) | 2024.05.01 |