25/01/26
Next.jsDNSRebinding,PythonCRLFipdfkitInjection
Redacció oficial: pdf.exe | 0xL4ugh v5
Thanks for sharing!
بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيمِ

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:
- DNS Rebinding SSRF a Next.js Image Optimizer per arribar a un servei intern
- CRLF Injection al controlador d'URI
urllib.requestdata:de Python per injectar capçaleres - 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 🙂
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àmetre | Descripció |
|---|---|
url | L'URL de la imatge per obtenir i optimitzar |
w | Amplada desitjada (ha d'estar en deviceSizes o imageSizes) |
q | Qualitat (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ó:
- Analitzeu el nom d'amfitrió a partir de l'URL
- Resol DNS per obtenir adreces IP
- Comproveu si alguna IP resolta és privada (127.0.0.0/8, 10.0.0.0/8, etc.)
- Si és privat -> rebutja la sol·licitud
- 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
- Controleu un domini (p. ex.,
evil.mushroom.cat) - 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)
- Primera consulta ->
- 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
- Resol
- 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

Nom d'amfitrió tornant a vincular:
7f000001.8efab5ae.rbndr.usAquest 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:5000per 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,mushroomQuan 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 VALUEAquesta 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 etiqueta | wkhtmltopdf Argument | Efecte |
|---|---|---|
pdfkit-post-file | --post-file | POST el contingut del fitxer a l'URL |
pdfkit-leak-data | --leak-data | Nom de camp arbitrari (argument consumit per --post-file) |
pdfkit-cache-dir | --cache-dir | Controlar 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:
- SSRF mitjançant reenllaç DNS per arribar al servei intern de Flask
- Injecció de CRLF a l'URI de dades per injectar la capçalera
Content-Disposition - 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 :)
Etiquetes:


