Direct naar inhoud

Clickjacking

CWE-1021OWASP A05:2021Bijgewerkt 3 september 20265 min leestijd

Bij clickjacking laadt een aanvaller uw applicatie onzichtbaar in een frame op zijn eigen pagina en legt daar zijn eigen knoppen overheen. Het slachtoffer denkt op de site van de aanvaller te klikken, maar klikt in werkelijkheid op een knop in uw applicatie, waar het nog is ingelogd. De actie wordt uitgevoerd met alle rechten van die gebruiker.

De meeste aanvallen draaien om invoer die een applicatie ten onrechte vertrouwt. Clickjacking draait om iets anders: de aanvaller misbruikt niet uw code, maar de ogen van uw gebruiker. Hieronder leest u hoe een onzichtbaar frame een klik kan omleiden, waarom oude JavaScript-trucs daar niet tegen helpen en met welke twee headers u het probleem in één keer afsluit.

Wat is clickjacking?

Clickjacking is een aanval waarbij een kwaadwillende uw applicatie in een frame op zijn eigen pagina laadt, dat frame vrijwel doorzichtig maakt en er zijn eigen opmaak overheen legt. De gebruiker ziet de pagina van de aanvaller, maar zijn muisklikken en toetsaanslagen komen terecht in úw applicatie, waar hij nog gewoon is ingelogd.

Denk aan een glasplaat met een nagetekende knop erop, die precies over de knop van een geldautomaat wordt gelegd. U ziet “saldo opvragen”, u drukt op wat u denkt dat die knop is, maar uw vinger bedient in werkelijkheid de knop “geld opnemen” die eronder zit. Het apparaat doet niets verkeerds: het registreert een echte druk van een echte klant. De misleiding zit volledig in de laag ertussen.

Precies daarom is clickjacking zo lastig te herkennen voor de gebruiker. Er wordt geen wachtwoord gestolen, er draait geen kwaadaardig script op uw domein en de sessie is volkomen legitiem. De aanvaller stuurt alleen waar de klik terechtkomt.

Hoe werkt een clickjacking-aanval?

De aanval bestaat uit twee lagen die exact over elkaar heen worden gepositioneerd. Onderop staat uw applicatie in een iframe, opgeschaald en verschoven totdat de gewenste knop onder de cursor van het slachtoffer ligt. Daarbovenop staat de lokpagina, met een aantrekkelijke aanleiding om juist daar te klikken.

Kwetsbaar:

HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Cache-Control: no-store

Dit antwoord bevat geen enkele uitspraak over wie de pagina mag insluiten. Elke willekeurige site op internet mag deze applicatie dus in een frame zetten, en de browser werkt daar zonder morren aan mee. Dat is alles wat een aanvaller nodig heeft:

<!-- Op de pagina van de aanvaller -->
<style>
  iframe {
    position: absolute; top: -180px; left: -420px;
    width: 1200px; height: 900px;
    opacity: 0.02;            /* praktisch onzichtbaar, maar wel klikbaar */
    border: 0;
  }
  .lokaas { position: absolute; top: 320px; left: 300px; z-index: -1; }
</style>

<div class="lokaas">
  <h1>U heeft een prijs gewonnen</h1>
  <button>Ophalen</button>
</div>

<iframe src="https://portaal.example/instellingen/beheerders"></iframe>

Het frame ligt bovenop, is nagenoeg doorzichtig en vangt daardoor de klik af; de tekst met het lokaas ligt eronder en is puur decor. Klikt het slachtoffer op “Ophalen”, dan raakt de muisaanwijzer in werkelijkheid de knop “Beheerder toevoegen” in het ingesloten portaal. De sessiecookie gaat automatisch mee, het verzoek komt van de gebruiker zelf en uw server ziet een volstrekt normale handeling.

Veilig:

HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Content-Security-Policy: frame-ancestors 'none'
X-Frame-Options: DENY

Met frame-ancestors 'none' weigert de browser de pagina in welk frame dan ook te tekenen; het lege of geblokkeerde frame maakt de aanval onbruikbaar. De regel X-Frame-Options: DENY zegt hetzelfde in de oudere, breder ondersteunde vorm en dient als vangnet voor verouderde browsers en webviews. Sluit u uw eigen pagina’s wél in, bijvoorbeeld in een portaal, gebruik dan frame-ancestors 'self' respectievelijk SAMEORIGIN. Moet een specifieke partner insluiten, benoem die dan expliciet, frame-ancestors https://partner.example, en nooit met een jokerteken.

Stuur deze headers op élk antwoord mee, niet alleen op de inlogpagina. Aanvallers kiezen juist de dieper gelegen schermen: een bevestigingsdialoog, een pagina met rechtenbeheer of een betaalstap. Een enkel endpoint zonder framebescherming is genoeg om de hele maatregel te omzeilen.

Wat is de impact van clickjacking?

De ernst hangt volledig af van wat er met één klik te bereiken valt. Op een informatieve pagina is het effect verwaarloosbaar en blijft de bevinding laag. Zodra er in de applicatie handelingen bestaan die in één beweging zijn af te ronden, verschuift dat beeld: een account verwijderen, een betaling bevestigen, een koppeling met een externe dienst goedkeuren of iemand beheerdersrechten geven.

Clickjacking wordt gevaarlijker naarmate uw interface efficiënter is ontworpen. Juist de knop die “in één klik” iets regelt, is de knop die een aanvaller onder de cursor van het slachtoffer wil krijgen. Een variant hierop, ook wel likejacking of cursorjacking genoemd, gebruikt dezelfde techniek om sleepbewegingen of toetsaanslagen af te vangen, waarmee zelfs ingevulde formulieren binnen bereik komen.

Belangrijk om te beseffen: het slachtoffer merkt niets en uw logbestanden ook niet. In de audittrail staat een gewone gebruiker die op een gewone knop drukt. Dat maakt achteraf onderzoek lastig en misbruik moeilijk aantoonbaar.

Hoe spoor je clickjacking op?

De eerste controle is triviaal: vraag een pagina op en kijk of het antwoord een Content-Security-Policy met frame-ancestors of een X-Frame-Options bevat. Ontbreken beide, dan is de pagina in beginsel insluitbaar. Een tester bevestigt dat vervolgens met een eigen HTML-bestand dat de pagina in een iframe laadt: verschijnt de applicatie gewoon, dan is de kwetsbaarheid aangetoond.

Daarna begint het echte werk, want de interessante vragen zijn genuanceerder. Geldt de header op álle routes of alleen op de startpagina? Wordt hij ook meegestuurd bij foutpagina’s en bij antwoorden uit een cache of CDN? Staat er misschien een verouderde ALLOW-FROM-waarde die door geen enkele moderne browser meer wordt gehonoreerd? En is de bescherming per ongeluk uitgeschakeld voor een subdomein dat wél gevoelige functies aanbiedt? AssistSec loopt bij een penetratietest de volledige routeboom na in plaats van één steekproef, en beoordeelt bovendien welke handelingen in de applicatie met één klik onomkeerbaar zijn, want dat bepaalt of de bevinding laag of middelzwaar uitvalt.

Hoe voorkom je clickjacking?

  • Stuur op elk HTTP-antwoord een Content-Security-Policy met een expliciete frame-ancestors-richtlijn: 'none' als u nooit ingesloten wordt, 'self' als u alleen uzelf insluit.
  • Voeg X-Frame-Options: DENY of SAMEORIGIN toe als vangnet voor oudere browsers en webviews.
  • Benoem toegestane insluiters altijd expliciet en gebruik nooit een jokerteken in frame-ancestors.
  • Regel de headers centraal in de webserver, reverse proxy of middleware, zodat nieuwe routes ze automatisch overnemen.
  • Vertrouw niet op JavaScript-framebusters; het sandbox-attribuut op een iframe schakelt die eenvoudig uit.
  • Vraag bij onomkeerbare of gevoelige handelingen om een tweede, bewuste bevestiging, bijvoorbeeld het opnieuw invoeren van het wachtwoord.
  • Controleer of ook foutpagina’s, redirects en antwoorden uit uw CDN de headers meesturen.

Bronnen

Veelgestelde vragen

Is X-Frame-Options nog nodig naast een CSP?

Voor moderne browsers is frame-ancestors in de Content Security Policy leidend; die wint zelfs van X-Frame-Options als beide aanwezig zijn. Toch is het verstandig beide mee te sturen, omdat verouderde browsers en sommige embedded webviews frame-ancestors niet kennen en dan terugvallen op de oudere header.

Waarom werkt een JavaScript-framebuster niet meer?

Framebusters (scripts die controleren of de pagina in een frame staat) zijn jarenlang gebruikt, maar zijn te omzeilen met het sandbox-attribuut op het iframe, dat de scripts van de ingesloten pagina simpelweg uitschakelt. Een verdediging die de aanvaller kan uitzetten is geen verdediging; alleen een HTTP-header werkt betrouwbaar.

Is clickjacking ernstig als er geen gevoelige knoppen zijn?

Dan blijft de impact beperkt, en dat is ook waarom de bevinding vaak als laag wordt beoordeeld. De ernst hangt volledig af van wat er met één klik te bereiken is: een 'verwijder account'-knop, een betaalbevestiging of het toekennen van rechten maakt dezelfde kwetsbaarheid ineens middelzwaar tot hoog.

Beschermt een CSRF-token tegen clickjacking?

Nee. Bij clickjacking klikt de echte gebruiker in uw echte pagina, dus het CSRF-token wordt keurig meegestuurd: het is immers gewoon uw eigen formulier. CSRF-tokens en framebescherming dekken verschillende aanvallen af en zijn geen vervanging van elkaar.

Verwante artikelen

Druk op / om te zoeken · Esc