Kort sagt: XSS i PHP forhindrer I ved at kode hvert output passende til sin kontekst: htmlspecialchars() til HTML, json_encode() med beskyttelsesindstillinger til JavaScript, kontrollerede URL'er til links. Skabeloner med automatisk escaping tager meget af det fra jer. En Content Security Policy og HttpOnly-cookies begrænser skaden, hvis en sårbarhed alligevel består.
Hvorfor cross-site scripting bør kontrolleres med nu
Opmærksomheden ligger i oktober 2026 på PHP2Shell, to kritiske sårbarheder i PHP-fortolkeren, som der siden 21.10.2026 findes patches til. Mange virksomheder lader i den forbindelse deres webapplikationer gennemgå. Det er en god anledning til også at se på svagheder i egen kode, som ingen PHP-opdatering udbedrer.
Cross-site scripting, kort XSS, er en af dem. OWASP Top 10 fører XSS inden for kategorien Injection. I modsætning til en sårbarhed i serveren rettes angrebet her mod besøgende og brugere af jeres website, f.eks. mod kunder i shoppen eller medarbejdere i backend.
Følgerne kan være betydelige: overtagne sessioner hos administratorer, manipulerede formularer, der sender data til tredjepart, eller forfalsket indhold på jeres website. Bliver en administratorkonto overtaget, kan det føre til fuldstændig kontrol over applikationen.
Hvordan opstår XSS i PHP-applikationer?
XSS opstår, når en applikation udskriver data fra en ikke-troværdig kilde ukontrolleret på en side. Browseren kan så ikke længere skelne mellem jeres indhold og indsmuglet kode og udfører begge dele.
Man skelner mellem tre former. Ved reflekteret XSS stammer indholdet direkte fra den aktuelle forespørgsel, f.eks. fra et søgeord i URL'en. Ved gemt XSS ligger det i databasen, for eksempel i en kommentar eller et profilfelt, og rammer enhver, der kalder siden. DOM-baseret XSS opstår i browserens JavaScript, når scripts skriver værdier fra URL'en ind på siden.
Ikke-troværdige er alle data, der ikke står fast i koden: formularfelter, URL-parametre, cookies, filnavne, svar fra eksterne grænseflader og indhold fra egen database, som på et tidspunkt kom udefra.
Kod output rigtigt efter kontekst
Den vigtigste regel lyder: Kod ved udlæsningen, og passende til det sted, hvor værdien havner. Til HTML-indhold og attributter i anførselstegn er htmlspecialchars() det rette middel. En lille hjælpefunktion forhindrer, at indstillinger glemmes:
<?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'] ?? '') ?>">
Andre kontekster kræver andre midler:
| Kontekst | Sikker metode |
|---|---|
| HTML-indhold, attribut i anførselstegn | htmlspecialchars() med ENT_QUOTES og UTF-8 |
| Data til JavaScript | json_encode() med JSON_HEX_TAG, JSON_HEX_AMP, JSON_HEX_APOS, JSON_HEX_QUOT |
| Parameter i en URL | rawurlencode() for den enkelte værdi |
| Fuldstændig URL fra brugerinput | Kontrollér skemaet, tillad kun https og http, derefter htmlspecialchars() |
| Attributter uden anførselstegn, event handlers, CSS | undgå, sæt ikke værdier ind dér |
Til data, som et script har brug for, er en dataattribut eller en JSON-blok som regel den reneste vej:
<?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>
I JavaScript læser I derefter værdien ud via dataattributten og sætter indhold med textContent i stedet for med innerHTML.
Skabeloner, HTML-rensning og frameworks
Skabelonmotorer som Twig og Blade koder output automatisk som standard. Det udelukker en stor del af de typiske fejl. Farligt bliver det dér, hvor denne adfærd bevidst slås fra, f.eks. med filteret raw i Twig eller syntaksen {!! !!} i Blade. Sådanne steder bør være sjældne i koden og målrettet kontrolleres ved hver gennemgang.
Skal jeres applikation bevidst tillade HTML fra brugere, f.eks. formateringer i kommentarer eller i et redaktionssystem, er kodning ikke nok, fordi HTML'en jo skal vises. Så har I brug for et gennemprøvet rensebibliotek som HTML Purifier, der kun lader en fast liste af tilladte elementer og attributter igennem. Egne filtre med regulære udtryk er erfaringsmæssigt hullede.
Kontrollér især backend-områder. XSS i visninger, som kun administratorer ser, undervurderes ofte, men rammer netop de konti med flest rettigheder.
Find XSS i eksisterende PHP-kode
I vokset kode er det første spørgsmål som regel ikke, hvordan man koder rigtigt, men hvor det hidtil er blevet glemt. En pragmatisk start er søgningen efter steder, hvor variabler udskrives direkte:
# Direkte udskrivning af forespørgselsdata
grep -rnE "(echo|print|<\?=)\s*\\\$_(GET|POST|REQUEST|COOKIE|SERVER)" src/ templates/ --include="*.php"
# Slået fra auto-escaping i Twig og Blade
grep -rnE "\|raw|\{!!" templates/ resources/views/
Fund er kontrolopgaver, ikke færdige resultater. Gemt XSS finder sådan en søgning kun delvist, fordi værdierne dér kommer fra databasen. Kontrollér derfor desuden alle visninger, der viser indhold fra brugere: kommentarer, profiler, ordrenoter, supporttickets. Ved CMS og shops hører plugins og temaer fra tredjepart udtrykkeligt med, for dér sidder en stor del af udlæsningslogikken.
Anden forsvarslinje: CSP og sikre cookies
Også ved omhyggelig kode kan et sted blive overset. To foranstaltninger begrænser da skaden. En Content Security Policy tillader kun browseren scripts fra fastlagte kilder og forbyder indlejrede scripts, medmindre de er udtrykkeligt frigivet:
Header always set Content-Security-Policy "default-src 'self'; script-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'self'"
Indfør sådan en politik trinvis, først i ren rapporttilstand via Content-Security-Policy-Report-Only, for eksisterende integrationer af analyse- eller betalingstjenester skal som regel suppleres. Yderligere headere beskriver vejledningen Sikkerhedsheadere til websites.
Desuden bør sessionscookies bære attributten HttpOnly, så scripts ikke kan læse dem. Hvordan I sikrer sessioner samlet set, står i vejledningen Sikr sessioner i PHP. En beslægtet svaghed på databasesiden behandler Forhindr SQL-injection i PHP.
Hvordan hjælper PHP SOS konkret?
- Vi gennemgår skabeloner og output i jeres applikation for manglende eller forkert kodning.
- Vi vurderer fund efter tilgængelighed og berørte konti og viser konkrete rettelser.
- Vi indfører en Content Security Policy trinvis uden at forstyrre shop- eller betalingsfunktioner.
- Vi tester igen efter rettelsen og dokumenterer resultatet.
En uafhængig kontrol beskriver ydelsen Sikkerhedsaudit og penetrationstest.
Ofte stillede spørgsmål
Er htmlspecialchars nok mod XSS?
Til almindelig tekst i HTML-indhold og i attributter i anførselstegn ja, hvis I angiver ENT_QUOTES og tegnsættet UTF-8. I JavaScript, i URL'er eller i CSS gælder andre regler, dér kræves en egen, passende kodning.
Skal jeg filtrere input ved lagring eller ved udlæsning?
Kod ved udlæsningen, passende til målet. Input bør I ved modtagelsen kontrollere for plausibilitet, f.eks. længde og format. Den, der kun filtrerer ved lagring, overser data fra andre kilder og besværliggør senere brug i andre kontekster.
Hvad kan en Content Security Policy?
En Content Security Policy fastlægger, fra hvilke kilder browseren må indlæse og udføre scripts. Den kan tydeligt begrænse følgerne af en overset XSS-sårbarhed, men erstatter ikke korrekt kodning.
Hvordan tillader jeg formateringer som fed eller links i kommentarer?
Brug et gennemprøvet HTML-rensebibliotek med en hvidliste over tilladte elementer og attributter. Egne regulære udtryk til at fjerne scripts er fejlbehæftede og bør ikke bruges.