CRLF injection en HTTP response splitting
CWE-113OWASP A03:2021Bijgewerkt 31 augustus 20266 min leestijd
CRLF injection is een kwetsbaarheid waarbij een aanvaller een regeleinde in een HTTP-headerwaarde krijgt en daarmee eigen headers toevoegt of de respons in twee berichten splitst. CRLF staat voor carriage return en line feed, de twee stuurtekens waarmee HTTP elke headerregel afsluit. De oplossing is stuurtekens weigeren en headers uitsluitend via de API van uw framework zetten.
Een HTTP-respons is gewone tekst met een strikte indeling: elke headerregel eindigt op een regeleinde en een lege regel scheidt de headers van de body. Belandt gebruikersinvoer ongefilterd in een headerwaarde, dan stuurt een aanvaller zo’n regeleinde zelf mee en neemt hij de opbouw van het bericht over. Dat heet CRLF injection. Hieronder leest u hoe dat werkt en wat u eraan doet.
Wat is CRLF injection?
CRLF injection is een kwetsbaarheid waarbij een aanvaller een regeleinde in een HTTP-headerwaarde krijgt en daarmee eigen headers toevoegt of de respons in twee losse berichten splitst. CRLF staat voor carriage return en line feed: de twee stuurtekens, geschreven als \r en \n, waarmee HTTP/1.1 elke headerregel afsluit. Twee van die paren achter elkaar, oftewel een lege regel, markeren het einde van de headers en het begin van de body.
Een alledaagse vergelijking: u dicteert een adres aan een typist die letterlijk opschrijft wat hij hoort. Zegt iemand halverwege hardop “nieuwe regel, afzender is Jansen”, dan typt hij dat gehoorzaam als een nieuw veld. Hij hoort niet welk deel uw gegevens waren en welk deel een instructie over de indeling. Een HTTP-parser doet precies hetzelfde met de tekens 13 en 10.
De gevolgen komen in twee gradaties. Bij header injection voegt de aanvaller één of meer extra headerregels toe aan een verder normale respons, bijvoorbeeld een eigen Set-Cookie. Bij HTTP response splitting stuurt hij ook de lege regel mee en schrijft hij een volledige tweede respons, met eigen statusregel, headers en body.
Hoe werkt een CRLF injection-aanval?
Neem een endpoint dat de gebruiker na het inloggen terugstuurt naar de pagina waar hij vandaan kwam. De bestemming komt uit een queryparameter en gaat rechtstreeks de Location-header in. De code stelt de respons zelf als tekst samen, zoals dat gebeurt in een eigen HTTP-laag, in een proxy of in oudere code die direct naar de socket schrijft.
Kwetsbaar:
function sendRedirect(socket, next) {
// De respons wordt als platte tekst opgebouwd en ongecontroleerd weggeschreven
const response =
"HTTP/1.1 302 Found\r\n" +
"Location: " + next + "\r\n" +
"Content-Length: 0\r\n" +
"\r\n";
socket.write(response);
}
Een gewone gebruiker vraagt /go?next=/dashboard op en wordt netjes doorgestuurd. Een aanvaller zet in diezelfde parameter een URL-gecodeerd regeleinde, %0d%0a, gevolgd door een header naar keuze:
GET /go?next=/dashboard%0d%0aSet-Cookie:%20session=6f21ab%3B%20Path=/ HTTP/1.1
Host: app.example.com
De server decodeert de parameter en plakt de waarde in de header. Wat er daadwerkelijk over de lijn gaat, is dit:
HTTP/1.1 302 Found
Location: /dashboard
Set-Cookie: session=6f21ab; Path=/
Content-Length: 0
Voor de browser is die derde regel niet te onderscheiden van een header die uw applicatie zelf heeft gezet. Daarmee bepaalt de aanvaller de sessiecookie van het slachtoffer: hij lokt iemand naar de link, de server zet de sessie-identificatie die hij zelf kent, en zodra het slachtoffer inlogt, deelt hij die sessie. Dat is session fixation, via een header die u nooit hebt geschreven.
Stuurt hij twee regeleindes achter elkaar mee, dan eindigt de eerste respons en begint een nieuwe die hij zelf invult, inclusief een eigen Content-Type en een body met HTML of script.
De oplossing heeft twee lagen: laat een header nooit vrije, door de gebruiker gekozen tekst bevatten, en gebruik de header-API van uw framework in plaats van zelf een responstekst samen te stellen.
Veilig:
const TOEGESTANE_PADEN = new Set(["/dashboard", "/profiel", "/instellingen"]);
function sendRedirect(res, next) {
// 1. Weiger alle stuurtekens, niet alleen CR en LF
if (typeof next !== "string" || /[\x00-\x1f\x7f]/.test(next)) {
res.writeHead(400).end("Ongeldige parameter");
return;
}
// 2. Allowlist: de header krijgt alleen een waarde die de server zelf kent
const target = TOEGESTANE_PADEN.has(next) ? next : "/dashboard";
// 3. Framework-API in plaats van een zelfgebouwde responstekst
res.writeHead(302, { Location: target });
res.end();
}
Node.js weigert een headerwaarde met een regeleinde en werpt een fout; de meeste moderne frameworks doen iets vergelijkbaars. Die controle is uw vangnet, niet uw ontwerp. De allowlist zorgt ervoor dat er sowieso niets in de header terechtkomt wat de gebruiker heeft bedacht, ook als er ooit een minder strenge component tussen komt.
Wat is de impact van CRLF injection?
De ernst hangt af van wat de aanvaller in de respons kan zetten en wie die respons te zien krijgt. Kan hij alleen zijn eigen respons manipuleren, dan is dat interessant voor een rapport, zonder direct slachtoffer. Kan hij een header forceren die de browser van iemand anders stuurt, dan ligt de lat hoger: een eigen Set-Cookie leidt tot session fixation en daarmee tot toegang tot het account van het slachtoffer, en een volledige tweede respons met HTML levert cross-site scripting op in de context van uw eigen domein.
De zwaarste variant loopt via caching. Slaat een reverse proxy, een CDN of de browsercache de gesplitste respons op onder de URL van een normale pagina, dan bedient dat systeem daarna elke bezoeker met de inhoud van de aanvaller, ook wie de kwaadaardige link nooit heeft aangeklikt. Een fout in één parameter wordt dan een defacement of een phishingpagina op uw eigen adres, die blijft staan tot de cache is geleegd.
Zakelijk vertaalt zich dat naar accountovernames, reputatieschade en, bij misbruik van persoonsgegevens, een meldplicht. Omdat het bereik zo sterk verschilt per situatie, loopt de classificatie van medium tot hoog.
Hoe spoort u CRLF injection op?
Begin met een inventarisatie van elke plek waar invoer in een header belandt: de Location van een redirect, een Set-Cookie met een taalvoorkeur of een terugkeerpad, de bestandsnaam in Content-Disposition bij een download, en eigen headers waarin een verzoek-id of een tenantnaam wordt teruggegeven.
Testen doet u door in zo’n parameter een gecodeerd regeleinde te plaatsen, gevolgd door een onschuldige eigen header met een herkenbare waarde. Bekijk daarna de ruwe respons met een proxy als Burp of met curl, niet de weergave van uw browser: die toont de headers niet. Verschijnt uw testheader als losse regel, dan is het raak. Blijft het stil, probeer dan een losse line feed en een dubbel gecodeerde variant.
Test bovendien de hele keten en niet alleen de applicatie: de applicatieserver kan de waarde weigeren terwijl de proxy ervoor hem alsnog doorlaat. Scanners melden het klassieke geval betrouwbaar, maar missen wat achter authenticatie zit of pas ontstaat in het samenspel van twee servers. AssistSec neemt dit onderzoek mee in een penetratietest en toont per bevinding de ruwe respons waarin de extra header verschijnt.
Hoe voorkomt u CRLF injection?
- Zet headers via de API van uw framework. Stel nooit zelf een responstekst samen. De ingebouwde functies valideren de waarde en weigeren stuurtekens.
- Weiger stuurtekens bij de invoer. Wijs alle tekens uit het bereik van de stuurcodes af, niet alleen carriage return en line feed, en doe dat na het decoderen van de parameter.
- Werk met een allowlist voor headerwaarden. Laat de gebruiker kiezen uit een vaste verzameling paden, talen of bestandsnamen en vertaal die keuze serverzijdig naar de echte waarde.
- Codeer waarden die wel dynamisch moeten zijn. Een bestandsnaam in
Content-Dispositionhoort percent-gecodeerd te zijn, een waarde in een cookie eveneens. - Controleer de hele keten en houd die bijgewerkt. Reverse proxies, CDN-regels en eigen middleware stellen ook headers samen; verouderde componenten hebben hier een lange geschiedenis van fouten.
- Ontsmet ook uw logregels. Dezelfde tekens vervalsen logbestanden en maken onderzoek na een incident onbetrouwbaar.
Bronnen
Veelgestelde vragen
Waar staat CRLF voor?
CRLF is de afkorting van carriage return en line feed, de twee stuurtekens met de codes 13 en 10 die samen een regeleinde vormen. In HTTP/1.1 sluit dat paar elke headerregel af, en een lege regel, dus twee paren achter elkaar, scheidt de headers van de body. Wie die tekens in een headerwaarde krijgt, bepaalt daarmee de indeling van het bericht in plaats van alleen de inhoud.
Wat is het verschil tussen CRLF injection en HTTP response splitting?
CRLF injection beschrijft de techniek: een regeleinde belandt in een headerwaarde. HTTP response splitting beschrijft het zwaarste gevolg daarvan: de aanvaller stuurt ook de lege regel mee en schrijft een volledige tweede respons met eigen statusregel, headers en body. De tussenvorm, waarbij hij alleen een extra headerregel toevoegt, heet header injection.
Is CRLF injection nog relevant nu frameworks headers valideren?
Ja. Moderne frameworks weigeren een regeleinde in een headerwaarde, maar die controle geldt alleen voor code die de framework-API gebruikt. Eigen HTTP-lagen, reverse proxies, CDN-configuraties en oudere componenten stellen headers vaak nog als tekst samen. Bovendien hoeft niet elke schakel in de keten dezelfde controle uit te voeren.
Hoe ernstig is CRLF injection?
Dat hangt af van wie de gemanipuleerde respons te zien krijgt. Kan de aanvaller alleen zijn eigen respons aanpassen, dan blijft de impact beperkt. Kan hij een Set-Cookie forceren bij een ander of belandt zijn gesplitste respons in een gedeelde cache, dan leidt dat tot session fixation, cross-site scripting of cache poisoning en loopt de classificatie op naar hoog.
Hoe test ik een parameter op CRLF injection?
Stuur een URL-gecodeerd regeleinde gevolgd door een eigen header, bijvoorbeeld een testheader met een vaste waarde, in elke parameter die in een header terechtkomt. Bekijk daarna de ruwe respons, niet de weergave in de browser. Verschijnt uw header als losse regel, dan is de parameter kwetsbaar; blijft het stil, probeer dan een losse line feed en een dubbel gecodeerde variant.
Verwante artikelen
- KwetsbaarhedenCWE-79A03:2021Cross-site scripting (XSS)Cross-site scripting (XSS) laat aanvallers scripts injecteren die in de browser van uw bezoekers draaien. Zo werkt de aanval en zo voorkomt u het.
- KwetsbaarhedenCWE-644A03:2021Host header injectionBouwt uw applicatie links op met de Host-header, dan bepaalt de aanvaller waar die naartoe wijzen. Lees hoe dat wachtwoordherstel kaapt.
- KwetsbaarhedenCWE-601A01:2021Open redirectOpen redirect uitgelegd: hoe een returnUrl-parameter bezoekers naar een phishingsite stuurt, welke bypasses werken en hoe u veilig doorverwijst.
- KwetsbaarhedenCWE-384A07:2021Session fixationSession fixation uitgelegd: hoe een aanvaller vooraf een session id vastlegt, waarom die na het inloggen geldig blijft en hoe sessierotatie het stopt.