Ajankohtainen tilanne: PHP2Shell. Kaksi kriittistä PHP-haavoittuvuutta (CVSS 9.2) sallii haittakoodin suorittamisen ilman kirjautumista. Korjaukset 21.10.2026 alkaen.Mitä nyt on tehtävä →

Local File Inclusionin estäminen PHP:ssä: LFI ja RFI

Kun include tai require ottaa polun pyynnöstä, hyökkääjät voivat ladata vieraita tiedostoja. Näin rakennatte sivunvalinnan ja mallit turvallisesti ja suojaatte palvelimen lisäksi.

Tilanne Lukuaika 4 minOppaat

Lyhyesti: Local File Inclusion syntyy PHP:ssä, kun include, require tai tiedostofunktiot käyttävät polkua, johon kävijä voi vaikuttaa. Turvallinen ratkaisu on kiinteä sallittujen luettelo: parametri valitsee vain tunnetuista arvoista, polku itse ei koskaan tule pyynnöstä. Palvelinpuolella allow_url_include=Off, open_basedir ja rajoittavat tiedosto-oikeudet täydentävät suojaa.

Mitä Local File Inclusion on PHP:ssä?

Monet vanhemmat PHP-sovellukset lataavat sivut yksinkertaisella mallilla: osoitteen parametri määrää, mikä tiedosto sisällytetään. Jos tämä parametri välitetään tarkistamatta include-käskylle, hyökkääjä voi pyytää palvelimelta muita tiedostoja. Vähemmän vakavassa tapauksessa hän lukee asetustiedostoja, joissa on tunnuksia. Pahemmassa tapauksessa hän saa PHP:n suorittamaan sisältöä, jonka hän on aiemmin tallentanut muualle, esimerkiksi lataukseen tai lokitiedostoon.

Johdolle tärkeää: tämä aukko on harvoin CMS:n ytimessä vaan useimmiten omissa kehitystöissä, vanhoissa laajennuksissa tai nopeasti tehdyissä lisäsivuissa. Arjessa se ei näy, koska verkkosivusto toimii normaalisti.

Remote File Inclusionissa PHP lataa koodin vieraasta osoitteesta. Se edellyttää, että allow_url_include on käytössä. Nykyisissä PHP-versioissa tämä asetus on oletuksena pois päältä, mutta vanhoilla tai väärin konfiguroiduilla palvelimilla se vielä tavataan.

Tyypilliset haavoittuvat mallit koodissa

Vaarassa ovat kohdat, joissa pyynnön, evästeiden tai tietokannan arvo muuttuu osaksi tiedostopolkua. Yleisiä esimerkkejä ovat kielenvaihtimet, jotka lataavat kielitiedoston parametrin mukaan, sivunvalinta osoitteen parametrilla, mallin tai teeman valinta sekä asiakirjojen lataussomakkeet. Polun loppuun lisätty .php ei suojaa luotettavasti, koska hyökkääjät voivat siirtyä olemassa oleviin PHP-tiedostoihin.

Ensimmäisen yleiskuvan saatte hakemalla sisällytyksiä, joissa on muuttujia. Se tuottaa myös harmittomia osumia, mutta näyttää, mihin on katsottava tarkemmin:

grep -rnE --include='*.php' '(include|require)(_once)?\s*\(?\s*\$' /var/www/projekt \
  | grep -v '/vendor/'

Tarkistakaa jokaisesta osumasta, mistä muuttuja tulee ja voiko kävijä vaikuttaa siihen.

Turvallinen malli: sallittujen luettelo pyynnön polun sijaan

Luotettavin suoja on, että kävijä valitsee vain avaimen ja sovellus päättää itse, mikä tiedosto siihen kuuluu. Polkua ei koskaan koota syötteistä.

<?php
declare(strict_types=1);

$seiten = [
    'start'      => __DIR__ . '/../vorlagen/start.php',
    'leistungen' => __DIR__ . '/../vorlagen/leistungen.php',
    'kontakt'    => __DIR__ . '/../vorlagen/kontakt.php',
];

$wahl = is_string($_GET['seite'] ?? null) ? $_GET['seite'] : 'start';

if (!array_key_exists($wahl, $seiten)) {
    http_response_code(404);
    $wahl = 'start';
}

require $seiten[$wahl];

Tämä malli on lyhyt, helppo tarkistaa eikä jätä liikkumavaraa. Nykyaikaisissa viitekehyksissä reititin hoitaa tämän tehtävän; niissäkään ohjaimia ja malleja ei pitäisi koskaan määrittää vapaista parametreista.

Kun tiedostonimien on oltava dynaamisia: realpath ja perushakemisto

Joskus kiinteä luettelo ei ole käytännöllinen, esimerkiksi asiakirjakansion latauksissa. Silloin tarkistatte ratkaistun polun kiinteää perushakemistoa vasten ja sallitte vain yksinkertaiset tiedostonimet.

<?php
declare(strict_types=1);

const BASIS = '/var/www/daten/dokumente';

function sichererPfad(string $name): string
{
    if (!preg_match('/^[a-z0-9_-]{1,64}\.pdf$/', $name)) {
        throw new InvalidArgumentException('Ungültiger Dateiname.');
    }

    $basis = realpath(BASIS);
    $pfad  = realpath($basis . DIRECTORY_SEPARATOR . $name);

    if ($basis === false || $pfad === false
        || !str_starts_with($pfad, $basis . DIRECTORY_SEPARATOR)) {
        throw new RuntimeException('Datei nicht gefunden.');
    }

    return $pfad;
}

// Toimita sisällyttämisen sijaan: readfile() ei suorita koodia.
$datei = sichererPfad((string) ($_GET['datei'] ?? ''));
header('Content-Type: application/pdf');
header('Content-Disposition: attachment; filename="' . basename($datei) . '"');
readfile($datei);

Kaksi seikkaa on ratkaiseva. Säännöllinen lauseke ei salli hakemistoerottimia eikä erikoismerkkejä. Ja tiedostot, jotka vain toimitetaan, luetaan readfile()-funktiolla, ei include-käskyllä, jotta mukana oleva koodi ei koskaan suoriteta. str_starts_with() on käytettävissä PHP 8.0:sta alkaen.

Palvelinpuolen suojaus: php.ini, open_basedir ja oikeudet

Turvallinen koodi on ensimmäinen puolustuslinja. Palvelimen asetukset huolehtivat siitä, että huomaamatta jäänyt virhe tekee vähemmän vahinkoa.

; php.ini tai FPM-pooli (php_admin_value / php_admin_flag)
allow_url_include = Off
allow_url_fopen   = Off   ; vain jos sovellus ei lataa ulkoisia URL-osoitteita
open_basedir      = /var/www/kunde-a/:/tmp/kunde-a/
display_errors    = Off
log_errors        = On

Jotkin sovellukset tarvitsevat allow_url_fopen-asetusta päivityksiin tai rajapintoihin. Kokeilkaa sen poistamista käytöstä ensin testiympäristössä. open_basedir rajaa PHP:n verkkosivuston omiin hakemistoihin, mikä on erityisen tärkeää palvelimilla, joilla on useita asiakkaita. Yksityiskohdat ovat oppaissa open_basedir ja sivustokohtainen eristys ja php.ini turvallisesti.

Lisäksi lataus- ja välimuistihakemistojen ei pitäisi olla suoritettavia, eikä lokitiedostoja pitäisi sijaita web-juuressa. Silloin hyökkääjältä puuttuvat paikat, joihin hän voisi tallentaa oman sisällön sisällytettäväksi.

Mistä tunnistatte hyväksikäyttöyritykset lokeista

File Inclusionin hyväksikäyttöyritykset jättävät yleensä jälkiä pääsylokiin. Huomiota herättävät parametrit, jotka sisältävät hakemistonvaihtoja, absoluuttisia järjestelmäpolkuja, stream-wrappereita tai täydellisiä vieraita osoitteita, etenkin parametreissa kuten page, file, template tai lang. Yksittäinen osuma on yleensä automaattista skannausta. Tärkeää on, vastattiinko tällaiseen pyyntöön tilakoodilla 200 ja epätavallisella vastauksen koolla.

Miten etenette järjestelmällisesti, kuvataan oppaassa Verkkopalvelimen lokien analysointi hyökkäyksen jälkeen. Jos löydätte merkkejä onnistuneista pääsyistä, olettakaa, että asetustiedostoja on luettu, ja uusikaa niissä olevat tunnukset.

Tähän kuuluvat ennen kaikkea tietokannan salasanat, maksu- ja toimituspalveluiden avaimet, SMTP-tunnukset ja CMS:n salaiset avaimet. Tarkistakaa lisäksi, onko ensimmäisen epäilyttävän pääsyn jälkeen syntynyt uusia tiedostoja. Joka on voinut lukea tiedostoja, on ehkä yrittänyt myös tallentaa omia. Silloin kyse ei ole enää vain koodin aukosta vaan tietoturvapoikkeamasta, jota on käsiteltävä rakenteellisesti.

Korjausten pysyvä varmistaminen

Korjauksen jälkeen kannattaa varmistaa suojatoiminto automaattisilla testeillä. Yksinkertainen testi antaa sivunvalinnalle tuntemattomia arvoja, hakemistonvaihtoja ja tyhjiä syötteitä ja odottaa kaikissa tapauksissa oletussivua tai virhettä 404. Tällaiset testit estävät myöhempää muutosta avaamasta aukkoa huomaamatta uudelleen.

Johdolle ja projektivastaaville hyödyllinen kysymys toimistolle: onko sovelluksessa kohtia, joissa tiedostoja ladataan parametrien perusteella, ja miten ne on suojattu? Konkreettinen vastaus löytökohtineen ja testeineen on hyvä merkki.

Miten PHP SOS auttaa konkreettisesti?

  • Etsimme koodistanne kohdennetusti include-, require- ja tiedostofunktioita, joissa on pyynnön syötteitä, ja arvioimme jokaisen löydön.
  • Asetamme allow_url_include-, open_basedir- ja sopivat oikeudet verkkosivustoittain ja tarkistamme, että sovellus toimii sen jälkeen edelleen.
  • Analysoimme lokit hyväksikäyttöyritysten varalta ja selvitämme, onko tunnukset uusittava.
  • Riippumatonta tarkistusta varten tarjoamme PHP-sovelluksille tietoturva-auditoinnin penetraatiotestillä.

Usein kysytyt kysymykset

Mikä ero on LFI:llä ja RFI:llä?

Local File Inclusionissa sovellus lataa tiedoston, joka on jo palvelimella mutta jota ei pitäisi ladata. Remote File Inclusionissa koodi ladataan vieraasta osoitteesta. RFI edellyttää turvatonta PHP-asetusta, LFI ei.

Riittääkö, että ../ poistetaan parametrista?

Ei. Tällaiset suodattimet voi usein kiertää koodauksella tai muunnelmilla, ja ne ohittavat absoluuttiset polut. Turvallista on vain sallittujen arvojen kiinteä kohdistus tiedostoihin tai ratkaistun polun tarkistus perushakemistoa vasten.

Suojaako open_basedir täysin File Inclusionilta?

Ei, se rajoittaa vain hakemistoja, joihin PHP saa päästä. Näiden rajojen sisällä aukko on yhä hyödynnettävissä. open_basedir on lisäsuojakerros eikä korvaa turvallista koodia.

Koskeeko LFI myös CMS-järjestelmiä kuten WordPress?

Yleisten CMS-järjestelmien ytimen katsotaan olevan tässä kohdassa hyvin tarkistettu. Aukkoja esiintyy todennäköisemmin laajennuksissa, teemoissa ja omissa kehitystöissä. Laajennusten päivitykset ja niiden tietoturvailmoitusten seuraaminen ovat siksi tärkeitä.

Lähteet

Pikayhteys

Jokainen tunti on tärkeä. Kirjoittakaa meille nyt.

Kuvatkaa lyhyesti järjestelmä, PHP-versio ja havainnot. Otamme yhteyttä, selvitämme tilanteen ja työskentelemme etänä, kunnes tapaus on käsitelty loppuun ja dokumentoitu.

Pikaapu