⏱️ Doba čtení: ~10 minut | 📅 Aktualizováno: 17. prosince 2025
SQL Injection zůstává v OWASP Top 10 již více než 20 let. V listopadu 2025 OWASP zveřejnil aktualizovaný žebříček, kde Injection zaujímá 5. místo (A05:2025). FBI a CISA oficiálně označily SQLi jako "unforgivable defect" — zranitelnost, která by v moderním softwaru neměla existovat.
Ale existuje. V Hostiserveru to vidíme pravidelně: klienti přicházejí po napadení, s poškozenými databázemi, s úniky uživatelských dat. Ve většině případů je příčinou chybějící prepared statements nebo zastaralý kód bez validace.
Tento průvodce není teoretický přehled. Sestavili jsme konfigurace a přístupy, které skutečně používáme na managed serverech Hostiserver: od nastavení ModSecurity po MySQL hardening. Vše ověřeno v praxi.
⚠️ Důležité: Pokud váš web přijímá jakýkoli uživatelský vstup (formuláře, vyhledávání, filtry, URL parametry) — je potenciálně zranitelný. I "jednoduchý blog" na WordPressu se může stát obětí kvůli zranitelnému pluginu.
SQL Injection je technika útoku, při které útočník vkládá škodlivý SQL kód do vstupních polí. Pokud aplikace nevaliduje vstup, tento kód se spustí na databázovém serveru.
// ❌ NEBEZPEČNÉ — nikdy to nedělejte!
$username = $_POST['username'];
$query = "SELECT * FROM users WHERE username = '$username'";
$result = mysqli_query($conn, $query);
Pokud útočník zadá do pole username:
' OR '1'='1' --
Dotaz se změní na:
SELECT * FROM users WHERE username = '' OR '1'='1' --'
Výsledek: útočník získá přístup ke všem záznamům v tabulce.
| Typ | Mechanismus | Obtížnost detekce |
|---|---|---|
| Classic (In-band) | Výsledek viditelný na stránce | Nízká |
| Union-based | UNION vytahuje data z jiných tabulek | Nízká |
| Error-based | Data přes chybové zprávy | Střední |
| Blind SQLi | Žádný viditelný výsledek, "hádání" | Vysoká |
| Time-based Blind | SLEEP() určuje pravdivost podmínek | Vysoká |
| Out-of-band | Data jdou na externí server | Velmi vysoká |
SQLi není archaický problém. Kritické zranitelnosti se nacházejí i v moderním enterprise softwaru. Zde je několik příkladů, které sledujeme:
Ironický případ: SQL Injection v samotném WAF. CVSS 9.6 Critical. Zranitelnost umožňovala neautentizovanému útočníkovi spouštět SQL příkazy přes HTTP požadavky. (Zdroj informací).
Kritická zranitelnost v escapovacích funkcích PostgreSQL. CVSS 8.1 High. Obejití prepared statements přes nesprávné zpracování multibyte znaků. Postihla všechny verze do 17.3. (Zdroj informací).
CVSS 9.9 Critical. Jakýkoli uživatel s API přístupem mohl využít SQLi a eskalovat oprávnění. Zabbix používají tisíce společností pro monitoring infrastruktury. (Zdroj informací).
Tento případ je stále zmiňován jako ukázka rozsahu problému. SQLi vedla ke kompromitaci více než 2 500 organizací. (Zdroj informací).
Prepared Statements (parametrizované dotazy) jsou nejúčinnější ochranou proti SQL Injection. Oddělují SQL kód od dat. Doporučujeme to jako základní standard pro všechny projekty.
// ✅ BEZPEČNÉ — PDO s prepared statements
$pdo = new PDO('mysql:host=localhost;dbname=app_db;charset=utf8mb4', $user, $pass);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); // Důležité!
$stmt = $pdo->prepare('SELECT * FROM users WHERE username = :username AND status = :status');
$stmt->execute([
':username' => $username,
':status' => 'active'
]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);
// ✅ BEZPEČNÉ — MySQLi s prepared statements
$mysqli = new mysqli('localhost', $user, $pass, 'app_db');
$mysqli->set_charset('utf8mb4');
$stmt = $mysqli->prepare('SELECT * FROM users WHERE username = ? AND status = ?');
$stmt->bind_param('ss', $username, $status);
$stmt->execute();
$result = $stmt->get_result();
$user = $result->fetch_assoc();
Při použití prepared statements:
' OR '1'='1 se stane pouhým řetězcem✅ Naše doporučení: VŽDY používejte prepared statements pro SQL dotazy s uživatelským vstupem. Bez výjimek.
V únoru 2025 bylo zjištěno, že i prepared statements mohou být obejity při nesprávném zpracování multibyte znaků v PostgreSQL. Řešení je jednoduché: udržujte software aktuální (PostgreSQL 17.3+, 16.7+, 15.11+).
Moderní frameworky mají ochranu proti SQL Injection "z krabice". Na serverech Hostiserver podporujeme všechny populární frameworky — Laravel, Django, Node.js stack.
// ✅ BEZPEČNÉ — Eloquent automaticky parametrizuje
$users = User::where('username', $username)
->where('status', 'active')
->get();
// ✅ BEZPEČNÉ — Query Builder
$users = DB::table('users')
->where('username', $username)
->get();
// ❌ NEBEZPEČNÉ — raw dotazy bez bindings
// DB::select("SELECT * FROM users WHERE username = '$username'");
# ✅ BEZPEČNÉ — Django ORM
users = User.objects.filter(username=username, status='active')
# ✅ BEZPEČNÉ — raw dotaz s parametry
users = User.objects.raw('SELECT * FROM users WHERE username = %s', [username])
# ❌ NEBEZPEČNÉ — string formatting
# User.objects.raw(f"SELECT * FROM users WHERE username = '{username}'")
// ✅ BEZPEČNÉ — Sequelize
const users = await User.findAll({
where: { username: username, status: 'active' }
});
// ✅ BEZPEČNÉ — Prisma
const users = await prisma.user.findMany({
where: { username: username, status: 'active' }
});
⚠️ Pozor: ORM chrání pouze při správném použití. Raw SQL uvnitř ORM může být stále zranitelné. Často to vidíme při auditu klientských projektů.
Validace je dodatečná ochrana, ne náhrada za prepared statements. Doporučujeme aplikovat oba přístupy současně.
| Typ | Popis | Příklad |
|---|---|---|
| Whitelist | Povolujeme pouze očekávané hodnoty | Řazení: pouze 'asc' nebo 'desc' |
| Type casting | Vynucená konverze typu | $id = (int) $_GET['id']; |
| Validace formátu | Kontrola formátu dat | Email, datum, UUID |
| Omezení délky | Omezení délky | Username: max 50 znaků |
// ✅ Whitelist pro řazení (ORDER BY nelze parametrizovat)
$allowed_columns = ['created_at', 'username', 'email'];
$sort_column = in_array($_GET['sort'], $allowed_columns) ? $_GET['sort'] : 'created_at';
$allowed_directions = ['ASC', 'DESC'];
$sort_dir = in_array(strtoupper($_GET['dir']), $allowed_directions) ? strtoupper($_GET['dir']) : 'DESC';
// ✅ Type casting pro ID
$user_id = filter_var($_GET['id'], FILTER_VALIDATE_INT);
if ($user_id === false) {
throw new InvalidArgumentException('Invalid user ID');
}
// ✅ Regex pro specifické formáty
if (!preg_match('/^[a-zA-Z0-9_]{3,20}$/', $username)) {
throw new InvalidArgumentException('Invalid username format');
}
💡 Z naší zkušenosti: Whitelist je vždy lepší než Blacklist. Místo blokování nebezpečných znaků — povolujte pouze očekávané.
WAF analyzuje HTTP požadavky a blokuje podezřelé vzory dříve, než se dostanou k aplikaci. Obzvláště důležité pro legacy kód, který je obtížné přepsat.
| Řešení | Úroveň | Použití |
|---|---|---|
| ModSecurity | Serverový (Apache/Nginx) | Hluboká inspekce požadavků |
| Cloudflare WAF | DNS proxy | Edge protection, DDoS |
Zde je příklad pravidel, která konfigurujeme pro klienty:
# Pravidlo 1: Detekce SQLi vzorů
SecRule ARGS|REQUEST_BODY \
"@rx (?i)(union\s+select|sleep\(|benchmark\(|or\s+1=1)" \
"id:1001002,phase:2,pass,log,tag:'attack-sqli',setvar:'tx.inbound_anomaly_score=+5',msg:'SQLi pattern detected'"
# Pravidlo 2: Blokování při překročení anomaly score
SecRule TX:INBOUND_ANOMALY_SCORE "@ge 5" \
"id:1001099,phase:2,deny,status:403,log,msg:'Inbound anomaly score exceeded'"
UNION SELECT — vytahování dat z jiných tabulekSLEEP() — time-based blind SQLiBENCHMARK() — alternativní time-based útokOR 1=1 — klasická boolean injectionPro komplexní ochranu instalujeme OWASP CRS — sadu pravidel pokrývající SQLi, XSS, LFI a další útoky:
# Instalace OWASP CRS pro Apache
sudo apt install libapache2-mod-security2
sudo mv /etc/modsecurity/modsecurity.conf-recommended /etc/modsecurity/modsecurity.conf
# Stažení CRS
cd /etc/modsecurity
sudo git clone https://github.com/coreruleset/coreruleset.git
sudo cp coreruleset/crs-setup.conf.example coreruleset/crs-setup.conf
✅ Náš přístup: Kombinujeme ModSecurity na serveru s Cloudflare WAF. Cloudflare blokuje většinu útoků na edge, ModSecurity zachytí to, co projde.
I když útočník najde SQLi, správně nakonfigurovaná databáze minimalizuje škody. Zde je, co děláme na managed serverech Hostiserver.
# /etc/mysql/mysql.conf.d/mysqld.cnf
# MySQL naslouchá pouze na localhost — KRITICKÉ!
bind-address = 127.0.0.1
# Port 3306 NENÍ otevřený do veřejného internetu
# Přístup povolen pouze z localhost nebo konkrétních IP přes firewall
Každá aplikace dostává samostatného uživatele s minimálními právy:
-- ✅ Samostatný uživatel pro aplikaci
CREATE USER 'app_user'@'localhost' IDENTIFIED BY 'STRONG_RANDOM_PASSWORD';
-- Pouze potřebná práva na konkrétní databázi
GRANT SELECT, INSERT, UPDATE, DELETE ON app_db.* TO 'app_user'@'localhost';
-- ❌ NEDÁVAT: GRANT ALL ON *.*
-- ❌ NEDÁVAT: SUPER, FILE, PROCESS, SHUTDOWN
FLUSH PRIVILEGES;
| Co děláme | Jak |
|---|---|
| Odstraňujeme anonymní uživatele | DELETE FROM mysql.user WHERE User=''; |
| Root pouze lokálně | Zákaz root@'%' |
| Silná hesla | Password policy, min. 16 znaků |
| Izolace databází | Uživatel vidí pouze svou DB |
| TLS pro remote | REQUIRE SSL |
# /etc/mysql/mysql.conf.d/mysqld.cnf
max_connections = 150
max_user_connections = 50
# Timeouty — uzavření idle spojení
wait_timeout = 300
interactive_timeout = 300
⚠️ Proč je to důležité: Pokud útočník najde SQLi, je omezen právy app_user. Bez FILE — nemůže zapisovat soubory. Bez SUPER — nemůže měnit konfiguraci serveru.
Preventivní ochrana je důležitá, ale musíte také vidět, co se děje v reálném čase.
# /etc/mysql/mysql.conf.d/mysqld.cnf
# Error log — vždy zapnutý
log_error = /var/log/mysql/error.log
# Slow query log — detekce podezřelých dotazů
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 2
Pro compliance a forensics používáme:
# MariaDB Audit Plugin
INSTALL SONAME 'server_audit';
SET GLOBAL server_audit_logging = ON;
SET GLOBAL server_audit_events = 'CONNECT,QUERY,TABLE';
Pro enterprise klienty nabízíme integraci s Elastic Security (ELK SIEM) — centralizovaný sběr logů, korelace událostí, automatická detekce hrozeb.
💡 Tip: Slow query log není jen o výkonu. Abnormálně pomalé dotazy mohou indikovat time-based SQLi (SLEEP, BENCHMARK).
Zastaralé verze PHP a MySQL mají známé zranitelnosti. Zde je, co doporučujeme klientům:
| Verze | Status | Naše doporučení |
|---|---|---|
| PHP 8.4 | ✅ Active Support | Nejlepší volba |
| PHP 8.3 | ✅ Active Support | Doporučeno |
| PHP 8.2 | ⚠️ Security Only | Minimální verze |
| PHP 8.1 a nižší | ❌ End of Life | Urgentně aktualizujte! |
| Verze | Status | Naše doporučení |
|---|---|---|
| MySQL 8.4 LTS | ✅ Long Term Support | Nejlepší volba |
| MySQL 8.0.3x+ | ✅ Active Support | Doporučeno |
| MariaDB 10.11 LTS | ✅ Long Term Support | Doporučeno |
| MySQL 5.7 | ❌ End of Life | Kritické riziko! |
Pokud používáte PostgreSQL — aktualizujte na verze 17.3+, 16.7+, 15.11+, 14.16+ nebo 13.19+.
🔴 Kritické: MySQL 5.7 a PHP 7.x již nedostávají bezpečnostní aktualizace. Pokud jste na těchto verzích — kontaktujte nás, pomůžeme s migrací.
ATTR_EMULATE_PREPARES = falsebind-address = 127.0.0.1Můžeme provést audit vašeho projektu, nakonfigurovat WAF, hardening databáze a monitoring.
WordPress core používá $wpdb->prepare() a je dobře chráněný. Ale pluginy a šablony jsou jiný příběh. Z naší zkušenosti většina napadení WordPressu probíhá přes zranitelné pluginy.
Doporučujeme: Používejte ověřené pluginy, pravidelně aktualizujte, nainstalujte WAF.
Prepared statements chrání před injekcí hodnot. Ale některé elementy nelze parametrizovat: názvy tabulek, ORDER BY, LIMIT. Pro ty — whitelist validace.
Také zvažte CVE-2025-1094: PostgreSQL měl zranitelnost v samotných escapovacích funkcích. Udržujte software aktuální.
Ne. WAF je důležitá vrstva, ale ne všelék. CVE-2025-25257 ukázal, že i Fortinet FortiWeb (samotný WAF!) měl kritickou SQLi zranitelnost.
Správný přístup: prepared statements + validace + WAF + database hardening.
Obě jsou bezpečné při správném použití:
Důležité pro PDO: ATTR_EMULATE_PREPARES = false.
Nástroje:
Důležité: Testujte pouze vlastní weby. Testování cizích bez povolení je trestný čin.
Pokud potřebujete pomoc s incident response — kontaktujte naši podporu.
Ano. Izolace je kritický element. Pokud je napaden jeden web — ostatní databáze zůstanou chráněny. Toto nastavujeme standardně na všech managed serverech.