Team : r3boot
Rank : 335/1127th
0 solve한 CTF이다. web/pwn 가장 solver가 많고 쉬운 문제를 잡았지만 풀지 못하였다. write-up이 공개되었으니 공부하자!
[DICECTF] knock-knock
https://tzion0.github.io/posts/dicectf-knock-knock/
const crypto = require('crypto');
class Database {
constructor() {
this.notes = [];
this.secret = `secret-${crypto.randomUUID}`;
}
createNote({ data }) {
const id = this.notes.length;
this.notes.push(data);
return {
id,
token: this.generateToken(id),
};
}
getNote({ id, token }) {
if (token !== this.generateToken(id)) return { error: 'invalid token' };
if (id >= this.notes.length) return { error: 'note not found' };
return { data: this.notes[id] };
}
generateToken(id) {
return crypto
.createHmac('sha256', this.secret)
.update(id.toString())
.digest('hex');
}
}
const db = new Database();
db.createNote({ data: process.env.FLAG });
const express = require('express');
const app = express();
app.use(express.urlencoded({ extended: false }));
app.use(express.static('public'));
app.post('/create', (req, res) => {
const data = req.body.data ?? 'no data provided.';
const { id, token } = db.createNote({ data: data.toString() });
res.redirect(`/note?id=${id}&token=${token}`);
});
app.get('/note', (req, res) => {
const { id, token } = req.query;
const note = db.getNote({
id: parseInt(id ?? '-1'),
token: (token ?? '').toString(),
});
if (note.error) {
res.send(note.error);
} else {
res.send(note.data);
}
});
app.listen(3000, () => {
console.log('listening on port 3000');
});
위 코드가 주어진다. 코드를 천천히 읽어보면 난독화나 어려운 내용은 없었기 때문에, 돌아가는 구조를 파악할 수 있다. createNote로 계속 push(data)를 하면서 진행되기 때문에 FLAG 값은 서비스를 처음 구동시킬 때 등록되어 id=0으로 push 되었음을 알 수 있었다.
즉, id는 알지만 token 값을 모르는 상황이다. token은 아래와 같이 만들어진다
class Database {
constructor() {
this.notes = [];
this.secret = `secret-${crypto.randomUUID}`;
}
generateToken(id) {
return crypto
.createHmac('sha256', this.secret)
.update(id.toString())
.digest('hex');
}
위 함수를 처음 보고 crypto.randomUUID 를 검색하였는데,
위와 같이 "36b8f84d-df4e-4d49-b662-bcde71a8764f"와 같은 형태의 랜덤 값을 만들어주는 것을 알 수 있었다
그래서 계속 저 randomUUID를 역으로 알아내는 형태의 문제라고 생각하고 접근했지만 전혀 아니었다
문제와 예시의 다른점을 보면,
주어진 문제에서는 randomUUID()에 ()가 빠져있다
즉, 잘못 사용하고 있는 것이다
()가 빠진 상태로 어떤 값이 this.secret에 저장되는지 확인해보니,
위와 같이 secret-후에 랜덤값이 아닌 randomUUID 함수의 코드가 저장되는 것을 알 수 있다.
즉, 랜덤한 값으로 token을 생성하고 있는 것이 아니라 고정된 위의 값으로 token을 생성하고 있다는 것이다
따라서 generateToken(0)을 실행해보면, token을 얻을 수 있다
위와 같이 접속하면 FLAG를 확인할 수 있다
FLAG : dice{1_d00r_y0u_d00r_w3_a11_d00r_f0r_1_d00r}
※ 함수 사용 시 () 등 오탈자를 잘 보고, 그로 인한 문제점을 잘 고려해보자
※ online IDE가 잘되어있으니, 적극 활용하자