JSForge
Acceder →
← Volver al catálogo
SERVIDORDIFÍCIL En navegador

Dos Comandos por Uno

OS Command Injection

// OBJETIVO

Inyectar un segundo comando en la herramienta de ping para que el servidor lea /flag.txt.

// 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 comando se arma por concatenación de strings y se ejecuta en una shell:

    app.post('/api/ping', (req, res) => {
      const { host } = req.body;
      exec(`ping -c1 ${host}`, (err, stdout) => {  // ← host sin escapar, en shell
        res.json({ output: stdout });
      });
    });

    Como exec pasa la línea entera a /bin/sh, cualquier metacaracter de shell en host rompe el comando previsto: 8.8.8.8; cat /flag.txt corre el ping y luego un cat arbitrario. Los separadores ;, &&, || y | encadenan comandos que el atacante controla.

    Remediación

    No pases datos del usuario por una shell: ejecutá el binario directamente con sus argumentos como lista, y validá la entrada.

    - const { host } = req.body;
    - exec(`ping -c1 ${host}`, (err, stdout) => {
    -   res.json({ output: stdout });
    - });
    + const { host } = req.body;
    + if (!/^[a-zA-Z0-9.-]+$/.test(host)) return res.status(400).json({ error: 'host inválido' });
    + execFile('ping', ['-c1', '--', host], (err, stdout) => {  // sin shell
    +   res.json({ output: stdout });
    + });

    Por qué funciona: execFile (a diferencia de exec) no invoca una shell, así que ; o | viajan como parte del argumento, no como sintaxis: no hay segundo comando que ejecutar. La validación con una allowlist de caracteres y el -- (fin de opciones) endurecen aún más la entrada.

    Verificación

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