Party Cat0%

25/01/26

Next.jsDNSRebinding,PythonCRLFipdfkitInjection

Redacció oficial: pdf.exe | 0xL4ugh v5

Thanks for sharing!

بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيمِ

Next.js DNS Rebinding, Python CRLF i pdfkit Injection
I know your browser history
Benvingut baaaaack!! Avui tenim un emocionant repte d'explotació web en diverses etapes! Això es presentava al 0xL4ugh CTF v5.

Resum Qucik

Aquest repte presenta una aplicació web senzilla construïda amb Next.js que envia sol·licituds a un servei intern de Python Flask per a la generació de PDF. La cadena d'explotació requereix tres vulnerabilitats diferents:
  1. DNS Rebinding SSRF a Next.js Image Optimizer per arribar a un servei intern
  2. CRLF Injection al controlador d'URI urllib.request data: de Python per injectar capçaleres
  3. pdfkit Argument Injection mitjançant metaetiquetes HTML injectades per exfiltrar la bandera
Primer passarem per cada pas per separat i, al final, els combinarem en una explotació completa
Descarrega el repte des d'aquí

Next.js Image Optimizer SSRF

Quan ens acostem a qualsevol aplicació, els fitxers de configuració són una mina d'or, així que marcant next.config.ts trobem el següent:
El comodí hostname: "**" permet que l'optimitzador d'imatges obtingui imatges de qualsevol host HTTP. Aquesta és una bandera vermella aquí i pot portar a un punt d'entrada!
Si preguntes com sabria que està relacionat amb l'optimitzador d'imatges? Podeu consultar la documentació o el codi font de Next.js, o simplement preguntar a ai 🙂
ai
Per què importa això? El punt final /_next/image de Next.js realitza obtencions del costat del servidor per recuperar i optimitzar imatges. Si podem controlar l'URL, podríem fer que el servidor obtingui recursos interns! territori clàssic SSRF.
També l'optimitzador d'imatges té un historial de problemes de seguretat. És un objectiu de gran valor! Submergem-nos en alguns detalls.
El punt final /_next/image accepta tres paràmetres:
ParàmetreDescripció
urlL'URL de la imatge per obtenir i optimitzar
wAmplada desitjada (ha d'estar en deviceSizes o imageSizes)
qQualitat (1-100)
Una sol·licitud típica és la següent:
El servidor obté l'URL, optimitza la imatge i la retorna. El nostre objectiu: fer-lo buscar http://127.0.0.1:5000/generate.
Si provem una SSRF ingènua:
Bloquejat com s'esperava. L'optimitzador d'imatges té proteccions contra adreces IP privades. Així que... anem a submergir-nos en el codi font next.js!

La nostra funció objectiu és fetchExternalImage, la podeu obtenir a (packages/next/server/image-optimizer/image-optimizer.ts). És responsable d'aconseguir imatges externes alhora que fa complir les comprovacions de seguretat com el bloqueig d'IP privada.
Què fa la funció:
  1. Analitzeu el nom d'amfitrió a partir de l'URL
  2. Resol DNS per obtenir adreces IP
  3. Comproveu si alguna IP resolta és privada (127.0.0.0/8, 10.0.0.0/8, etc.)
  4. Si és privat -> rebutja la sol·licitud
  5. En cas contrari -> continua amb fetch()
Aquí hi ha un buit entre el hora de comprovació i el temps d'ús (TOCTOU)!
La resolució de DNS per a la validació es produeix abans del fetch() real. Però fetch() realitzarà la seva propia resolució DNS. Si la resposta del DNS canvia entre aquestes dues cerques, la validació es salta.
Això s'anomena DNS Rebinding!
Reenllaç de DNS aprofita la bretxa entre la resolució de DNS per a la validació i la resolució de DNS per a la sol·licitud real
  1. Controleu un domini (p. ex., evil.mushroom.cat)
  2. El servidor DNS està configurat amb un TTL molt baix i les respostes alternatives:
    • Primera consulta -> 1.2.3.4 (IP pública, passa la validació)
    • Segona consulta -> 127.0.0.1 (IP privada, objectiu real)
  3. Optimitzador d'imatges:
    • Resol evil.mushroom.cat -> 1.2.3.4 ✅ (passes de validació)
    • Trucades fetch(evil.mushroom.cat) -> DNS torna a resoldre -> 127.0.0.1
  • Sol·licitud de visites localhost! ✅✅✅✅
No necessitareu tenir un servidor DNS per a això, podeu utilitzar serveis com https://lock.cmpxchg8b.com/rebinder.html. El format del nom d'amfitrió A.B.rbndr.us alterna entre adreces IP codificades com A i B en hexadecimal. Podeu configurar-ho a la interfície d'usuari
dns rebind website
Nom d'amfitrió tornant a vincular: 7f000001.8efab5ae.rbndr.us
Aquest nom d'amfitrió es resoldrà aleatòriament a 127.0.0.1 o 142.250.181.174. Amb prou sol·licituds, finalment arribarem a la finestra TOCTOU on la validació veu la IP pública però fetch() obté la IP privada.
Exemple de càrrega útil:
Avís que hem afegit :5000 per especificar el port de servei intern de Flask
El que només hem de fer és repetir aquesta sol·licitud fins que arribem a la finestra TOCTOU
El que hem aconseguit:
  • SSRF als serveis interns
  • Possibilitat de trucar al punt final /generate
Ara... hora de la part de pyhton!

Python urllib CRLF Injection in (data:) URIs

Mirant l'aplicació interna de Flask (internal/app.py), veiem el punt final /generate:
La funció fetch_pdf_report processa l'URI de dades:
L'entrada controlada per l'usuari (data_uri) es passa directament a urlopen(). L'única validació és que ha de començar amb data:plain/text.

El mòdul urllib.request de Python gestiona els URI data: mitjançant la classe DataHandler. El format d'URI data: és:
Per exemple: data:text/plain,mushroom
Quan Python analitza això, extreu les capçaleres de la secció de tipus de suport mitjançant email.message_from_string(). Aquí és on rau la vulnerabilitat.
El codi a urllib/request.py:
La part mediatype de l'URI de dades s'insereix directament en una cadena que s'analitza mitjançant email.message_from_string(). Aquesta funció espera capçaleres formatades on les noves línies separen diferents capçaleres.

La funció email.message_from_string() analitza les capçaleres. Les capçaleres estan separades per noves línies (\r\n o \n). Per tant, si injectem noves línies (%0A) a la part del tipus de mitjà
Quan es descodifica per URL, %0A es converteix en una nova línia. L'analitzador de correu electrònic veu:
Hem injectat una capçalera arbitraria d'estil HTTP! Aquest és CVE-2025-15282 (El meu primer CVE 😁. Encara no es va publicar en una versió estable en el moment d'escriure aquest escrit)

Mirant enrere a fetch_pdf_report:
Si injectem una capçalera Content-Disposition mitjançant CRLF, s'extreu i s'incorpora al contingut de l'informe. A continuació, aquest contingut es passa a pdfkit.from_string():
Podem injectar contingut arbitrari a la cadena que pdfkit converteix en PDF
Això estableix el pas final d'explotació.

pdfkit Injecció d'arguments (The Flag Exfiltration)

pdfkit és un embolcall de Python al voltant de wkhtmltopdf, una eina de línia d'ordres que converteix HTML a PDF. Quan es crida a pdfkit.from_string(content, output_path), bàsicament s'executa:
Amb el contingut HTML canalitzat a stdin.
La tècnica d'injecció de metatag
wkhtmltopdf admet una funció on determinades metaetiquetes HTML s'interpreten com a arguments de línia d'ordres. Aquesta és una característica documentada (però perillosa):
Es converteix en l'argument de la línia d'ordres: --KEY VALUE
Aquesta classe de vulnerabilitat s'ha documentat abans, vegeu aquesta anàlisi de la vulnerabilitat de la biblioteca Python pdfkit per obtenir més informació sobre com es pot abusar de les metaetiquetes wkhtmltopdf.
Els arguments inclouen:
Meta etiquetawkhtmltopdf ArgumentEfecte
pdfkit-post-file--post-filePOST el contingut del fitxer a l'URL
pdfkit-leak-data--leak-dataNom de camp arbitrari (argument consumit per --post-file)
pdfkit-cache-dir--cache-dirControlar la ubicació de la memòria cau
Hem d'injectar HTML que farà que wkhtmltopdf s'exfilti /flag al nostre webhook.
Càrrega útil objectiu:
Aquestes metaetiquetes es tradueixen en arguments wkhtmltopdf que llegiran /flag i PUBLICAR el seu contingut al nostre webhook.

Cadena d'explotació completa

Combinant els tres passos:
  1. SSRF mitjançant reenllaç DNS per arribar al servei intern de Flask
  2. Injecció de CRLF a l'URI de dades per injectar la capçalera Content-Disposition
  3. Injecció de metaetiquetes pdfkit per exfiltrar la bandera
Exemple de solucionador final:

I això va ser tot! Sincerament, aquest va ser un dels meus preferits per treballar. Tot i que vaig obtenir un duplicat a l'informe Next.js, encara va ser fantàstic trobar-lo i veure'l jugant un paper en una cadena més gran. A més, com que el Python CVE va ser la meva pròpia troballa, va fer que l'explotació final se sentia molt millor! 😍
Aviat hi haurà un nou escrit sobre alguns consells de recerca de seguretat i com vaig poder trobar 6 CVE tant a la biblioteca estàndard de Python com a altres paquets Python populars! Estigueu atents :)

També et pot interessar