Host header injection
CWE-644CWE-601OWASP A03:2021Bijgewerkt 3 september 20265 min leestijd
Veel applicaties leiden hun eigen adres af uit de Host-header van het binnenkomende verzoek. Die header wordt door de client bepaald, dus een aanvaller kan hem vervangen. Belandt die waarde in een wachtwoordresetlink, dan wordt het herstel-token naar de server van de aanvaller gestuurd zodra het slachtoffer erop klikt.
Een applicatie moet soms haar eigen adres kennen: om een link in een e-mail te zetten, om een absolute verwijzing op te bouwen, om te bepalen welke klant er wordt bediend. De eenvoudigste bron daarvoor lijkt de Host-header van het binnenkomende verzoek. Die bron is alleen niet van u, hij is van degene die het verzoek stuurt. In dit artikel leest u waar dat toe leidt.
Wat is host header injection?
Elk HTTP-verzoek bevat een Host-header met de naam van de server die wordt aangesproken. Die header is nodig omdat één IP-adres veel domeinen kan bedienen; hij vertelt de webserver welke site wordt bedoeld. Van host header injection spreken we wanneer een applicatie die waarde overneemt in haar eigen uitvoer, zonder te controleren of het wel een adres van haarzelf is.
De denkfout is subtiel maar wezenlijk. De Host-header voelt als infrastructuur, iets van de server, niet van de gebruiker. In werkelijkheid staat hij gewoon in het verzoek en kan iedereen die zelf verzoeken opstelt er invullen wat hij wil. Het is dus invoer, en het hoort met dezelfde argwaan te worden behandeld als een formulierveld.
Vergelijk het met een antwoordenvelop waarop de afzender zelf het retouradres mag invullen. Het formulier komt netjes bij u binnen en u stuurt het antwoord terug naar het opgegeven adres. Dat het antwoord daarmee ergens anders belandt dan bij de rechtmatige eigenaar, valt pas op als het te laat is.
Hoe verloopt een host header injection-aanval?
Het meest concrete misbruik zit in de wachtwoordherstelfunctie, omdat daar een geheim per e-mail wordt verstuurd.
Kwetsbaar:
app.post('/wachtwoord-vergeten', async (req, res) => {
const gebruiker = await gebruikers.zoekOpEmail(req.body.email);
if (gebruiker) {
const token = await maakHersteltoken(gebruiker);
// Basis-URL uit het verzoek: door de aanvaller te bepalen
const link = `https://${req.headers.host}/herstel?token=${token}`;
await mail.stuur(gebruiker.email, `Herstel uw wachtwoord: ${link}`);
}
res.send('Als dit adres bekend is, ontvangt u een e-mail.');
});
De aanvaller vraagt een herstel aan voor het adres van het slachtoffer, maar past de header aan:
POST /wachtwoord-vergeten HTTP/1.1
Host: kwaadaardig.example
Content-Type: application/json
{"email":"j.dekker@bedrijf.nl"}
Het slachtoffer krijgt een e-mail die van uw organisatie afkomstig is, met de juiste aanhef en het juiste onderwerp. Alleen wijst de link naar https://kwaadaardig.example/herstel?token=.... Klikt hij erop, dan ontvangt de server van de aanvaller het geldige herstel-token, waarmee die vervolgens zelf het wachtwoord kan instellen. Het slachtoffer heeft niets verdachts gezien: hij had immers zelf om een herstel gevraagd, of dacht dat.
Veilig:
// De applicatie weet zelf onder welk adres zij draait
const BASIS_URL = process.env.BASIS_URL; // https://portaal.example
app.post('/wachtwoord-vergeten', async (req, res) => {
const gebruiker = await gebruikers.zoekOpEmail(req.body.email);
if (gebruiker) {
const token = await maakHersteltoken(gebruiker);
const link = `${BASIS_URL}/herstel?token=${token}`;
await mail.stuur(gebruiker.email, `Herstel uw wachtwoord: ${link}`);
}
res.send('Als dit adres bekend is, ontvangt u een e-mail.');
});
// Vangnet: weiger verzoeken met een onbekende hostnaam
const TOEGESTANE_HOSTS = new Set(['portaal.example', 'www.portaal.example']);
app.use((req, res, next) => {
const host = req.headers.host?.split(':')[0];
if (!TOEGESTANE_HOSTS.has(host)) return res.status(400).send('Ongeldige host');
next();
});
De basis-URL komt nu uit de configuratie en is dus een eigenschap van de installatie in plaats van van het verzoek. De aanvullende controle zorgt ervoor dat verzoeken met een onbekende hostnaam helemaal niet worden verwerkt, waarmee ook de varianten worden afgevangen die u nog niet had voorzien. Bedient u meerdere domeinen, dan bepaalt u de basis-URL aan de hand van een gevalideerde waarde uit die lijst.
X-Forwarded-Host niet. Veel frameworks gebruiken die header bij het bepalen van de basis-URL wanneer de applicatie achter een proxy draait. Wordt hij geaccepteerd van elke client in plaats van alleen van uw eigen vertrouwde proxy, dan is de bescherming op de Host-header eenvoudig te omzeilen.Wat is de impact van host header injection?
De ernst loopt van middelzwaar tot hoog, waarbij de wachtwoordherstelroute het zwaarst weegt. Daar leidt de kwetsbaarheid tot een volledige accountovername zonder dat de aanvaller iets van het slachtoffer hoeft te weten behalve het e-mailadres.
De aanval ontleent zijn kracht aan de geloofwaardigheid. De e-mail komt werkelijk van u: hij is verstuurd door uw server, komt van uw domein, doorstaat uw SPF- en DKIM-controles en ziet er exact uit zoals uw gebruikers gewend zijn. Er is geen enkel signaal dat een oplettende ontvanger zou waarschuwen, behalve de link zelf, en die wordt zelden gecontroleerd bij een e-mail die men verwacht.
Daarnaast zijn er gevolgen buiten e-mail. Wordt het antwoord gecachet, dan kan een vergiftigde versie met verwijzingen naar het domein van de aanvaller aan andere bezoekers worden geserveerd. In omgevingen waar een proxy op basis van de hostnaam routeert of autoriseert, kan een gemanipuleerde header verkeer naar een interne applicatie leiden. Bovendien bij toepassingen die per klant een omgeving selecteren op basis van het subdomein, kan hij toegang tot andermans gegevens opleveren.
Hoe spoor je host header injection op?
Een tester stuurt verzoeken met een gewijzigde Host-header en kijkt of die waarde ergens in het antwoord terugkomt: in een doorverwijzing, in een absolute link in de HTML, in een Location-header of in een verwijzing naar een scriptbestand. Verschijnt de opgegeven waarde, dan wordt de header ergens overgenomen.
De belangrijkste test is die op de wachtwoordherstelfunctie, omdat het effect daar het duidelijkst is: een herstel aanvragen met een afwijkende host en controleren welke link in de e-mail belandt. Verder wordt geprobeerd of de controle te omzeilen is met X-Forwarded-Host, met een poortnummer achter de hostnaam, met een dubbele Host-header of met een absolute URL op de eerste regel van het verzoek. Ook wordt gekeken naar cachegedrag: wordt een antwoord met een vreemde host bewaard en aan anderen geserveerd? AssistSec neemt daarbij de volledige keten mee, inclusief de proxy of loadbalancer ervoor, omdat de vraag welke component de hostnaam mag bepalen daar wordt beantwoord.
Hoe voorkom je host header injection?
- Haal de basis-URL uit uw configuratie en nooit uit de
Host-header van het verzoek. - Weiger verzoeken waarvan de hostnaam niet op uw lijst van toegestane domeinen staat.
- Accepteer
X-Forwarded-Hosten soortgelijke headers uitsluitend van uw eigen vertrouwde proxy. - Bouw links in e-mails altijd op met de geconfigureerde waarde, ook in achtergrondprocessen.
- Configureer uw webserver met een expliciete standaardhost die onbekende namen afwijst.
- Neem de hostnaam niet op in gecachete antwoorden, of laat de cache variëren op die waarde.
- Selecteer een klantomgeving op basis van een gevalideerde waarde, niet op de ruwe header.
- Controleer bij een wachtwoordherstel dat het token alleen bruikbaar is op uw eigen domein.
Bronnen
Veelgestelde vragen
Waarom kan de Host-header niet worden vertrouwd?
Omdat hij onderdeel is van het verzoek en dus volledig door de verzender wordt bepaald. Een browser vult hem correct in, maar een aanvaller die zelf verzoeken opstelt zet erin wat hij wil. Alles wat de server daaruit afleidt, is daarmee door de aanvaller beïnvloedbaar.
Wat zijn X-Forwarded-Host en soortgelijke headers?
Headers die een proxy toevoegt om de oorspronkelijke hostnaam door te geven. Veel frameworks houden er rekening mee bij het bepalen van de basis-URL. Komen ze rechtstreeks van een client, dan zijn ze even onbetrouwbaar als de Host-header, en ze worden vaker over het hoofd gezien.
Hoe stel ik de basis-URL dan wel in?
Als vaste waarde in uw configuratie, per omgeving. De applicatie hoort te weten onder welk adres zij bereikbaar is; dat is een eigenschap van de installatie en niet iets wat per verzoek verschilt. Bij meerdere domeinen houdt u een lijst aan en controleert u daartegen.
Is dit ook een risico zonder e-mail?
Ja. De header kan ook in de cache belanden, waardoor een vergiftigde pagina met verwijzingen naar het domein van de aanvaller aan andere bezoekers wordt geserveerd. Verder kan hij worden misbruikt om routeringsbeslissingen of toegangscontroles in een proxy te beïnvloeden.
Verwante artikelen
- KwetsbaarhedenCWE-287A07:2021Broken authenticationBroken authentication uitgelegd: hoe aanvallers via brute force, gelekte wachtwoorden en voorspelbare sessietokens accounts overnemen, en wat u eraan doet.
- KwetsbaarhedenCWE-942A05:2021Onveilige CORS-configuratieEen CORS-beleid dat elke herkomst weerspiegelt, geeft vreemde sites toegang tot uw API namens ingelogde gebruikers. Lees hoe u het goed instelt.
- KwetsbaarhedenCWE-601A01:2021Open redirectOpen redirect uitgelegd: hoe een returnUrl-parameter bezoekers naar een phishingsite stuurt, welke bypasses werken en hoe u veilig doorverwijst.
- KwetsbaarhedenCWE-16A05:2021Security misconfigurationSecurity misconfiguration uitgelegd: hoe standaardwachtwoorden, debug-modi en open cloud-buckets aanvallers binnenlaten, en hoe u dit voorkomt.
- KwetsbaarhedenCWE-918A10:2021Server-side request forgery (SSRF)Server-side request forgery (SSRF) uitgelegd: hoe aanvallers uw server dwingen interne systemen en clouddiensten te bereiken, en hoe u het voorkomt.