# XSS:n estäminen PHP:ssä: Cross-Site Scriptingin turvallinen torjunta

> XSS:n estäminen PHP:ssä: koodaa tulosteet oikein kunkin yhteyden mukaan, käytä automaattisen escapoinnin malleja ja ota käyttöön Content Security Policy. Esimerkein.

Lähde: https://phpsos.de/fi/ratgeber/xss-php/  
Tilanne: 25.10.2026  
Palveluntarjoaja: PHP SOS, PHP-hätäapu ja palvelimen tietoturva. Pikaapu WhatsAppilla: https://wa.me/491736494914?text=Hei%20PHP%20SOS%2C%20tarvitsemme%20apua%20PHP-palvelimemme%20kanssa.%20Yritys%3A%20%E2%80%A6%20Verkkosivusto%3A%20%E2%80%A6

**Lyhyesti:** XSS estetään PHP:ssä koodaamalla jokainen tuloste sen yhteyden mukaan: `htmlspecialchars()` HTML:lle, `json_encode()` suojavalitsimin JavaScriptille, tarkistetut URL-osoitteet linkeille. Automaattisesti escapoivat mallit hoitavat tästä suuren osan. Content Security Policy ja HttpOnly-evästeet rajoittavat vahinkoa, jos aukko kuitenkin jää.

## Miksi Cross-Site Scripting kannattaa tarkistaa nyt

Lokakuussa 2026 huomio on PHP2Shellissä, kahdessa kriittisessä haavoittuvuudessa PHP-tulkissa, joihin on ollut korjaukset 21.10.2026 alkaen. Moni yritys tarkistuttaa verkkosovelluksensa tässä yhteydessä. Se on hyvä tilaisuus katsoa myös oman koodin heikkouksia, joita mikään PHP-päivitys ei korjaa.

Cross-Site Scripting eli XSS on yksi niistä. OWASP Top 10 käsittelee XSS:ää Injection-luokassa. Toisin kuin palvelimen aukossa, hyökkäys kohdistuu tässä verkkosivustonne kävijöihin ja käyttäjiin, esimerkiksi verkkokaupan asiakkaisiin tai hallintapaneelin työntekijöihin.

Seuraukset voivat olla merkittäviä: ylläpitäjien kaapatut istunnot, manipuloidut lomakkeet, jotka lähettävät tietoja kolmansille, tai väärennetty sisältö verkkosivustollanne. Jos ylläpitäjän tili kaapataan, seurauksena voi olla sovelluksen täydellinen hallinta.

## Miten XSS syntyy PHP-sovelluksissa?

XSS syntyy, kun sovellus tulostaa sivulle tarkistamatta tietoja epäluotettavasta lähteestä. Selain ei enää erota sisältöänne lisätystä koodista ja suorittaa molemmat.

Muotoja on kolme. Heijastetussa XSS:ssä sisältö tulee suoraan nykyisestä pyynnöstä, esimerkiksi URL-osoitteen hakusanasta. Tallennetussa XSS:ssä se on tietokannassa, esimerkiksi kommentissa tai profiilikentässä, ja osuu jokaiseen, joka avaa sivun. DOM-pohjainen XSS syntyy selaimen JavaScriptissä, kun skriptit kirjoittavat URL-osoitteen arvoja sivulle.

Epäluotettavia ovat kaikki tiedot, jotka eivät ole kiinteästi koodissa: lomakekentät, URL-parametrit, evästeet, tiedostonimet, ulkoisten rajapintojen vastaukset ja oman tietokannan sisältö, joka on jossain vaiheessa tullut ulkopuolelta.

## Tulosteen oikea koodaus yhteyden mukaan

Tärkein sääntö kuuluu: koodatkaa tulostettaessa, ja sen paikan mukaan, johon arvo päätyy. HTML-sisällölle ja lainausmerkeissä oleville attribuuteille `htmlspecialchars()` on oikea väline. Pieni apufunktio estää asetusten unohtumisen:

```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'] ?? '') ?>">
```

Muut yhteydet vaativat muita keinoja:

| Yhteys | Turvallinen menetelmä |
|---|---|
| HTML-sisältö, lainausmerkeissä oleva attribuutti | `htmlspecialchars()` asetuksilla `ENT_QUOTES` ja UTF-8 |
| JavaScriptin tiedot | `json_encode()` asetuksilla `JSON_HEX_TAG`, `JSON_HEX_AMP`, `JSON_HEX_APOS`, `JSON_HEX_QUOT` |
| URL-osoitteen parametri | `rawurlencode()` yksittäiselle arvolle |
| Täydellinen URL-osoite käyttäjän syötteestä | Tarkista skeema, salli vain `https` ja `http`, sen jälkeen `htmlspecialchars()` |
| Attribuutit ilman lainausmerkkejä, tapahtumankäsittelijät, CSS | vältä, älä sijoita arvoja niihin |

Skriptin tarvitsemille tiedoille data-attribuutti tai JSON-lohko on yleensä siistein tapa:

```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>
```

JavaScriptissä luette arvon sitten data-attribuutin kautta ja asetatte sisällön `textContent`-ominaisuudella `innerHTML`-ominaisuuden sijaan.

## Mallit, HTML:n puhdistus ja viitekehykset

Mallimoottorit kuten Twig ja Blade koodaavat tulosteet oletuksena automaattisesti. Se sulkee pois suuren osan tyypillisistä virheistä. Vaaralliseksi tilanne muuttuu siellä, missä tämä käytös kytketään tietoisesti pois, esimerkiksi Twigin `raw`-suodattimella tai Bladen syntaksilla `{!! !!}`. Tällaisia kohtia pitäisi olla koodissa vähän, ja ne tulisi tarkistaa kohdennetusti jokaisessa katselmoinnissa.

Jos sovelluksenne sallii tietoisesti käyttäjien HTML:n, esimerkiksi muotoilut kommenteissa tai sisällönhallintajärjestelmässä, koodaus ei riitä, koska HTML:n on tarkoitus näkyä. Silloin tarvitsette tarkistetun puhdistuskirjaston kuten HTML Purifier, joka päästää läpi vain kiinteän luettelon sallittuja elementtejä ja attribuutteja. Säännöllisillä lausekkeilla tehdyt omat suodattimet ovat kokemuksen mukaan aukkoisia.

> **Tärkeää:**
> Tarkistakaa erityisesti hallintapuolen alueet. XSS näkymissä, jotka näkevät vain ylläpitäjät, aliarvioidaan usein, mutta se osuu juuri tileihin, joilla on eniten oikeuksia.

## XSS:n löytäminen vanhasta PHP-koodista

Vanhoissa sovelluksissa ensimmäinen kysymys ei yleensä ole, miten koodataan oikein, vaan missä se on toistaiseksi unohtunut. Käytännöllinen aloitus on haku kohtiin, joissa muuttujia tulostetaan suoraan:

```bash
# Pyyntötietojen suorat tulosteet
grep -rnE "(echo|print|<\?=)\s*\\\$_(GET|POST|REQUEST|COOKIE|SERVER)" src/ templates/ --include="*.php"
# Twigin ja Bladen automaattinen escapointi poistettu käytöstä
grep -rnE "\|raw|\{!!" templates/ resources/views/
```

Osumat ovat tarkistustehtäviä, eivät valmiita löydöksiä. Tallennetun XSS:n tällainen haku löytää vain osittain, koska arvot tulevat siellä tietokannasta. Tarkistakaa siksi lisäksi kaikki näkymät, jotka näyttävät käyttäjien sisältöä: kommentit, profiilit, tilausmuistiinpanot, tukitiketit. CMS-järjestelmissä ja verkkokaupoissa kolmansien osapuolten lisäosat ja teemat kuuluvat nimenomaisesti mukaan, sillä niissä on suuri osa tulostelogiikasta.

## Toinen puolustuslinja: CSP ja turvalliset evästeet

Huolellisessakin koodissa yksi kohta voi jäädä huomaamatta. Kaksi toimenpidettä rajoittaa silloin vahinkoa. Content Security Policy sallii selaimelle vain skriptit määritellyistä lähteistä ja kieltää upotetut skriptit, ellei niitä ole nimenomaisesti sallittu:

```apache
Header always set Content-Security-Policy "default-src 'self'; script-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'self'"
```

Ottakaa tällainen linjaus käyttöön vaiheittain, aluksi pelkässä raportointitilassa otsakkeella `Content-Security-Policy-Report-Only`, sillä analytiikka- tai maksupalveluiden nykyiset upotukset on yleensä lisättävä. Muita otsakkeita kuvaa opas [Verkkosivustojen tietoturvaotsakkeet](https://phpsos.de/fi/ratgeber/sicherheits-header/).

Lisäksi istuntoevästeillä pitäisi olla `HttpOnly`-määre, jotta skriptit eivät voi lukea niitä. Miten suojaatte istunnot kokonaisuudessaan, kerrotaan oppaassa [Istuntojen suojaaminen PHP:ssä](https://phpsos.de/fi/ratgeber/session-sicherheit-php/). Aiheeseen liittyvää heikkoutta tietokantapuolella käsittelee [SQL-injektion estäminen PHP:ssä](https://phpsos.de/fi/ratgeber/sql-injection-php/).

## Miten PHP SOS auttaa konkreettisesti?

- Tarkistamme sovelluksenne mallit ja tulosteet puuttuvan tai väärän koodauksen varalta.
- Arvioimme löydökset tavoitettavuuden ja koskettujen tilien mukaan ja näytämme konkreettiset korjaukset.
- Otamme Content Security Policyn käyttöön vaiheittain häiritsemättä verkkokaupan tai maksujen toimintoja.
- Testaamme korjauksen jälkeen uudelleen ja dokumentoimme tuloksen.

Riippumattoman tarkistuksen kuvaa palvelu [Tietoturva-auditointi ja penetraatiotesti](https://phpsos.de/fi/leistungen/sicherheitsaudit-penetrationstest/).


## Usein kysytyt kysymykset

**Riittääkö htmlspecialchars XSS:ää vastaan?**  
Tavalliselle tekstille HTML-sisällössä ja lainausmerkeissä olevissa attribuuteissa kyllä, kun annatte ENT_QUOTES-asetuksen ja merkistön UTF-8. JavaScriptissä, URL-osoitteissa tai CSS:ssä pätevät muut säännöt, ja siellä tarvitaan oma, sopiva koodaus.

**Pitäisikö syötteet suodattaa tallennettaessa vai tulostettaessa?**  
Koodatkaa tulostettaessa, kohteeseen sopivalla tavalla. Syötteiden uskottavuus kannattaa tarkistaa vastaanotettaessa, esimerkiksi pituus ja muoto. Joka suodattaa vain tallennettaessa, ohittaa muista lähteistä tulevat tiedot ja vaikeuttaa myöhempää käyttöä muissa yhteyksissä.

**Mitä hyötyä Content Security Policystä on?**  
Content Security Policy määrittää, mistä lähteistä selain saa ladata ja suorittaa skriptejä. Se voi rajoittaa huomaamatta jääneen XSS-aukon vaikutuksia selvästi, mutta ei korvaa oikeaa koodausta.

**Miten sallin muotoilut, kuten lihavoinnin tai linkit, kommenteissa?**  
Käyttäkää tarkistettua HTML-puhdistuskirjastoa, jossa on sallittujen elementtien ja attribuuttien luettelo. Omat säännölliset lausekkeet skriptien poistamiseen ovat virhealttiita, eikä niitä pitäisi käyttää.

## Lähteet

- [OWASP Top 10](https://owasp.org/www-project-top-ten/)
- [OWASP Cheat Sheet Series](https://cheatsheetseries.owasp.org/)
- [php.net: tietoturvatiedotteet](https://www.php.net/)
