# Sprječavanje SQL injectiona u PHP-u

> Sprječavanje SQL injectiona u PHP-u: prepared statements uz PDO i mysqli, whitelist za stupce, uska prava baze podataka. S primjerima koda i popisom za provjeru.

Izvor: https://phpsos.de/hr/ratgeber/sql-injection-php/  
Stanje: 25.10.2026  
Pružatelj: PHP SOS, PHP hitna pomoć i sigurnost poslužitelja. Hitna pomoć putem WhatsAppa: https://wa.me/491736494914?text=Pozdrav%20PHP%20SOS%2C%20trebamo%20pomo%C4%87%20s%20na%C5%A1im%20PHP%20poslu%C5%BEiteljem.%20Tvrtka%3A%20%E2%80%A6%20Web-stranica%3A%20%E2%80%A6

**Ukratko:** SQL injection u PHP-u pouzdano sprječavate tako da vrijednosti nikad ne ugrađujete u SQL tekstove, nego ih uvijek predajete putem prepared statementa s rezerviranim mjestima. Za nazive stupaca i tablica, koji se ne mogu predati kao rezervirana mjesta, vrijedi čvrsti whitelist. Dodatno, korisnik baze podataka s minimalnim pravima ograničava štetu ako ipak ostane ranjivost.

## Zašto SQL injection nakon PHP2Shella ne smije izaći iz vida

U listopadu 2026. mnogo se toga vrti oko PHP2Shella, dviju kritičnih ranjivosti u PHP interpreteru, ispravljenih od 21.10.2026. u PHP-u 8.5.12, 8.4.27, 8.3.36 i 8.2.35. Mnogi su timovi tih dana ažurirali poslužitelje i ponovno pokrenuli procese. To je ispravno i hitno.

Aktualno stanje PHP-a ipak ne govori ništa o vlastitom kodu. SQL injection već godinama spada među najčešće slabosti web-aplikacija, a u OWASP Top 10 vodi se u kategoriji Injection. Tko ionako planira sigurnosne provjere, treba odmah pogledati i pristupe bazi podataka vlastite aplikacije.

Dobra vijest: SQL injection jedna je od rijetkih slabosti koja se jednostavnim, dosljedno primijenjenim pravilom može gotovo potpuno isključiti.

## Kako nastaje SQL injection u PHP-u

SQL injection nastaje kad aplikacija korisničke unose tretira kao dio SQL naredbe, a ne kao čiste podatke. Tipično je sastavljanje upita od tekstualnih dijelova i varijabli. Ako posjetitelj može odrediti sadržaj takve varijable, pod određenim okolnostima može promijeniti značenje naredbe.

Posljedice idu od čitanja tuđih podataka klijenata, preko mijenjanja cijena ili prava, do brisanja cijelih tablica. Ovisno o bazi i pravima, ranjivost može omogućiti i pristupe izvan baze podataka. Ako su pogođeni osobni podaci, može nastati obveza prijave prema GDPR-u.

Unosi pritom nisu samo polja obrazaca. I URL parametri, kolačići, HTTP zaglavlja, učitane datoteke, odgovori vanjskih sučelja i već pohranjeni podaci iz vlastite baze smatraju se nepouzdanima.

## Prepared statements uz PDO i mysqli

Prepared statements šalju SQL naredbu i vrijednosti odvojeno prema bazi podataka. Vrijednosti se nikad ne tumače kao dio naredbe. Uz PDO siguran upit izgleda ovako:

```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();
```

Opcija `PDO::ATTR_EMULATE_PREPARES => false` osigurava da baza podataka koristi prave prepared statemente, ako to upravljački program podržava. Uz mysqli vrijedi isto načelo:

```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 popisi za `IN (...)` mogu se sigurno prikazati tako da za svaku vrijednost stvorite zasebno rezervirano mjesto i vrijednosti predate odvojeno.

```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);
```

## Posebni slučajevi: nazivi stupaca, sortiranje i sirovi SQL

Rezervirana mjesta rade samo za vrijednosti, ne za nazive tablica, nazive stupaca ni smjerove sortiranja. Upravo ovdje u praksi nastaju mnoge ranjivosti, primjerice kod tablica koje se mogu sortirati ili kod filtara. Rješenje je čvrst popis dopuštenih vrijednosti:

```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");
```

Frameworki i CMS-ovi donose vlastite alate. U WordPressu tu zadaću preuzima `$wpdb->prepare()`, u Laravelu i Symfonyju query builderi i ORM-ovi. Čim tamo upotrijebite sirovi SQL, primjerice putem funkcija s „raw“ u nazivu, ponovno vrijede pravila ovog odjeljka. Napomene o Laravelu nalaze se na stranici [Zaštita Laravel aplikacija](https://phpsos.de/hr/plattformen/laravel/).

> **Savjet:**
> Ciljano potražite u kodu SQL tekstove u koje se umeću varijable. Jednostavan početak je pretraga za `query(` i `->raw` u kombinaciji s `$_GET`, `$_POST` ili `$_REQUEST`.

## Pronalaženje SQL injectiona u postojećem kodu

Kod novog softvera pravilo se može držati od početka. Teže je kod aplikacija koje su godinama rasle. Tamo se isplati sustavna potraga za mjestima na kojima se SQL tekstovi sastavljaju s varijablama:

```bash
# SQL naredbe u koje se umeću varijable
grep -rnE "(SELECT|INSERT|UPDATE|DELETE)[^;]*\\\$[a-zA-Z_]" src/ --include="*.php" | head -50
# Izravna uporaba podataka zahtjeva u funkcijama upita
grep -rnE "(query|exec|raw)\s*\(.*\\\$_(GET|POST|REQUEST|COOKIE)" src/ --include="*.php"
```

Nije svaki pogodak ranjivost, primjerice ako varijabla sadrži samo fiksan naziv tablice. Svako pronađeno mjesto ipak bi trebalo jednom svjesno provjeriti i po potrebi prebaciti na prepared statemente. Alati za statičku analizu koda pomažu da se takvi obrasci trajno prepoznaju u build pipelineu, tako da nove pogreške uopće ne dođu u produkciju.

## Ograničavanje štete: prava, poruke o pogreškama, provjera

Siguran kod prva je linija obrane. Daljnje razine ograničavaju štetu ako se ranjivost ipak previdi:

- **Minimalna prava:** Korisniku baze podataka web-stranice obično trebaju samo `SELECT`, `INSERT`, `UPDATE` i `DELETE` na vlastitoj bazi, bez prava za stvaranje korisnika ili pristup drugim bazama.
- **Bez poruka o pogreškama prema van:** `display_errors = Off` u php.ini. Pogreške baze podataka pripadaju u log, a ne u preglednik.
- **Razdvajanje po aplikaciji:** Svaka web-stranica dobiva vlastitog korisnika baze podataka.
- **Ovisnosti držati ažurnima:** Ranjivosti su i u bibliotekama, kako pokazuje vodič [composer audit](https://phpsos.de/hr/ratgeber/composer-abhaengigkeiten-pruefen/).
- **Dati provjeriti:** Pregled koda i automatizirana analiza pronalaze mnoga mjesta, a penetracijski test pokazuje što je izvana zaista dostupno.

```sql
-- Primjer: korisnik baze podataka samo s potrebnim pravima
CREATE USER 'shop_app'@'localhost' IDENTIFIED BY '<starkes Passwort>';
GRANT SELECT, INSERT, UPDATE, DELETE ON shop.* TO 'shop_app'@'localhost';
```

Srodna slabost tiče se izlaza podataka u pregledniku. Kako je osigurati, objašnjava vodič [Sprječavanje cross-site scriptinga u PHP-u](https://phpsos.de/hr/ratgeber/xss-php/).

## Kako PHP SOS konkretno pomaže?

- Sustavno provjeravamo pristupe bazi podataka Vaše aplikacije na nesigurne upite.
- Nalaze prioritiziramo prema dostupnosti i pogođenim podacima te pokazujemo konkretne ispravke.
- Prava baze podataka i obradu pogrešaka dovodimo na sigurnu razinu.
- Nakon ispravka ponovno testiramo i dokumentiramo rezultat.

Neovisnu provjeru opisuje usluga [Sigurnosni audit i penetracijski test](https://phpsos.de/hr/leistungen/sicherheitsaudit-penetrationstest/).


## Česta pitanja

**Je li dovoljno obraditi unose s addslashes ili real_escape_string?**  
Ne, to je podložno pogreškama. Escaping ovisi o skupu znakova i kontekstu te se lako zaboravi na jednom mjestu. Prepared statements u osnovi odvajaju naredbu i podatke pa su zato preporučeni put.

**Štiti li ORM automatski od SQL injectiona?**  
Uglavnom, sve dok koristite funkcije upita ORM-a. Čim pišete sirovi SQL ili umećete vrijednosti u sortiranja i nazive stupaca, vrijede ista pravila kao bez ORM-a.

**Pomaže li Web Application Firewall protiv SQL injectiona?**  
WAF može presresti poznate obrasce i dobiti vrijeme. Ne zamjenjuje, međutim, siguran kod jer se pravila temeljena na podacima zahtjeva mogu zaobići, a ponekad pogode i legitimne unose.

**Zatvara li PHP ažuriranje protiv PHP2Shella i SQL injection ranjivosti?**  
Ne. PHP2Shell pogađa PHP interpreter. SQL injection je pogreška u aplikacijskom kodu i ostaje bez obzira na to koja verzija PHP-a radi.

## Izvori

- [OWASP Top 10](https://owasp.org/www-project-top-ten/)
- [OWASP Cheat Sheet Series](https://cheatsheetseries.owasp.org/)
- [php.net: sigurnosne objave](https://www.php.net/)
