Actuele situatie: PHP2Shell. Twee kritieke PHP-kwetsbaarheden (CVSS 9.2) maken schadelijke code zonder inloggen mogelijk. Patches sinds 21-10-2026.Wat u nu moet doen →

Local file inclusion in PHP voorkomen: LFI en RFI

Als include of require paden uit het verzoek overnemen, kunnen aanvallers vreemde bestanden laden. Zo bouwt u paginakeuze en sjablonen veilig en beveiligt u de server aanvullend.

Stand 6 min. leestijdGidsen

Kort gezegd: Local file inclusion in PHP ontstaat wanneer include, require of bestandsfuncties een pad gebruiken dat de bezoeker kan beïnvloeden. De veilige oplossing is een vaste toegestane lijst: de parameter kiest alleen uit bekende waarden, het pad zelf komt nooit uit het verzoek. Aan serverzijde vullen allow_url_include=Off, open_basedir en restrictieve bestandsrechten de bescherming aan.

Wat is local file inclusion in PHP?

Veel oudere PHP-toepassingen laden pagina's volgens een eenvoudig patroon: een parameter in het adres bepaalt welk bestand wordt ingesloten. Wordt die parameter ongecontroleerd aan include doorgegeven, dan kan een aanvaller andere bestanden op de server opvragen. In het onschuldigere geval leest hij configuratiebestanden met toegangsgegevens. In het ergere geval laat hij PHP inhoud uitvoeren die hij eerder elders heeft neergezet, bijvoorbeeld in een upload of een logbestand.

Voor de directie is belangrijk: deze kwetsbaarheid zit zelden in de CMS-kern, maar meestal in maatwerk, oude extensies of snel gebouwde extra pagina's. In het dagelijks gebruik valt ze niet op, omdat de website normaal functioneert.

Bij remote file inclusion laadt PHP code van een vreemd adres. Dat vereist dat allow_url_include is ingeschakeld. In actuele PHP-versies staat deze instelling standaard uit, maar op oude of verkeerd geconfigureerde servers komt ze nog voor.

Typische kwetsbare patronen in de code

Gevaar bestaat op plekken waar een waarde uit het verzoek, uit cookies of uit de database deel wordt van een bestandspad. Veelvoorkomende voorbeelden zijn taalschakelaars die een taalbestand laden op grond van een parameter, paginakeuze via een parameter in het adres, keuze van sjablonen of thema's en downloadscripts voor documenten. Een aan het einde van het pad toegevoegde .php beschermt daarbij niet betrouwbaar, omdat aanvallers kunnen uitwijken naar bestaande PHP-bestanden.

Een eerste overzicht krijgt u met een zoekopdracht naar insluitingen die variabelen bevatten. Ze levert ook onschuldige treffers op, maar toont waar nauwkeuriger moet worden gekeken:

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

Controleer bij elke treffer waar de variabele vandaan komt en of een bezoeker haar kan beïnvloeden.

Het veilige patroon: toegestane lijst in plaats van pad uit het verzoek

De betrouwbaarste bescherming is dat de bezoeker alleen een sleutel kiest en de toepassing zelf beslist welk bestand daarbij hoort. Het pad wordt nooit uit invoer samengesteld.

<?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];

Dit patroon is kort, gemakkelijk te controleren en laat geen speelruimte. In moderne frameworks neemt de router deze taak over; ook daar mogen controllers en sjablonen nooit uit vrije parameters worden bepaald.

Als bestandsnamen dynamisch moeten zijn: realpath en basismap

Soms is een vaste lijst niet praktisch, bijvoorbeeld bij downloads uit een documentenmap. Controleer dan het opgeloste pad tegen een vaste basismap en laat alleen eenvoudige bestandsnamen toe.

<?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;
}

// Uitleveren in plaats van insluiten: readfile() voert geen code uit.
$datei = sichererPfad((string) ($_GET['datei'] ?? ''));
header('Content-Type: application/pdf');
header('Content-Disposition: attachment; filename="' . basename($datei) . '"');
readfile($datei);

Twee punten zijn beslissend. De reguliere expressie staat geen mapscheidingstekens en geen speciale tekens toe. En bestanden die alleen moeten worden uitgeleverd, worden met readfile() gelezen, niet met include, zodat erin aanwezige code nooit wordt uitgevoerd. str_starts_with() is beschikbaar vanaf PHP 8.0.

Beveiliging aan serverzijde: php.ini, open_basedir en rechten

Veilige code is de eerste verdedigingslinie. De serverconfiguratie zorgt ervoor dat een over het hoofd geziene fout minder schade aanricht.

; php.ini of FPM-pool (php_admin_value / php_admin_flag)
allow_url_include = Off
allow_url_fopen   = Off   ; alleen als de toepassing geen externe URL's laadt
open_basedir      = /var/www/kunde-a/:/tmp/kunde-a/
display_errors    = Off
log_errors        = On

allow_url_fopen is voor sommige toepassingen nodig voor updates of koppelingen. Test het uitschakelen eerst in een testomgeving. open_basedir beperkt PHP tot de eigen mappen van de website, wat vooral belangrijk is op servers met meerdere klanten. Details staan in de gidsen open_basedir en isolatie per website en php.ini veilig configureren.

Aanvullend moeten upload- en cachemappen niet uitvoerbaar zijn en logbestanden niet in de webroot staan. Zo ontbreken voor een aanvaller de plekken waar hij eigen inhoud voor een insluiting zou kunnen neerzetten.

Waaraan u pogingen tot misbruik in logs herkent

Pogingen om een file inclusion uit te buiten laten meestal sporen na in het toegangslog. Opvallend zijn parameters die mapwisselingen, absolute systeempaden, stream-wrappers of volledige vreemde adressen bevatten, vooral bij parameters als page, file, template of lang. Een enkele treffer is meestal geautomatiseerd scannen. Belangrijk is of zo'n verzoek werd beantwoord met statuscode 200 en een ongebruikelijke antwoordgrootte.

Hoe u daarbij systematisch te werk gaat, beschrijft Webserverlogs na een aanval analyseren. Vindt u aanwijzingen voor geslaagde toegang, ga er dan van uit dat configuratiebestanden zijn gelezen en vernieuw de daarin opgenomen toegangsgegevens.

Daartoe horen vooral databasewachtwoorden, sleutels voor betaal- en verzenddiensten, SMTP-toegangen en de geheime sleutels van het CMS. Controleer bovendien of er sinds de eerste verdachte toegang nieuwe bestanden zijn ontstaan. Wie bestanden kon lezen, heeft mogelijk ook geprobeerd eigen bestanden neer te zetten. In dat geval gaat het niet meer alleen om een kwetsbaarheid in de code, maar om een beveiligingsincident dat gestructureerd moet worden afgehandeld.

Correcties duurzaam borgen

Na een correctie loont het om de beschermfunctie met geautomatiseerde tests te borgen. Een eenvoudige test geeft de paginakeuze onbekende waarden, mapwisselingen en lege invoer mee en verwacht in alle gevallen de standaardpagina of een fout 404. Zulke tests voorkomen dat een latere wijziging de kwetsbaarheid ongemerkt weer opent.

Voor directie en projectverantwoordelijken is een vraag aan het bureau nuttig: zijn er in de toepassing plekken waar bestanden op grond van parameters worden geladen, en hoe zijn die beveiligd? Een concreet antwoord met vindplaatsen en tests is een goed teken.

Hoe helpt PHP SOS concreet?

  • We zoeken in uw code gericht naar include, require en bestandsfuncties met invoer uit het verzoek en beoordelen elke vondst.
  • We stellen allow_url_include, open_basedir en passende rechten per website in en controleren dat de toepassing daarna blijft werken.
  • We analyseren logs op pogingen tot misbruik en bepalen of toegangsgegevens moeten worden vernieuwd.
  • Voor een onafhankelijke controle bieden we een beveiligingsaudit met penetratietest voor PHP-toepassingen aan.

Veelgestelde vragen

Wat is het verschil tussen LFI en RFI?

Bij local file inclusion laadt de toepassing een bestand dat al op de server staat, maar niet geladen mag worden. Bij remote file inclusion wordt code van een vreemd adres nageladen. RFI veronderstelt een onveilige PHP-instelling, LFI niet.

Volstaat het om ../ uit de parameter te verwijderen?

Nee. Zulke filters zijn vaak te omzeilen via codering of varianten en missen absolute paden. Veilig is alleen een vaste toewijzing van toegestane waarden aan bestanden of een controle van het opgeloste pad tegen een basismap.

Beschermt open_basedir volledig tegen file inclusion?

Nee, het beperkt alleen de mappen waartoe PHP toegang heeft. Binnen die grenzen blijft een kwetsbaarheid uitbuitbaar. open_basedir is een extra beschermlaag en vervangt geen veilige code.

Zijn CMS'en als WordPress door LFI getroffen?

De kern van gangbare CMS'en geldt op dit punt als goed gecontroleerd. Kwetsbaarheden duiken eerder op in extensies, thema's en maatwerk. Updates van de extensies en een blik op hun beveiligingsmeldingen zijn daarom belangrijk.

Bronnen

Direct contact

Elk uur telt. Schrijf ons nu.

Beschrijf kort het systeem, de PHP-versie en wat u opmerkt. Wij nemen contact op, brengen de situatie in kaart en werken op afstand tot het incident is afgerond en gedocumenteerd.

Directe hulp