# XSS in PHP voorkomen: cross-site scripting veilig afweren

> XSS in PHP voorkomen: uitvoer per context correct coderen, templates met auto-escaping gebruiken en een Content Security Policy instellen. Met voorbeelden.

Bron: https://phpsos.de/nl/ratgeber/xss-php/  
Stand: 25-10-2026  
Aanbieder: PHP SOS, PHP-noodhulp en serverbeveiliging. Directe hulp via WhatsApp: https://wa.me/491736494914?text=Hallo%20PHP%20SOS%2C%20wij%20hebben%20hulp%20nodig%20met%20onze%20PHP-server.%20Bedrijf%3A%20%E2%80%A6%20Website%3A%20%E2%80%A6

**Kort gezegd:** XSS in PHP voorkomt u door elke uitvoer passend bij haar context te coderen: `htmlspecialchars()` voor HTML, `json_encode()` met beschermingsopties voor JavaScript, gecontroleerde URL's voor links. Templates met automatisch escapen nemen u veel daarvan uit handen. Een Content Security Policy en HttpOnly-cookies beperken de schade als er toch een kwetsbaarheid overblijft.

## Waarom cross-site scripting nu mee gecontroleerd moet worden

De aandacht ligt in oktober 2026 bij PHP2Shell, twee kritieke kwetsbaarheden in de PHP-interpreter waarvoor sinds 21-10-2026 patches beschikbaar zijn. Veel bedrijven laten in dat kader hun webtoepassingen controleren. Dat is een goede aanleiding om ook zwakke plekken in de eigen code te bekijken die geen PHP-update verhelpt.

Cross-site scripting, kortweg XSS, is er één van. De OWASP Top 10 plaatsen XSS binnen de categorie injection. Anders dan bij een kwetsbaarheid in de server richt de aanval zich hier tegen de bezoekers en gebruikers van uw website, bijvoorbeeld tegen klanten in de webshop of medewerkers in de backend.

De gevolgen kunnen aanzienlijk zijn: overgenomen sessies van beheerders, gemanipuleerde formulieren die gegevens naar derden sturen, of vervalste inhoud op uw website. Wordt een beheerdersaccount overgenomen, dan kan dat leiden tot volledige controle over de toepassing.

## Hoe ontstaat XSS in PHP-toepassingen?

XSS ontstaat wanneer een toepassing gegevens uit een niet-vertrouwde bron ongecontroleerd op een pagina weergeeft. De browser kan dan niet meer onderscheiden tussen uw inhoud en binnengebrachte code en voert beide uit.

Men onderscheidt drie vormen. Bij gereflecteerde XSS komt de inhoud rechtstreeks uit het huidige verzoek, bijvoorbeeld uit een zoekterm in de URL. Bij opgeslagen XSS staat ze in de database, bijvoorbeeld in een reactie of profielveld, en treft iedereen die de pagina opent. DOM-gebaseerde XSS ontstaat in het JavaScript van de browser, wanneer scripts waarden uit de URL naar de pagina schrijven.

Niet te vertrouwen zijn alle gegevens die niet vast in de code staan: formuliervelden, URL-parameters, cookies, bestandsnamen, antwoorden van externe koppelingen en inhoud uit de eigen database die ooit van buiten kwam.

## Uitvoer per context correct coderen

De belangrijkste regel luidt: codeer bij de uitvoer, en wel passend bij de plek waar de waarde terechtkomt. Voor HTML-inhoud en attributen tussen aanhalingstekens is `htmlspecialchars()` het middel bij uitstek. Een kleine hulpfunctie voorkomt dat opties worden vergeten:

```php
<?php
function e(?string $wert): string
{
    return htmlspecialchars($wert ?? '', ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML5, 'UTF-8');
}
?>
<h2><?= e($produkt['name']) ?></h2>
<input type="text" name="suche" value="<?= e($_GET['q'] ?? '') ?>">
```

Andere contexten vragen andere middelen:

| Context | Veilige methode |
|---|---|
| HTML-inhoud, attribuut tussen aanhalingstekens | `htmlspecialchars()` met `ENT_QUOTES` en UTF-8 |
| Gegevens voor JavaScript | `json_encode()` met `JSON_HEX_TAG`, `JSON_HEX_AMP`, `JSON_HEX_APOS`, `JSON_HEX_QUOT` |
| Parameter in een URL | `rawurlencode()` voor de afzonderlijke waarde |
| Volledige URL uit gebruikersinvoer | Schema controleren, alleen `https` en `http` toestaan, dan `htmlspecialchars()` |
| Attributen zonder aanhalingstekens, event-handlers, CSS | vermijden, daar geen waarden invoegen |

Voor gegevens die een script nodig heeft, is een data-attribuut of een JSON-blok meestal de schoonste weg:

```php
<?php
$daten = json_encode($konfiguration, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT);
?>
<div id="app" data-konfig="<?= e($daten) ?>"></div>
```

In JavaScript leest u de waarde vervolgens uit via het data-attribuut en zet u inhoud met `textContent` in plaats van met `innerHTML`.

## Templates, HTML-opschoning en frameworks

Template-engines als Twig en Blade coderen uitvoer standaard automatisch. Dat sluit een groot deel van de typische fouten uit. Gevaarlijk wordt het waar dit gedrag bewust wordt uitgeschakeld, bijvoorbeeld met het filter `raw` in Twig of de syntaxis `{!! !!}` in Blade. Zulke plekken moeten in de code zeldzaam zijn en bij elke review gericht worden gecontroleerd.

Moet uw toepassing bewust HTML van gebruikers toestaan, bijvoorbeeld opmaak in reacties of in een redactiesysteem, dan volstaat coderen niet, omdat de HTML juist moet worden weergegeven. Dan hebt u een geteste opschoningsbibliotheek nodig zoals HTML Purifier, die alleen een vaste lijst van toegestane elementen en attributen doorlaat. Eigen filters met reguliere expressies zijn volgens de ervaring lekkend.

> **Belangrijk:**
> Controleer vooral backendgebieden. XSS in weergaven die alleen beheerders zien, wordt vaak onderschat, maar treft precies de accounts met de meeste rechten.

## XSS in bestaande PHP-code vinden

In gegroeide toepassingen is de eerste vraag meestal niet hoe men correct codeert, maar waar het tot nu toe is vergeten. Een pragmatische start is het zoeken naar plekken waar variabelen rechtstreeks worden uitgevoerd:

```bash
# Directe uitvoer van aanvraaggegevens
grep -rnE "(echo|print|<\?=)\s*\\\$_(GET|POST|REQUEST|COOKIE|SERVER)" src/ templates/ --include="*.php"
# Uitgeschakeld auto-escaping in Twig en Blade
grep -rnE "\|raw|\{!!" templates/ resources/views/
```

Treffers zijn controleopdrachten, geen kant-en-klare bevindingen. Opgeslagen XSS vindt zo'n zoekopdracht maar beperkt, omdat de waarden daar uit de database komen. Controleer daarom aanvullend alle weergaven die inhoud van gebruikers tonen: reacties, profielen, bestelnotities, supporttickets. Bij CMS'en en webshops horen plug-ins en thema's van derden er uitdrukkelijk bij, want daar zit een groot deel van de uitvoerlogica.

## Tweede verdedigingslinie: CSP en veilige cookies

Ook bij zorgvuldige code kan een plek over het hoofd worden gezien. Twee maatregelen beperken dan de schade. Een Content Security Policy staat de browser alleen scripts uit vastgelegde bronnen toe en verbiedt ingebedde scripts, tenzij ze uitdrukkelijk zijn vrijgegeven:

```apache
Header always set Content-Security-Policy "default-src 'self'; script-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'self'"
```

Voer zo'n beleid stapsgewijs in, eerst in de zuivere rapportagemodus via `Content-Security-Policy-Report-Only`, want bestaande inbedding van analyse- of betaaldiensten moet meestal worden aangevuld. Verdere headers beschrijft de gids [Beveiligingsheaders voor websites](https://phpsos.de/nl/ratgeber/sicherheits-header/).

Daarnaast moeten sessiecookies het attribuut `HttpOnly` dragen, zodat scripts ze niet kunnen uitlezen. Hoe u sessies als geheel beveiligt, staat in de gids [Sessies in PHP beveiligen](https://phpsos.de/nl/ratgeber/session-sicherheit-php/). Een verwante zwakke plek aan de databasekant behandelt [SQL-injectie in PHP voorkomen](https://phpsos.de/nl/ratgeber/sql-injection-php/).

## Hoe helpt PHP SOS concreet?

- We controleren templates en uitvoer van uw toepassing op ontbrekende of verkeerde codering.
- We beoordelen vondsten naar bereikbaarheid en getroffen accounts en tonen concrete correcties.
- We voeren een Content Security Policy stapsgewijs in, zonder webshop- of betaalfuncties te verstoren.
- We testen na de correctie opnieuw en documenteren het resultaat.

Een onafhankelijke controle beschrijft de dienst [Beveiligingsaudit en penetratietest](https://phpsos.de/nl/leistungen/sicherheitsaudit-penetrationstest/).


## Veelgestelde vragen

**Volstaat htmlspecialchars tegen XSS?**  
Voor gewone tekst in HTML-inhoud en in attributen tussen aanhalingstekens wel, als u ENT_QUOTES en de tekenset UTF-8 opgeeft. In JavaScript, in URL's of in CSS gelden andere regels; daar is een eigen, passende codering nodig.

**Moet ik invoer bij het opslaan of bij de uitvoer filteren?**  
Codeer bij de uitvoer, passend bij de bestemming. Invoer moet u bij ontvangst op plausibiliteit controleren, bijvoorbeeld lengte en formaat. Wie alleen bij het opslaan filtert, mist gegevens uit andere bronnen en bemoeilijkt later gebruik in andere contexten.

**Wat levert een Content Security Policy op?**  
Een Content Security Policy legt vast uit welke bronnen de browser scripts mag laden en uitvoeren. Ze kan de gevolgen van een over het hoofd geziene XSS-kwetsbaarheid aanzienlijk beperken, maar vervangt geen correcte codering.

**Hoe sta ik opmaak zoals vet of links in reacties toe?**  
Gebruik een geteste bibliotheek voor HTML-opschoning met een whitelist van toegestane elementen en attributen. Eigen reguliere expressies om scripts te verwijderen zijn foutgevoelig en moet u niet gebruiken.

## Bronnen

- [OWASP Top 10](https://owasp.org/www-project-top-ten/)
- [OWASP Cheat Sheet Series](https://cheatsheetseries.owasp.org/)
- [php.net: beveiligingsmeldingen](https://www.php.net/)
