JSForge
Acceder →
← Volver al catálogo
SERVIDORMEDIO En navegador

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

app vulnerable · sandbox aislado

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.

    Verificación

    Se verifica en tu navegador contra un hash. La flag no se envía a ningún lado.