Consulta Torcida
NoSQL Injection
// OBJETIVO
Autenticarte como admin sin la contraseña, inyectando un operador de consulta en lugar de un valor.
// ZONA DE ATAQUE
Corre en un iframe de origen opaco: no puede leer este sitio ni tu progreso. Cuando logres el exploit, la flag aparece dentro de la app — copiala y verificala.
// CONSULTÁ
Pistas
Código vulnerable
El body entra sin validar en el filtro de la consulta:
app.post('/api/login', async (req, res) => {
const { user, pass } = req.body;
const account = await users.findOne({ user, pass }); // ← objetos del cliente
if (account) return res.json({ ok: true, flag: FLAG });
res.status(401).json({ ok: false });
});
Si pass llega como { "$ne": null }, el filtro pasa a ser
{ user: "admin", pass: { $ne: null } }, que matchea a admin con cualquier
contraseña. El operador viaja como dato dentro del JSON.
Remediación
Forzá que las credenciales sean strings antes de consultar, y compará el hash de la contraseña por separado:
const { user, pass } = req.body;
+ if (typeof user !== 'string' || typeof pass !== 'string') {
+ return res.status(400).json({ ok: false });
+ }
- const account = await users.findOne({ user, pass });
+ const account = await users.findOne({ user });
+ const ok = account && (await bcrypt.compare(pass, account.passHash));
- if (account) return res.json({ ok: true, flag: FLAG });
+ if (ok) return res.json({ ok: true, flag: FLAG });
Por qué funciona: al exigir typeof === 'string', un objeto con $ne se
rechaza antes de tocar la base. Buscar solo por user y verificar el hash aparte
saca la contraseña del filtro, donde el operador ya no tiene efecto.