# Jak zabránit SQL injection v PHP

> Jak zabránit SQL injection v PHP: prepared statements v PDO a mysqli, whitelisty sloupců, minimální práva databáze. S ukázkami kódu a kontrolním seznamem.

Zdroj: https://phpsos.de/cs/ratgeber/sql-injection-php/  
Stav: 25.10.2026  
Poskytovatel: PHP SOS, Nouzová pomoc pro PHP a bezpečnost serverů. Okamžitá pomoc přes WhatsApp: https://wa.me/491736494914?text=Dobr%C3%BD%20den%2C%20PHP%20SOS%2C%20pot%C5%99ebujeme%20pomoc%20s%20na%C5%A1%C3%ADm%20PHP%20serverem.%20Firma%3A%20%E2%80%A6%20Web%3A%20%E2%80%A6

**Stručně:** SQL injection v PHP spolehlivě zabráníte tak, že hodnoty nikdy nevkládáte do textů SQL, ale vždy je předáváte přes prepared statements se zástupnými symboly. Pro názvy sloupců a tabulek, které se jako zástupné symboly předat nedají, platí pevný whitelist. Navíc databázový uživatel s minimálními právy omezí škodu, pokud nějaká mezera přece jen zůstane.

## Proč SQL injection po PHP2Shell nesmí vypadnout ze zřetele

V říjnu 2026 se mnohé točí kolem PHP2Shell, dvou kritických zranitelností v interpretu PHP, opravených od 21. 10. 2026 ve verzích PHP 8.5.12, 8.4.27, 8.3.36 a 8.2.35. Mnoho týmů v těchto dnech aktualizovalo servery a restartovalo procesy. To je správné a naléhavé.

Aktuální stav PHP ale nic neříká o vlastním kódu. SQL injection patří už roky k nejčastějším slabinám webových aplikací a OWASP Top 10 ji vede v kategorii Injection. Kdo si právě tak či tak plánuje bezpečnostní kontroly, měl by se rovnou podívat i na přístupy k databázi ve své aplikaci.

Dobrá zpráva: SQL injection je jedna z mála slabin, které lze jednoduchým, důsledně uplatňovaným pravidlem téměř úplně vyloučit.

## Jak SQL injection v PHP vzniká?

SQL injection vzniká, když aplikace zachází se vstupy uživatele jako s částí příkazu SQL, nikoli jako s čistými daty. Typické je skládání dotazu z textových částí a proměnných. Pokud může návštěvník určit obsah takové proměnné, může za určitých okolností změnit význam příkazu.

Následky sahají od čtení cizích údajů zákazníků přes změnu cen nebo oprávnění až po smazání celých tabulek. Podle databáze a práv může zranitelnost umožnit i přístupy za hranice databáze. Pokud jsou dotčeny osobní údaje, může vzniknout oznamovací povinnost podle GDPR.

Vstupy přitom nejsou jen pole formulářů. Za nedůvěryhodné se považují také parametry URL, cookies, hlavičky HTTP, nahrané soubory, odpovědi cizích rozhraní a již uložená data z vlastní databáze.

## Prepared statements s PDO a mysqli

Prepared statements posílají příkaz SQL a hodnoty databázi odděleně. Hodnoty nejsou nikdy interpretovány jako součást příkazu. S PDO vypadá bezpečný dotaz takto:

```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();
```

Volba `PDO::ATTR_EMULATE_PREPARES => false` zajišťuje, že databáze používá skutečné prepared statements, pokud to ovladač podporuje. U mysqli platí stejný princip:

```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();
```

I seznamy pro `IN (...)` lze zapsat bezpečně, když pro každou hodnotu vytvoříte vlastní zástupný symbol a hodnoty předáte odděleně.

```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);
```

## Zvláštní případy: názvy sloupců, řazení a holé SQL

Zástupné symboly fungují jen pro hodnoty, nikoli pro názvy tabulek, názvy sloupců nebo směr řazení. Právě tady v praxi vzniká mnoho mezer, například u řaditelných tabulek nebo filtrů. Řešením je pevný seznam povolených hodnot:

```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");
```

Frameworky a CMS přinášejí vlastní nástroje. Ve WordPressu tuto úlohu přebírá `$wpdb->prepare()`, v Laravelu a Symfony query buildery a ORM. Jakmile tam použijete holé SQL, například přes funkce s „raw“ v názvu, platí znovu pravidla z této části. Pokyny k Laravelu najdete v článku [Jak zabezpečit aplikace v Laravelu](https://phpsos.de/cs/plattformen/laravel/).

> **Tip:**
> Hledejte v kódu cíleně texty SQL, do nichž se dosazují proměnné. Jednoduchým začátkem je hledání `query(` a `->raw` v kombinaci s `$_GET`, `$_POST` nebo `$_REQUEST`.

## Jak SQL injection vyhledat ve stávajícím kódu

U nového softwaru lze pravidlo dodržet od začátku. Obtížnější je to u aplikací, které rostly léta. Tam se vyplatí systematické hledání míst, kde se texty SQL skládají s proměnnými:

```bash
# Příkazy SQL, do nichž se dosazují proměnné
grep -rnE "(SELECT|INSERT|UPDATE|DELETE)[^;]*\\\$[a-zA-Z_]" src/ --include="*.php" | head -50
# Přímé použití dat požadavku v dotazovacích funkcích
grep -rnE "(query|exec|raw)\s*\(.*\\\$_(GET|POST|REQUEST|COOKIE)" src/ --include="*.php"
```

Ne každý nález je zranitelnost, například pokud proměnná obsahuje jen pevný název tabulky. Každé nalezené místo by se však mělo jednou vědomě zkontrolovat a podle potřeby převést na prepared statements. Nástroje statické analýzy kódu pomáhají takové vzory trvale odhalovat v build pipeline, takže nové chyby vůbec nepůjdou do ostrého provozu.

## Omezení škod: práva, chybová hlášení, kontrola

Bezpečný kód je první linie obrany. Další úrovně omezují škodu, pokud by se nějaká mezera přece jen přehlédla:

- **Minimální práva:** Databázový uživatel webu zpravidla potřebuje jen `SELECT`, `INSERT`, `UPDATE` a `DELETE` na vlastní databázi, žádná práva zakládat uživatele ani přistupovat k jiným databázím.
- **Žádná chybová hlášení navenek:** `display_errors = Off` v php.ini. Chyby databáze patří do logu, nikoli do prohlížeče.
- **Oddělení podle aplikace:** Každý web dostane vlastního databázového uživatele.
- **Udržovat závislosti aktuální:** Zranitelnosti sídlí i v knihovnách, jak ukazuje příručka [composer audit](https://phpsos.de/cs/ratgeber/composer-abhaengigkeiten-pruefen/).
- **Nechat zkontrolovat:** Code review a automatizovaná analýza najdou mnoho míst, penetrační test ukáže, co je zvenčí skutečně dosažitelné.

```sql
-- Příklad: databázový uživatel jen s potřebnými právy
CREATE USER 'shop_app'@'localhost' IDENTIFIED BY '<starkes Passwort>';
GRANT SELECT, INSERT, UPDATE, DELETE ON shop.* TO 'shop_app'@'localhost';
```

Příbuzná slabina se týká výpisu dat v prohlížeči. Jak ji zabezpečit, vysvětluje příručka [Jak zabránit cross-site scriptingu v PHP](https://phpsos.de/cs/ratgeber/xss-php/).

## Jak konkrétně pomáhá PHP SOS?

- Systematicky kontrolujeme přístupy k databázi vaší aplikace na nebezpečné dotazy.
- Nálezy řadíme podle dosažitelnosti a dotčených dat a ukazujeme konkrétní opravy.
- Práva databáze a zpracování chyb nastavíme na bezpečnou úroveň.
- Po opravě znovu testujeme a výsledek zdokumentujeme.

Nezávislou kontrolu popisuje služba [Bezpečnostní audit a penetrační test](https://phpsos.de/cs/leistungen/sicherheitsaudit-penetrationstest/).


## Časté dotazy

**Stačí vstupy ošetřit pomocí addslashes nebo real_escape_string?**  
Ne, je to náchylné k chybám. Escapování závisí na znakové sadě a kontextu a snadno se někde zapomene. Prepared statements zásadně oddělují příkaz a data, a jsou proto doporučenou cestou.

**Chrání ORM automaticky před SQL injection?**  
Do značné míry, dokud používáte dotazovací funkce ORM. Jakmile píšete holé SQL nebo dosazujete hodnoty do řazení a názvů sloupců, platí stejná pravidla jako bez ORM.

**Pomáhá proti SQL injection Web Application Firewall?**  
WAF může zachytit známé vzory a získat čas. Bezpečný kód ale nenahrazuje, protože pravidla založená na datech požadavku lze obejít a občas zasáhnou legitimní vstupy.

**Uzavře aktualizace PHP proti PHP2Shell i zranitelnosti typu SQL injection?**  
Ne. PHP2Shell se týká interpretu PHP. SQL injection je chyba v kódu aplikace a zůstává bez ohledu na to, jaká verze PHP běží.

## Zdroje

- [OWASP Top 10](https://owasp.org/www-project-top-ten/)
- [OWASP Cheat Sheet Series](https://cheatsheetseries.owasp.org/)
- [php.net: bezpečnostní oznámení](https://www.php.net/)
