문제 페이지
문제에 접근하면 토큰을 주고 있음을 알 수 있다.
문제만으론 더 분석할 수 없어 코드를 바로 확인해 보았다.
# chal.py
from flask import Flask, request, send_file
import secrets
app = Flask(__name__)
FLAG = "irisctf{testflag}"
ADMIN = "redacted"
valid_tokens = {}
@app.route("/")
def index():
return send_file("index.html")
@app.route("/giveflag")
def hello_world():
if "token" not in request.args or "admin" not in request.cookies:
return "Who are you?"
token = request.args["token"]
admin = request.cookies["admin"]
if token not in valid_tokens or admin != ADMIN:
return "Why are you?"
valid_tokens[token] = True
return "GG"
@app.route("/token")
def tok():
token = secrets.token_hex(16)
valid_tokens[token] = False
return token
@app.route("/redeem", methods=["POST"])
def redeem():
if "token" not in request.form:
return "Give me token"
token = request.form["token"]
if token not in valid_tokens or valid_tokens[token] != True:
return "Nice try."
return FLAG
코드는 간단한데, 공격을 위해선 다음과 같은 절차를 따라야 했다.
- ADMIN 쿠키와 token 값을 가진 상태로 /giveflag 경로로 접근해 valid_tokens[token] 값을 True로 설정
- redeem 경로로 이전의 token 값을 가진 상태로 접근, flag 출력
하지만 ADMIN 값을 안다는 것이 불가능하기 때문에 위 코드만 보고는 문제를 푸는 것이 불가능하다.
문제를 다시 보면 일반적인 웹 문제와 달리 nc로 접근 가능함을 알 수 있는데
접근해 보면 bot이 작동하고 있음을 알 수 있다.
// /bot/bot.js
const puppeteer = require('puppeteer');
const fs = require('fs');
const net = require('net');
const BOT_TIMEOUT = process.env.BOT_TIMEOUT || 2*1000;
const puppeter_args = {};
(async function(){
const browser = await puppeteer.launch(puppeter_args);
function ask_for_url(socket) {
socket.state = 'URL';
socket.write('Please send me a URL to open.\n');
}
async function load_url(socket, data) {
let url = data.toString().trim();
console.log(`checking url: ${url}`);
// replace with your server as needed
if (!url.startsWith('http://localhost:1337/') && !url.startsWith('https://localhost:1337/')) {
socket.state = 'ERROR';
socket.write('Invalid URL (must start with http:// or https://).\n');
socket.destroy();
return;
}
socket.state = 'LOADED';
let cookie = JSON.parse(fs.readFileSync('/home/user/cookie'));
const context = await browser.createBrowserContext();
const page = await context.newPage();
await page.setJavaScriptEnabled(false);
await page.setCookie(cookie);
socket.write(`Loading page ${url}.\n`);
setTimeout(()=>{
try {
context.close();
socket.write('timeout\n');
socket.destroy();
} catch (err) {
console.log(`err: ${err}`);
}
}, BOT_TIMEOUT);
await page.goto(url);
}
var server = net.createServer();
server.listen(1338);
console.log('listening on port 1338');
server.on('connection', socket=>{
socket.on('data', data=>{
try {
if (socket.state == 'URL') {
load_url(socket, data);
}
} catch (err) {
console.log(`err: ${err}`);
}
});
try {
ask_for_url(socket);
} catch (err) {
console.log(`err: ${err}`);
}
});
})();
bot의 코드인데 이 봇은 ADMIN 쿠키를 가진 상태로 사용자가 입력한 URL로 접근한다.
즉, bot이 token 값을 가진 채로 /giveflag 경로로 접근하게 된다면 이후 사용자가 /redeem 경로로 접근했을 때
flag를 얻을 수 있을 것이다.
다만 문제의 이름에서 예고하듯 봇에게 입력 가능한 url은 제한되어 있다.
// /bot/policy.json
{
"URLBlocklist": ["*/giveflag", "*?token=*"]
}
해당되는 문자열의 일부를 url 인코딩하여 값을 넘겨주면 정상적으로 bot이 해당 url로 접근 가능하다.
이제 /redeem 경로로 접근하면 flag를 얻을 수 있다.
flag: irisctf{flag_blocked_by_admin}
'CTF' 카테고리의 다른 글
[ISITDTU CTF] Another one Write-Up (0) | 2025.02.27 |
---|---|
[hkcert] Custom-Web-Server(1) Write Up (0) | 2025.02.27 |
[hkcert] Mystiz's Mini CTF (2) Write Up (0) | 2025.02.27 |
[IrisCTF] Password Manager (0) | 2025.02.27 |
[SSUCTF] hellOphp (0) | 2025.02.27 |