quiz3 / CTF wirte_up
25회 해킹캠프 CTF 웹 분야 접근 방법과 풀이 입니다.
2022, Sep 04
구성
🎓Quiz3
사실 quiz3는 quiz1과 quiz2를 풀어야 나오는 CTF문제이다. 하지만 점점 페이지에 대한 filtering이 추가되며 난이도가 올라가는 형식이기 나는 quiz3만 다뤄보려 한다😉 우선 문제 페이지는 우측과 같다 간략하게 문제 설명을 하자면 페이지를 새로고침 할 때마다 세션을 부여받고 이는 문제정보, 정답, 현재 내 점수에 대한 정보를 담고 있다. 페이지 소스를 확인해보면 이해될 테니 한번 확인해 보도록 하자😗
<html>
<body>
<h1>Quiz!</h1>
<p>your score : 0</p>
<hr>
<h3>3534 * 5239 = ?</h3>
<form action="#" method="GET">
<input type="hidden" name="score" value="1">
<input type="number" name="answer">
<input type="submit">
</form>
</body>
</html>
추가적으로 이번 문제는 quiz1,2 와는 다르게 서버 자체의 소스코드도 문제 자료로 제공 되었는데 코드는 아래와 같다.
from flask import Flask, render_template, request, session
import os
import random
server = Flask(__name__)
server.secret_key = "*****"
FLAG = "HCAMP{*****}"
def check_flag_condition():
if session['score'] > 100:
session['score'] = 0
return True
if session['score'] > 50:
check_number = session['score'] * 100
if check_number % 1 == 0:
# bye bye :)
session['score'] = 0
return False
@server.route('/')
def main():
return render_template('index.html')
@server.route('/quiz')
def quiz():
if session.get('score') == None:
session['score'] = 0
if session.get('answer') != None:
try:
user_answer = int(request.args.get('answer'))
score = round(float(request.args.get('score')), 1)
if score > 1:
score = 1
except:
user_answer = 0
score = 1
if int(session['answer']) == user_answer:
session['score'] += score
else:
# bye bye :)
session['score'] = 0
if check_flag_condition():
return FLAG
# quiz!
a = random.randint(1000, 10000)
b = random.randint(1000, 10000)
session['answer'] = a * b
return render_template('quiz.html',
total_score=session['score'],
a=a, b=b)
if __name__ == '__main__':
server.run(host='0.0.0.0')
코드 분석
quiz 1,2 에서는 hidden타입인 input tag의 value값을 조정하여 score를 100점을 만들어 flag를 획득하는 문제였는데 이번 문제에선 위의 소스코드를 보면 알겠지만 check_flag_condition이라는 함수가 socore를 검열한다.. 해당 부분만 짧게 해석하자면
def check_flag_condition():
if session['score'] > 100:
session['score'] = 0
return True
if session['score'] > 50:
check_number = session['score'] * 100
if check_number % 1 == 0:
# bye bye :)
session['score'] = 0
우선 score가 100점이 넘어가면 True를 반환한다(목표!) 하지만! 50개의 문제를 풀고 51개째의 문제를 푸는 시점부터는 score값이 정수 형태이면 0으로 초기화 된다...😫 아마 여기까지 코드를 확인하면 이런 생각이 들거다 '그럼 소수점 3자리 이상의 값으로 값을 넘기거나 이전 문제와 마찬가지로 100이상의 value값을 넘겨주면 되는거 아닌가?'
def quiz():
if session.get('score') == None:
session['score'] = 0
if session.get('answer') != None:
try:
user_answer = int(request.args.get('answer'))
score = round(float(request.args.get('score')), 1)
if score > 1:
score = 1
except:
user_answer = 0
score = 1
하지만 당연하게도 그 부분도 검열한다😂 quiz라는 함수에 의해 input tag의 value값을 100이상으로 조정해 request 요청을 하더라도 점수는 1점만 오른다. 뿐만 아니라 1이하의 3자리 이상 유리수값을 설정하더라도 type을 float(실수)로 변환후 round()함수를 통해 소수점 첫째 자리까지만 을 score에 저장하도록 설계되어있다🤔 그렇다면 이를 우회 할 방법은 없을까?
Exploit
우선 이론상 위의 소스코드를 우회하여 소수점 3자리 이상의 값을 score로 설정할 수 있다면 50점을 넘긴 시점에도 0으로 초기화 되지않고 100점 이상에 도달해 flag를 획득할 수 있다. 우선 내가 이 문제를 푼 방법은 다음과 같다.
document.getElementsByTagName('input')[0].value = 0.8;
document.getElementsByTagName('input')[1].value = eval(document.getElementsByTagName('h3')[0].innerHTML.replace('= ?', ''));
document.getElementsByTagName('input')[2].click();