Trumpai: įvestį PHP tikrinate su filter_var() ar filter_input_array() ir aiškiomis taisyklėmis kiekvienam laukui: tipas, intervalas, ilgis ir leidžiamos reikšmės. Kas netinka, atmetama, o ne tyliai perdaroma. Patikra svarbi švariems duomenims ir mažesniam atakos paviršiui, bet nepakeičia nei parengtų užklausų (prepared statements) nuo SQL injekcijos, nei kontekstui tinkamo kodavimo nuo XSS.
Kodėl „filtruoti viską“ nėra koncepcija
Daugelyje senesnių PHP projektų kiekvieno failo pradžioje yra ciklas, kuris visas reikšmes iš $_GET ir $_POST praleidžia per išvalymo funkciją. Tai atrodo saugu. Praktikoje ji kartais pašalina per daug, pavyzdžiui, apostrofus pavardėse, o kartais per mažai, nes ta pati funkcija turėtų tikti SQL, HTML ir failų vardams vienu metu.
Prasminga patikra veikia kitaip: ji kiekvienam laukui klausia, kas ten leidžiama. Užsakymo kiekis yra sveikasis skaičius tarp 1 ir viršutinės ribos. Rikiavimo laukas yra viena iš trijų fiksuotų reikšmių. Data turi tam tikrą formatą ir yra po vienos ir prieš kitą datą. Viskas kita atmetama.
OWASP skiria du lygmenis: sintaksinę patikrą, ar reikšmė turi tinkamą formą, ir semantinę patikrą, ar ji prasminga kontekste. Abi priklauso serveriui; patikros naršyklėje tarnauja tik patogumui.
Kokius filtrus PHP turi
Filtrų plėtinys PHP įtrauktas pagal numatytuosius nustatymus. Svarbiausi patikros filtrai pagal PHP vadovą:
| Filtras | Tikrina | Svarbios parinktys ir pastabos |
|---|---|---|
FILTER_VALIDATE_INT |
sveikasis skaičius | min_range, max_range |
FILTER_VALIDATE_FLOAT |
skaičius su kableliu | decimal skirtukui |
FILTER_VALIDATE_BOOL |
loginė reikšmė | su FILTER_NULL_ON_FAILURE atskiriama nuo „false“ |
FILTER_VALIDATE_EMAIL |
el. pašto adreso forma | neįrodo, kad dėžutė egzistuoja |
FILTER_VALIDATE_URL |
URL forma | schemą tikrinti patiems, jokių tarptautinių domenų |
FILTER_VALIDATE_IP |
IP adresas | vėliavėlės privatiems ir rezervuotiems intervalams |
FILTER_VALIDATE_REGEXP |
savas šablonas | šabloną įtvirtinti su ^ ir $ |
Išvalymo filtras FILTER_SANITIZE_STRING nuo PHP 8.1 pasenęs. Kas jį dar naudoja, turėtų perjungti patikrą į tinkamus filtrus, o išvedimui naudoti htmlspecialchars().
filter_var PHP: tvarkingai patikrinti formą
Su filter_input_array() visą formą galima patikrinti vienu žingsniu pagal apibrėžimą. Nežinomi laukai nepriimami:
<?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);
// Išvesti klaidos pranešimą kiekvienam laukui, įvesties neapdoroti
}
Atkreipkite dėmesį į griežtus palyginimus su ===: galiojanti reikšmė 0 ar tuščia eilutė negali būti painiojama su nesėkme. Kodėl laisvi palyginimai čia pavojingi, aiškina Type Juggling: laisvi palyginimai kaip saugumo rizika. Kaip PHP apskritai pateikia užklausos duomenis, aprašo Superglobals: $_GET, $_POST, $_FILES ir kiti.
Spąstai su el. paštu, URL ir laisvu tekstu
Kai kurie filtrai daro mažiau, nei leidžia manyti jų pavadinimas:
- URL:
FILTER_VALIDATE_URLpriima ir kitas schemas nei http ir https. Schemą po to patikrinkite suparse_url()pagal fiksuotą sąrašą. Jei URL kviečia serveris, taikomos papildomos taisyklės, žr. Server-Side Request Forgery PHP prevencija. - El. pašto adresai: filtras tikrina formą, o ne nuosavybę. Kam reikia tikrumo, siunčia patvirtinimo nuorodą.
- Laisvas tekstas: vardų, žinučių ir adresų beveik neįmanoma prasmingai apriboti pagal simbolius. Tikrinkite ilgį ir simbolių koduotę, pavyzdžiui, su
mb_check_encoding($wert, 'UTF-8'), o išvedant pasikliaukite kodavimu. - Reguliarieji reiškiniai: be
^ir$šablonas tikrina tik tai, ar jis kur nors tekste pasitaiko, o ne ar tinka visa reikšmė. - Masyvai vietoj eilučių: užpuolikas vietoj teksto gali siųsti masyvą. Be tipo patikros tai sukelia įspėjimus ar netikėtą elgesį.
Fiksuotoms pasirinkimo reikšmėms, pavyzdžiui, rikiavimui, kalbai ar siuntimo būdui, leidžiamųjų sąrašas su in_array($wert, $erlaubt, true) ar match išraiška yra paprastesnis ir griežtesnis nei bet koks reguliarusis reiškinys.
Dar viena dažna klaida susijusi su skaičiais, turinčiais reikšmę, pavyzdžiui, kainomis, kiekiais ar nuolaidomis. Kad reikšmė yra galiojantis skaičius, nesako, ar ji kontekste leidžiama. Neigiamas kiekis, nuolaida didesnė už prekės vertę ar kaina, perimta iš formos, o ne iš duomenų bazės, yra klasikinės logikos klaidos. Kainos, teisės ir būsenos reikšmės todėl visada priklauso serveriui ir niekada paslėptiems formos laukams.
JSON, webhook ir importai: patikra už formų ribų
Daugelis įvesčių šiandien jau nebėra formos, o programų JSON užklausos, mokėjimo paslaugų webhook ar tiekėjų importo failai. Ir ten galioja: pirmiau saugiai nuskaityti, tada tikrinti. OWASP pataria apriboti dydį ir įdėjimo gylį dar prieš nuskaitant duomenis. PHP dalį to perima json_decode() su didžiausiu gyliu ir JSON_THROW_ON_ERROR:
<?php
$roh = file_get_contents('php://input', false, null, 0, 65536); // daugiausia 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;
}
Su webhook prieš turinio patikrą stovi klausimas, ar žinutė tikrai kilo iš nurodytos paslaugos. Jei paslauga siūlo parašą, patikrinkite jį ir palyginkite su hash_equals(). Importo failuose kiekvieną eilutę tikrinkite kaip formą ir klaidų atveju tvarkingai nutraukite, užuot perėmę pusiau įrašus.
Klaidų pranešimai turėtų įvardyti paveiktą lauką, bet atmestos reikšmės negrąžinti nefiltruotos. Kitaip pačiame klaidos pranešime atsiranda nauja išvesties vieta, kurią vėl reikia koduoti.
Ko patikra negali atlikti
Patikra sumažina atakos paviršių, bet, pasak OWASP, nėra pagrindinė gynyba nuo injekcijos. Pavardė su apostrofu yra galiojanti ir vis tiek turi saugiai patekti į duomenų bazę. Tai pavyksta tik su parengtomis užklausomis (prepared statements), kaip rodo vadovas SQL injekcijos PHP prevencija. O galiojantis komentaro tekstas nepavojingas tampa tik kontekstui tinkamu kodavimu išvedant.
Teisinga seka tokia: gaunant tikrinti, apdorojant parametrizuoti, išvedant koduoti. Papildomai prieš tai stovi dydžio ribos: žiniatinklio serveris ir PHP užklausų, kurios per didelės, neturėtų net visiškai apdoroti. Tam skirti nustatymai kaip post_max_size, kuriuos po PHP2Shell vis tiek derėtų peržiūrėti.
Kaip PHP SOS padeda konkrečiai?
- Surenkame visus jūsų programos įėjimus: formas, sąsajas, įkėlimus, importus ir webhook.
- Kiekvienam laukui nustatome, kas leidžiama, ir bendras išvalymo funkcijas pakeičiame aiškiomis patikromis.
- Tikriname, ar duomenų bazės prieigos ir išvestys apsaugotos nepriklausomai nuo patikros.
- Taisykles dokumentuojame taip, kad jūsų komanda galėtų jas tęsti kurdama naujas funkcijas.
Sisteminę patikrą aprašo paslauga Saugumo auditas ir įsiskverbimo testas.
Dažni klausimai
Kuo skiriasi patikra (validavimas) nuo išvalymo?
Patikra tikrina, ar reikšmė atitinka lūkesčius, ir kitu atveju ją atmeta. Išvalymas reikšmę pakeičia, pavyzdžiui, pašalindamas simbolius. Saugumo sprendimams beveik visada geriau atmesti, nes tyliai pakeista reikšmė gali sukelti naujų klaidų.
Ar FILTER_SANITIZE_STRING dar galima naudoti?
Filtras nuo PHP 8.1 pažymėtas kaip pasenęs. PHP vadovas vietoj jo rekomenduoja išvedant naudoti htmlspecialchars(). Esamą kodą turėtumėte pakeisti, nes pasenusios funkcijos būsimose versijose gali būti pašalintos.
Ar FILTER_VALIDATE_URL pakanka nuorodoms padaryti saugias?
Ne. Filtras tikrina tik adreso formą. Pasak vadovo, galiojantis URL neprivalo prasidėti http, todėl schemą turite patys patikrinti pagal leidžiamųjų sąrašą, pavyzdžiui, https. Tarptautinius domenų vardus filtras atmeta.
Ar turiu tikrinti ir savos duomenų bazės duomenis?
Išvedant jie laikomi nepatikimais ir atitinkamai koduojami. Pakartotinė patikra prasminga, kai duomenys iš pradžių atėjo iš išorės, o dabar naudojami naujame kontekste, pavyzdžiui, kaip failo vardas ar komandos dalis.
Ar patikra padeda nuo PHP2Shell?
Ne. PHP2Shell paveikia užklausų apdorojimą PHP interpretatoriuje dar prieš paleidžiant jūsų programos kodą. Nuo to padeda tik PHP atnaujinimas su vėlesniu visų PHP procesų paleidimu iš naujo.