Secure Notes
HackTheBox
A Node.js note-taking application with Express, Mongoose 7.2.4, and MongoDB. The flag endpoint only responds to requests from localhost (127.0.0.1).
$ ls tags/ techniques/
$ cat /etc/rate-limit
Rate limit reached (20 reads/hour per IP). Showing preview only — full content returns at the next hour roll-over.
Secure Notes - HackTheBox
Description
We built this note-taking app to be so simple, there can't possibly be any bugs. We even added a door to claim the flag. However, only those who knock from inside may enter!
A Node.js note-taking application with Express, Mongoose 7.2.4, and MongoDB. The flag endpoint only responds to requests from localhost (127.0.0.1).
Analysis
Application Structure
The application provides three main endpoints:
POST /create- Create a new notePOST /update- Update an existing noteGET /flag- Returns flag only if request comes from localhost
The Flag Endpoint
app.get('/flag', (req, res) => { const remoteAddress = req.connection.remoteAddress; if (remoteAddress === '127.0.0.1' || remoteAddress === '::1' || remoteAddress === '::ffff:127.0.0.1') { res.send(process.env.FLAG ?? 'HTB{f4k3_fl4g_f0r_t3st1ng}'); } else { res.status(403).json({ Message: 'Access denied' }); } });
The flag is protected by an IP check - only localhost can access it. The hint "knock from inside" suggests we need to make the server think our request comes from localhost.
The Vulnerable Update Endpoint
app.post('/update', async (req, res) => { const { noteId } = req.body; await Note.findByIdAndUpdate(noteId, req.body); // req.body passed directly! let result = await Note.find({ _id: noteId }); res.json(result); });
Critical vulnerability: The entire req.body is passed directly to findByIdAndUpdate(), allowing MongoDB operators like $rename to be injected.
Vulnerability Chain
1. CVE-2023-3696: Mongoose Prototype Pollution
Mongoose version 7.2.4 is vulnerable to Prototype Pollution via the $rename operator. When using $rename with a path like __proto__.something, Mongoose's init() function will create nested objects and pollute Object.prototype.
Key insight: MongoDB's $rename operator creates nested objects, not dotted field names. So:
$rename: {"title": "__proto__._peername.address"}
Creates:
{__proto__: {_peername: {address: "127.0.0.1"}}}
...
$ grep --similar
Similar writeups
- [web][free]Celestial Scribe— HackTheBox
- [web][Pro]Lab 326 — PulseBoard — NoSQL Injection in Authentication— hackadvisor
- [web][Pro]Слепая инъекция (Blind Injection)— bug-makers
- [web][Pro]Personal Blog— uoftctf2026
- [web][Pro]Lab 78 — MetricVault — NoSQL Injection in Login Authentication— hackadvisor