Ukratko: Unos u PHP-u validirate pomoću filter_var() ili filter_input_array() i jasnih pravila po polju: tip, raspon, duljina i dopuštene vrijednosti. Što ne odgovara, odbija se, a ne šutke preinačuje. Validacija je važna za čiste podatke i manju napadnu površinu, ali ne zamjenjuje ni Prepared Statements protiv SQL injectiona ni kodiranje prilagođeno kontekstu protiv XSS-a.
Zašto „sve filtrirati“ nije koncept
U mnogim starijim PHP projektima na početku svake datoteke nalazi se petlja koja sve vrijednosti iz $_GET i $_POST šalje kroz funkciju za čišćenje. To djeluje sigurno. U praksi ponekad ukloni previše, primjerice apostrofe u prezimenima, a ponekad premalo, jer ista funkcija treba istodobno vrijediti za SQL, HTML i nazive datoteka.
Smislena validacija radi drugačije: za svako polje pita što je tamo dopušteno. Količina narudžbe je cijeli broj između 1 i gornje granice. Polje sortiranja jedna je od triju fiksnih vrijednosti. Datum postoji u određenom formatu i nakon nekog drugog datuma. Sve ostalo se odbija.
OWASP pritom razlikuje dvije razine: sintaktičku provjeru ima li vrijednost ispravan oblik i semantičku provjeru ima li smisla u kontekstu. Obje pripadaju na poslužitelj; provjere u pregledniku služe samo praktičnosti korištenja.
Koje filtre PHP donosi
Proširenje Filter standardno je uključeno u PHP. Najvažniji filtri za validaciju prema PHP priručniku:
| Filtar | Provjerava | Važne opcije i napomene |
|---|---|---|
FILTER_VALIDATE_INT |
cijeli broj | min_range, max_range |
FILTER_VALIDATE_FLOAT |
decimalni broj | decimal za znak razdvajanja |
FILTER_VALIDATE_BOOL |
logička vrijednost | s FILTER_NULL_ON_FAILURE razlučiva od „false“ |
FILTER_VALIDATE_EMAIL |
oblik adrese e-pošte | ne dokazuje da poštanski sandučić postoji |
FILTER_VALIDATE_URL |
oblik URL-a | shemu provjeriti sami, bez internacionaliziranih domena |
FILTER_VALIDATE_IP |
IP adresa | zastavice za privatne i rezervirane raspone |
FILTER_VALIDATE_REGEXP |
vlastiti uzorak | uzorak usidriti s ^ i $ |
Filtar za čišćenje FILTER_SANITIZE_STRING zastario je od PHP-a 8.1. Tko ga još koristi, trebao bi validaciju prebaciti na odgovarajuće filtre, a za izlaz koristiti htmlspecialchars().
filter_var u PHP-u: pravilno provjeriti obrazac
Pomoću filter_input_array() cijeli se obrazac može u jednom koraku provjeriti prema definiciji. Nepoznata polja se pritom ne preuzimaju:
<?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);
// ispisati poruku o pogrešci po polju, ne obrađivati unos
}
Obratite pažnju na stroge usporedbe pomoću ===: valjana vrijednost 0 ili prazan string ne smiju se zamijeniti s neuspjehom. Zašto su ovdje opasne labave usporedbe, objašnjava članak Type Juggling: labave usporedbe kao sigurnosni rizik. Kako PHP uopće stavlja na raspolaganje podatke zahtjeva, opisuje Superglobals: $_GET, $_POST, $_FILES i ostali.
Zamke kod e-pošte, URL-a i slobodnog teksta
Neki filtri rade manje nego što njihov naziv sugerira:
- URL-ovi:
FILTER_VALIDATE_URLprihvaća i druge sheme osim http i https. Shemu zatim provjerite pomoćuparse_url()prema fiksnom popisu. Ako poslužitelj dohvaća URL, vrijede dodatna pravila, vidi Spriječiti Server-Side Request Forgery u PHP-u. - Adrese e-pošte: filtar provjerava oblik, a ne vlasništvo. Tko mora biti siguran, šalje poveznicu za potvrdu.
- Slobodni tekst: imena, poruke i adrese teško se smisleno ograničavaju na znakove. Provjerite duljinu i kodiranje znakova, primjerice pomoću
mb_check_encoding($wert, 'UTF-8'), a pri izlazu se oslonite na kodiranje. - Regularni izrazi: bez
^i$uzorak provjerava samo pojavljuje li se negdje u tekstu, a ne odgovara li cijela vrijednost. - Polja umjesto stringova: napadač umjesto teksta može poslati polje (array). Bez provjere tipa to dovodi do upozorenja ili neočekivanog ponašanja.
Za fiksne vrijednosti odabira poput sortiranja, jezika ili načina dostave popis dopuštenog pomoću in_array($wert, $erlaubt, true) ili izraza match jednostavniji je i stroži od svakog regularnog izraza.
Još jedna česta pogreška tiče se brojeva sa značenjem, primjerice cijena, količina ili popusta. To što je vrijednost valjan broj ne govori ništa o tome je li u kontekstu dopuštena. Negativna količina, popust veći od vrijednosti robe ili cijena preuzeta iz obrasca umjesto iz baze podataka klasične su logičke pogreške. Cijene, ovlasti i statusne vrijednosti stoga uvijek pripadaju na poslužitelj, a nikad u skrivena polja obrasca.
JSON, webhookovi i uvozi: validacija izvan obrazaca
Mnogi ulazi danas više nisu obrasci, nego JSON zahtjevi aplikacija, webhookovi pružatelja plaćanja ili uvozne datoteke dobavljača. I tamo vrijedi: najprije sigurno učitati, zatim provjeriti. OWASP savjetuje ograničiti veličinu i dubinu ugniježđenja prije nego se podaci uopće učitaju. U PHP-u json_decode() s maksimalnom dubinom i JSON_THROW_ON_ERROR preuzima dio toga:
<?php
$roh = file_get_contents('php://input', false, null, 0, 65536); // najviše 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;
}
Kod webhookova prije provjere sadržaja stoji pitanje potječe li poruka doista od navedene usluge. Ako usluga nudi potpis, provjerite ga i usporedite pomoću hash_equals(). Kod uvoznih datoteka svaki redak provjerite kao obrazac i pri pogreškama uredno prekinite umjesto da preuzimate napola ispunjene zapise.
Poruke o pogreškama trebale bi navesti zahvaćeno polje, ali odbijenu vrijednost ne bi smjele vraćati nefiltriranu. Inače se na samoj poruci o pogrešci stvara novo mjesto izlaza koje se opet mora kodirati.
Što validacija ne može učiniti
Validacija smanjuje napadnu površinu, ali prema OWASP-u nije glavna obrana protiv injectiona. Prezime s apostrofom je valjano i ipak mora sigurno dospjeti u bazu podataka. To uspijeva samo s Prepared Statements, kako pokazuje vodič Spriječiti SQL injection u PHP-u. A valjan tekst komentara postaje bezopasan tek kodiranjem prilagođenim kontekstu pri izlazu.
Ispravan redoslijed dakle glasi: pri primitku validirati, pri obradi parametrizirati, pri izlazu kodirati. Dodatno ispred toga idu ograničenja veličine: zahtjeve koji su preveliki web-poslužitelj i PHP uopće ne bi trebali obrađivati u cijelosti. Za to služe postavke poput post_max_size, koje se nakon PHP2Shella ionako trebaju provjeriti.
Kako PHP SOS konkretno pomaže?
- Bilježimo sve ulaze Vaše aplikacije: obrasce, sučelja, uploade, uvoze i webhookove.
- Po polju utvrđujemo što je dopušteno i paušalne funkcije za čišćenje zamjenjujemo jasnim provjerama.
- Provjeravamo jesu li pristupi bazi podataka i izlazi osigurani neovisno o validaciji.
- Pravila dokumentiramo tako da ih Vaš tim može nastaviti kod novih funkcija.
Sustavnu provjeru opisuje usluga Sigurnosni audit i penetracijski test.
Česta pitanja
Koja je razlika između validiranja i čišćenja?
Validiranje provjerava odgovara li vrijednost očekivanjima i inače je odbija. Čišćenje mijenja vrijednost, primjerice uklanjanjem znakova. Za sigurnosne odluke odbijanje je gotovo uvijek bolje, jer šutke promijenjena vrijednost može stvoriti nove pogreške.
Može li se FILTER_SANITIZE_STRING još koristiti?
Filtar je od PHP-a 8.1 označen kao zastarjeli. PHP priručnik umjesto toga preporučuje htmlspecialchars() pri izlazu. Postojeći kod trebali biste prilagoditi, jer zastarjele funkcije u budućim verzijama mogu nestati.
Je li FILTER_VALIDATE_URL dovoljan da linkovi budu sigurni?
Ne. Filtar provjerava samo oblik adrese. Prema priručniku valjan URL ne mora počinjati s http, zato shemu morate sami provjeriti prema popisu dopuštenog, primjerice https. Internacionalizirane nazive domena filtar odbija.
Moram li validirati i podatke iz vlastite baze podataka?
Pri izlazu se smatraju nepouzdanima i odgovarajuće se kodiraju. Ponovna validacija ima smisla kad su podaci izvorno došli izvana, a sada se koriste u novom kontekstu, primjerice kao naziv datoteke ili dio naredbe.
Pomaže li validacija protiv PHP2Shella?
Ne. PHP2Shell pogađa obradu zahtjeva u PHP interpreteru, prije nego što Vaš aplikacijski kod uopće radi. Protiv toga pomaže samo PHP update s ponovnim pokretanjem svih PHP procesa.