Kort gezegd: Invoer in PHP valideert u met filter_var() of filter_input_array() en duidelijke regels per veld: type, bereik, lengte en toegestane waarden. Wat niet past, wordt afgewezen en niet stilzwijgend omgebouwd. Validatie is belangrijk voor schone gegevens en minder aanvalsoppervlak, maar vervangt noch prepared statements tegen SQL-injectie, noch contextgerichte codering tegen XSS.
Waarom "alles filteren" geen concept is
In veel oudere PHP-projecten staat aan het begin van elk bestand een lus die alle waarden uit $_GET en $_POST door een opschoonfunctie stuurt. Dat voelt veilig. In de praktijk verwijdert het soms te veel, bijvoorbeeld apostrofs in achternamen, en soms te weinig, omdat dezelfde functie tegelijk voor SQL, HTML en bestandsnamen moet gelden.
Zinvolle validatie werkt anders: ze vraagt voor elk veld wat daar is toegestaan. Een bestelhoeveelheid is een geheel getal tussen 1 en een bovengrens. Een sorteerveld is een van drie vaste waarden. Een datum heeft een bepaald formaat en ligt na een andere datum. Al het andere wordt afgewezen.
OWASP onderscheidt daarbij twee niveaus: de syntactische controle of een waarde de juiste vorm heeft en de semantische controle of ze in de context zinvol is. Beide horen op de server; controles in de browser dienen alleen voor het gebruiksgemak.
Welke filters PHP meebrengt
De filterextensie is standaard in PHP opgenomen. De belangrijkste validatiefilters volgens de PHP-handleiding:
| Filter | Controleert | Belangrijke opties en opmerkingen |
|---|---|---|
FILTER_VALIDATE_INT |
geheel getal | min_range, max_range |
FILTER_VALIDATE_FLOAT |
kommagetal | decimal voor het scheidingsteken |
FILTER_VALIDATE_BOOL |
waarheidswaarde | met FILTER_NULL_ON_FAILURE te onderscheiden van "onwaar" |
FILTER_VALIDATE_EMAIL |
vorm van een e-mailadres | bewijst niet dat de mailbox bestaat |
FILTER_VALIDATE_URL |
vorm van een URL | schema zelf controleren, geen geïnternationaliseerde domeinen |
FILTER_VALIDATE_IP |
IP-adres | vlaggen voor privé- en gereserveerde bereiken |
FILTER_VALIDATE_REGEXP |
eigen patroon | patroon verankeren met ^ en $ |
Het opschoonfilter FILTER_SANITIZE_STRING is sinds PHP 8.1 verouderd. Wie het nog gebruikt, moet de validatie omzetten naar passende filters en voor de uitvoer htmlspecialchars() gebruiken.
filter_var in PHP: een formulier netjes controleren
Met filter_input_array() kan een heel formulier in één stap tegen een definitie worden gecontroleerd. Onbekende velden worden daarbij niet overgenomen:
<?php
declare(strict_types=1);
$regeln = [
'menge' => ['filter' => FILTER_VALIDATE_INT,
'options' => ['min_range' => 1, 'max_range' => 50]],
'email' => FILTER_VALIDATE_EMAIL,
'agb' => ['filter' => FILTER_VALIDATE_BOOL, 'flags' => FILTER_NULL_ON_FAILURE],
'versand' => ['filter' => FILTER_VALIDATE_REGEXP,
'options' => ['regexp' => '/^(standard|express)$/']],
];
$daten = filter_input_array(INPUT_POST, $regeln) ?? [];
$fehler = [];
foreach ($regeln as $feld => $_) {
if (!array_key_exists($feld, $daten) || $daten[$feld] === false || $daten[$feld] === null) {
$fehler[] = $feld;
}
}
if ($fehler !== []) {
http_response_code(422);
// Foutmelding per veld tonen, invoer niet verwerken
}
Let op de strikte vergelijkingen met ===: een geldige waarde 0 of een lege string mag niet met een mislukking worden verward. Waarom losse vergelijkingen hier gevaarlijk zijn, legt Type juggling: losse vergelijkingen als beveiligingsrisico uit. Hoe PHP aanvraaggegevens überhaupt beschikbaar stelt, beschrijft Superglobals: $_GET, $_POST, $_FILES en meer.
Valkuilen bij e-mail, URL en vrije tekst
Sommige filters doen minder dan hun naam doet vermoeden:
- URL's:
FILTER_VALIDATE_URLaccepteert ook andere schema's dan http en https. Controleer het schema daarna metparse_url()tegen een vaste lijst. Wordt de URL door de server opgehaald, dan gelden aanvullende regels, zie Server-side request forgery in PHP voorkomen. - E-mailadressen: Het filter controleert de vorm, niet het bezit. Wie zeker moet zijn, stuurt een bevestigingslink.
- Vrije tekst: Namen, berichten en adressen laten zich nauwelijks zinvol tot bepaalde tekens beperken. Controleer lengte en tekencodering, bijvoorbeeld met
mb_check_encoding($wert, 'UTF-8'), en vertrouw bij de uitvoer op codering. - Reguliere expressies: Zonder
^en$controleert een patroon alleen of het ergens in de tekst voorkomt, niet of de hele waarde past. - Arrays in plaats van strings: Een aanvaller kan in plaats van een tekst een array sturen. Zonder typecontrole leidt dat tot waarschuwingen of onverwacht gedrag.
Voor vaste keuzewaarden zoals sortering, taal of verzendwijze is een lijst met toegestane waarden met in_array($wert, $erlaubt, true) of een match-expressie eenvoudiger en strenger dan welke reguliere expressie ook.
Een andere veelvoorkomende fout betreft getallen met betekenis, zoals prijzen, aantallen of kortingen. Dat een waarde een geldig getal is, zegt niets over de vraag of ze in de context is toegestaan. Een negatief aantal, een korting boven de waarde van de goederen of een prijs die uit het formulier in plaats van uit de database wordt overgenomen, zijn klassieke logische fouten. Prijzen, machtigingen en statuswaarden horen daarom altijd op de server en nooit in verborgen formuliervelden.
JSON, webhooks en imports: validatie voorbij formulieren
Veel invoer is tegenwoordig geen formulier meer, maar JSON-verzoeken van apps, webhooks van betaaldiensten of importbestanden van leveranciers. Ook daar geldt: eerst veilig inlezen, dan controleren. OWASP adviseert grootte en nestingdiepte te beperken voordat gegevens überhaupt worden ingelezen. In PHP neemt json_decode() met een maximale diepte en JSON_THROW_ON_ERROR een deel daarvan over:
<?php
$roh = file_get_contents('php://input', false, null, 0, 65536); // maximaal 64 KB
try {
$daten = json_decode($roh, true, 16, JSON_THROW_ON_ERROR);
} catch (JsonException) {
http_response_code(400);
exit;
}
if (!is_array($daten) || !is_string($daten['bestellnummer'] ?? null)) {
http_response_code(422);
exit;
}
Bij webhooks komt vóór de inhoudelijke controle de vraag of het bericht werkelijk van de opgegeven dienst afkomstig is. Biedt de dienst een handtekening aan, controleer die dan en vergelijk haar met hash_equals(). Bij importbestanden controleert u elke regel als een formulier en breekt u bij fouten netjes af, in plaats van halve gegevensrecords over te nemen.
Foutmeldingen moeten het betrokken veld noemen, maar de afgewezen waarde niet ongefilterd teruggeven. Anders ontstaat bij de foutmelding zelf een nieuw uitvoerpunt, dat opnieuw moet worden gecodeerd.
Wat validatie niet kan bereiken
Validatie verkleint het aanvalsoppervlak, maar is volgens OWASP niet de hoofdverdediging tegen injectie. Een achternaam met apostrof is geldig en moet toch veilig in een database terechtkomen. Dat lukt alleen met prepared statements, zoals de gids SQL-injectie in PHP voorkomen laat zien. En een geldige commentaartekst wordt pas door contextgerichte codering bij de uitvoer ongevaarlijk.
De juiste volgorde luidt dus: bij ontvangst valideren, bij verwerking parametriseren, bij uitvoer coderen. Daarvoor horen ook groottegrenzen: verzoeken die veel te groot zijn, moeten webserver en PHP niet eens volledig verwerken. Daarvoor dienen instellingen zoals post_max_size, die na PHP2Shell sowieso gecontroleerd moeten worden.
Hoe helpt PHP SOS concreet?
- Wij inventariseren alle invoerpunten van uw applicatie: formulieren, interfaces, uploads, imports en webhooks.
- Wij leggen per veld vast wat is toegestaan en vervangen algemene opschoonfuncties door duidelijke controles.
- Wij controleren of databasetoegang en uitvoer los van de validatie zijn beveiligd.
- Wij documenteren de regels zo dat uw team ze bij nieuwe functies kan voortzetten.
Een systematische controle beschrijft de dienst beveiligingsaudit en penetratietest.
Veelgestelde vragen
Wat is het verschil tussen valideren en opschonen?
Valideren controleert of een waarde aan de verwachtingen voldoet en wijst haar anders af. Opschonen wijzigt de waarde, bijvoorbeeld door tekens te verwijderen. Voor beveiligingsbeslissingen is afwijzen bijna altijd beter, omdat een stilzwijgend gewijzigde waarde nieuwe fouten kan veroorzaken.
Is FILTER_SANITIZE_STRING nog bruikbaar?
Het filter is sinds PHP 8.1 als verouderd gemarkeerd. De PHP-handleiding adviseert in plaats daarvan htmlspecialchars() bij de uitvoer. Bestaande code moet u omzetten, omdat verouderde functies in toekomstige versies kunnen vervallen.
Is FILTER_VALIDATE_URL genoeg om links veilig te maken?
Nee. Het filter controleert alleen de vorm van het adres. Volgens de handleiding hoeft een geldige URL niet met http te beginnen, daarom moet u het schema zelf controleren tegen een lijst met toegestane waarden zoals https. Geïnternationaliseerde domeinnamen wijst het filter af.
Moet ik ook gegevens uit mijn eigen database valideren?
Bij de uitvoer gelden ze als niet vertrouwd en worden ze passend gecodeerd. Een nieuwe validatie is zinvol als de gegevens oorspronkelijk van buiten kwamen en nu in een nieuwe context worden gebruikt, bijvoorbeeld als bestandsnaam of onderdeel van een opdracht.
Helpt validatie tegen PHP2Shell?
Nee. PHP2Shell betreft de verwerking van verzoeken in de PHP-interpreter, voordat uw applicatiecode überhaupt draait. Daartegen helpt alleen de PHP-update met aansluitend een herstart van alle PHP-processen.