# Kaip užkirsti kelią SQL injekcijai PHP

> Kaip užkirsti kelią SQL injekcijai PHP: parengtos užklausos su PDO ir mysqli, leistinų reikšmių sąrašai, minimalios duomenų bazės teisės.

Šaltinis: https://phpsos.de/lt/ratgeber/sql-injection-php/  
Būsena: 2026-10-25  
Teikėjas: PHP SOS, PHP avarinė pagalba ir serverio sauga. Skubi pagalba per „WhatsApp“: https://wa.me/491736494914?text=Sveiki%2C%20PHP%20SOS%2C%20mums%20reikia%20pagalbos%20su%20m%C5%ABs%C5%B3%20PHP%20serveriu.%20%C4%AEmon%C4%97%3A%20%E2%80%A6%20Svetain%C4%97%3A%20%E2%80%A6

**Trumpai:** SQL injekciją PHP patikimai užkertate, niekada neįterpdami reikšmių į SQL tekstą, o visada perduodami jas per parengtas užklausas su vietos žymekliais. Stulpelių ir lentelių pavadinimams, kurių negalima perduoti kaip vietos žymeklių, galioja fiksuotas leistinų reikšmių sąrašas. Papildomai duomenų bazės naudotojas su minimaliomis teisėmis apriboja žalą, jei spraga vis dėlto lieka.

## Kodėl po PHP2Shell SQL injekcijos negalima pamiršti

2026 m. spalį daug kas sukasi apie PHP2Shell, du kritinius PHP interpretatoriaus pažeidžiamumus, ištaisytus nuo 2026-10-21 PHP 8.5.12, 8.4.27, 8.3.36 ir 8.2.35 versijose. Daugelis komandų tomis dienomis atnaujino serverius ir iš naujo paleido procesus. Tai teisinga ir skubu.

Tačiau šiuolaikinė PHP būsena nieko nesako apie jūsų pačių kodą. SQL injekcija jau daug metų yra vienas dažniausių žiniatinklio programų pažeidžiamumų, OWASP Top 10 ji vedama kategorijoje Injection. Kas dabar vis tiek planuoja saugumo patikras, savo programos duomenų bazės prieigas turėtų peržiūrėti kartu.

Gera žinia: SQL injekcija yra vienas iš nedaugelio pažeidžiamumų, kurį beveik visiškai galima pašalinti paprasta, nuosekliai taikoma taisykle.

## Kaip PHP atsiranda SQL injekcija?

SQL injekcija atsiranda, kai programa naudotojo įvestis traktuoja kaip SQL komandos dalį, o ne kaip gryną duomenų turinį. Tipiška yra užklausos sudarymas iš teksto dalių ir kintamųjų. Jei lankytojas gali nustatyti tokio kintamojo turinį, jis tam tikromis aplinkybėmis gali pakeisti komandos prasmę.

Pasekmės siekia nuo svetimų klientų duomenų nuskaitymo, kainų ar teisių keitimo iki ištisų lentelių ištrynimo. Priklausomai nuo duomenų bazės ir teisių, pažeidžiamumas gali leisti prieigą ir už duomenų bazės ribų. Jei paveikti asmens duomenys, gali kilti pranešimo pareiga pagal BDAR.

Įvestys čia yra ne tik formų laukai. Nepatikimais laikomi ir URL parametrai, slapukai, HTTP antraštės, įkelti failai, svetimų sąsajų atsakymai ir jau išsaugoti duomenys iš savo duomenų bazės.

## Parengtos užklausos su PDO ir mysqli

Parengtos užklausos SQL komandą ir reikšmes siunčia duomenų bazei atskirai. Reikšmės niekada netraktuojamos kaip komandos dalis. Su PDO saugi užklausa atrodo taip:

```php
<?php
$pdo = new PDO($dsn, $benutzer, $passwort, [
    PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_EMULATE_PREPARES   => false,
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]);

$stmt = $pdo->prepare('SELECT id, name, email FROM kunden WHERE id = :id AND status = :status');
$stmt->execute([
    'id'     => (int) $_GET['id'],
    'status' => 'aktiv',
]);
$kunde = $stmt->fetch();
```

Parinktis `PDO::ATTR_EMULATE_PREPARES => false` užtikrina, kad duomenų bazė naudoja tikras parengtas užklausas, jei tai palaiko tvarkyklė. Su mysqli galioja tas pats principas:

```php
<?php
$stmt = $mysqli->prepare('SELECT id, name FROM produkte WHERE kategorie = ? AND preis <= ?');
$stmt->bind_param('sd', $kategorie, $maxPreis);
$stmt->execute();
$ergebnis = $stmt->get_result();
```

Saugiai galima užrašyti ir `IN (...)` sąrašus, sukuriant kiekvienai reikšmei savo vietos žymeklį ir perduodant reikšmes atskirai.

```php
<?php
$ids = array_map('intval', $ausgewaehlteIds);
$platzhalter = implode(',', array_fill(0, count($ids), '?'));
$stmt = $pdo->prepare("SELECT id, name FROM produkte WHERE id IN ($platzhalter)");
$stmt->execute($ids);
```

## Ypatingi atvejai: stulpelių pavadinimai, rūšiavimas ir grynasis SQL

Vietos žymekliai tinka tik reikšmėms, o ne lentelių pavadinimams, stulpelių pavadinimams ar rūšiavimo krypčiai. Būtent čia praktikoje atsiranda daug spragų, pavyzdžiui, rūšiuojamose lentelėse ar filtruose. Sprendimas yra fiksuotas leistinų reikšmių sąrašas:

```php
<?php
$erlaubteSpalten = ['name' => 'name', 'preis' => 'preis', 'datum' => 'erstellt_am'];
$spalte   = $erlaubteSpalten[$_GET['sort'] ?? ''] ?? 'name';
$richtung = (($_GET['dir'] ?? '') === 'desc') ? 'DESC' : 'ASC';

$stmt = $pdo->query("SELECT id, name, preis FROM produkte ORDER BY $spalte $richtung");
```

Karkasai ir CMS turi savų įrankių. WordPress šią užduotį atlieka `$wpdb->prepare()`, Laravel ir Symfony užklausų konstruktoriai bei ORM. Kai tik ten naudojate grynąjį SQL, pavyzdžiui, per funkcijas su „raw“ pavadinime, vėl galioja šio skyriaus taisyklės. Patarimus dėl Laravel rasite puslapyje [Laravel programų apsauga](https://phpsos.de/lt/plattformen/laravel/).

> **Patarimas:**
> Kode tikslingai ieškokite SQL tekstų, į kuriuos įterpiami kintamieji. Paprastas pradžios taškas yra `query(` ir `->raw` paieška kartu su `$_GET`, `$_POST` ar `$_REQUEST`.

## SQL injekcijos paieška esamame kode

Naujoje programinėje įrangoje taisyklę galima laikytis nuo pat pradžios. Sunkiau su programomis, kurios augo metų metais. Ten verta sistemingai ieškoti vietų, kur SQL tekstai sudaromi su kintamaisiais:

```bash
# SQL komandos, į kurias įterpiami kintamieji
grep -rnE "(SELECT|INSERT|UPDATE|DELETE)[^;]*\\\$[a-zA-Z_]" src/ --include="*.php" | head -50
# Tiesioginis užklausos duomenų naudojimas užklausų funkcijose
grep -rnE "(query|exec|raw)\s*\(.*\\\$_(GET|POST|REQUEST|COOKIE)" src/ --include="*.php"
```

Ne kiekvienas radinys yra spraga, pavyzdžiui, kai kintamasis turi tik fiksuotą lentelės pavadinimą. Tačiau kiekvieną radimo vietą reikėtų sąmoningai patikrinti vieną kartą ir prireikus pereiti prie parengtų užklausų. Statinės kodo analizės įrankiai padeda tokius šablonus nuolat atpažinti kūrimo procese (pipeline), kad naujos klaidos net nepatektų į gyvą aplinką.

## Žalos ribojimas: teisės, klaidų pranešimai, patikra

Saugus kodas yra pirmoji gynybos linija. Kiti lygmenys riboja žalą, jei spraga vis dėlto būtų praleista:

- **Minimalios teisės:** svetainės duomenų bazės naudotojui dažniausiai reikia tik `SELECT`, `INSERT`, `UPDATE` ir `DELETE` savo duomenų bazėje, jokių teisių kurti naudotojus ar kreiptis į kitas duomenų bazes.
- **Jokių klaidų pranešimų į išorę:** `display_errors = Off` php.ini. Duomenų bazės klaidos priklauso žurnalui, o ne naršyklei.
- **Atskyrimas kiekvienai programai:** kiekviena svetainė gauna savą duomenų bazės naudotoją.
- **Priklausomybes laikyti atnaujintas:** pažeidžiamumų esama ir bibliotekose, kaip rodo vadovas [composer audit](https://phpsos.de/lt/ratgeber/composer-abhaengigkeiten-pruefen/).
- **Leisti patikrinti:** kodo peržiūra ir automatizuota analizė randa daug vietų, skverbties testas parodo, kas iš išorės iš tikrųjų pasiekiama.

```sql
-- Pavyzdys: duomenų bazės naudotojas tik su būtinomis teisėmis
CREATE USER 'shop_app'@'localhost' IDENTIFIED BY '<starkes Passwort>';
GRANT SELECT, INSERT, UPDATE, DELETE ON shop.* TO 'shop_app'@'localhost';
```

Susijęs pažeidžiamumas liečia duomenų išvedimą naršyklėje. Kaip jį apsaugoti, paaiškina vadovas [Kaip užkirsti kelią Cross-Site Scripting PHP](https://phpsos.de/lt/ratgeber/xss-php/).

## Kaip PHP SOS padeda konkrečiai?

- Sistemingai tikriname jūsų programos duomenų bazės prieigas dėl nesaugių užklausų.
- Radinius prioritetizuojame pagal pasiekiamumą ir paveiktus duomenis ir parodome konkrečius pataisymus.
- Duomenų bazės teises ir klaidų tvarkymą nustatome saugiu lygiu.
- Po pataisymo išbandome iš naujo ir dokumentuojame rezultatą.

Nepriklausomą patikrą aprėpia paslauga [Saugumo auditas ir skverbties testas](https://phpsos.de/lt/leistungen/sicherheitsaudit-penetrationstest/).


## Dažni klausimai

**Ar pakanka įvestis apdoroti su addslashes ar real_escape_string?**  
Ne, tai linkę klysti. Pabėgimo simbolių naudojimas (escaping) priklauso nuo simbolių rinkinio ir konteksto, ir jį lengva kurioje nors vietoje pamiršti. Parengtos užklausos iš esmės atskiria komandą ir duomenis, todėl yra rekomenduojamas kelias.

**Ar ORM automatiškai apsaugo nuo SQL injekcijos?**  
Daugiausia taip, kol naudojate ORM užklausų funkcijas. Kai tik rašote grynąjį SQL arba reikšmes įterpiate į rūšiavimą ar stulpelių pavadinimus, galioja tos pačios taisyklės kaip be ORM.

**Ar žiniatinklio programų ugniasienė (WAF) padeda nuo SQL injekcijos?**  
WAF gali sustabdyti žinomus šablonus ir laimėti laiko. Tačiau ji nepakeičia saugaus kodo, nes taisykles pagal užklausos duomenis galima apeiti, o kartais jos užkliudo ir teisėtas įvestis.

**Ar PHP atnaujinimas nuo PHP2Shell uždaro ir SQL injekcijos spragas?**  
Ne. PHP2Shell liečia PHP interpretatorių. SQL injekcija yra programos kodo klaida ir išlieka, nesvarbu, kokia PHP versija veikia.

## Šaltiniai

- [OWASP Top 10](https://owasp.org/www-project-top-ten/)
- [OWASP Cheat Sheet Series](https://cheatsheetseries.owasp.org/)
- [php.net: saugumo pranešimai](https://www.php.net/)
