quiz3 / CTF wirte_up

25회 해킹캠프 CTF 웹 분야 접근 방법과 풀이 입니다.

구성

펼치기/접기
  1. 🎓Quiz3
  2. 코드 분석
  3. Exploit


🎓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();
솔직히 이 문제를 풀었다기 보다는 이것 저것 시도해보다 보니 얻어 걸린 느낌이 강하다..나는 javascript를 통해 문제를 계산하여 submit요청을 보내도록 console을 통해 요청했고 이때 hidden type의 input tag의 value 값을 0.7로 조정 했다. 근데 아니나 다를까 이를 반복해서 요청 했더니 round()함수가 정상적으로 작동하지 못하고 소수점 3자리 이상의 결과값을 반환했다. 해당 문제는 같은 팀원이 말하길 부동소수점에 대한 오류라고 말하긴 했으나.. 나는 전혀 알지 못했다😨 write up 을 작성하기위해 잠시 찾아봤으나 생각보다 내용이 심오하다..이부분은 추가적으로 공부해서 따로 posting하도록 하겠다🧐

📃최근 게시글