SQL Prevence vstřikování
Injekce SQL je jednou z nejnebezpečnějších a nejběžnějších zranitelností webových aplikací. Tato příručka popisuje, jak chránit vaše moduly XOOPS před útoky injekcí SQL.
Související dokumentace
Sekce “Související dokumentace”- Bezpečnostní-Best-Practices - Komplexní bezpečnostní průvodce
- CSRF-Protection - Systém tokenů a třída XOOPSSecurity
- Input-Sanitization - MyTextSanitizer a validace
Pochopení SQL Injection
Sekce “Pochopení SQL Injection”Injekce SQL nastává, když je uživatelský vstup zahrnut přímo do dotazů SQL bez řádné dezinfekce nebo parametrizace.
Příklad zranitelného kódu
Sekce “Příklad zranitelného kódu”// DANGEROUS - DO NOT USE$id = $_GET['id'];$sql = "SELECT * FROM " . $xoopsDB->prefix('items') . " WHERE id = " . $id;$result = $xoopsDB->query($sql);Pokud uživatel předá 1 OR 1=1 jako ID, dotaz bude:
SELECT * FROM xoops_items WHERE id = 1 OR 1=1Tím se vrátí všechny záznamy namísto pouze jednoho.
Použití parametrizovaných dotazů
Sekce “Použití parametrizovaných dotazů”Nejúčinnější obranou proti injekci SQL je použití parametrizovaných dotazů (připravených příkazů).
Základní parametrizovaný dotaz
Sekce “Základní parametrizovaný dotaz”// Get database connection$xoopsDB = XOOPSDatabaseFactory::getDatabaseConnection();
// SECURE - Using parameterized query$sql = "SELECT * FROM " . $xoopsDB->prefix('mytable') . " WHERE id = ?";$result = $xoopsDB->query($sql, [(int)$_GET['id']]);Více parametrů
Sekce “Více parametrů”$sql = "SELECT * FROM " . $xoopsDB->prefix('mytable') . " WHERE username = ? AND status = ?";$result = $xoopsDB->query($sql, [$username, $status]);Pojmenované parametry
Sekce “Pojmenované parametry”Některé databázové abstrakce podporují pojmenované parametry:
$sql = "SELECT * FROM " . $xoopsDB->prefix('mytable') . " WHERE username = :username AND status = :status";$result = $xoopsDB->query($sql, [ ':username' => $username, ':status' => $status]);Použití XOOPSObject a XOOPSObjectHandler
Sekce “Použití XOOPSObject a XOOPSObjectHandler”XOOPS poskytuje objektově orientovaný přístup k databázi, který pomáhá zabránit vkládání SQL prostřednictvím systému Criteria.
Použití základních kritérií
Sekce “Použití základních kritérií”// Get the handler$itemHandler = xoops_getModuleHandler('item', 'mymodule');
// Create criteria$criteria = new Criteria('category_id', (int)$categoryId);
// Get objects - automatically safe from SQL injection$items = $itemHandler->getObjects($criteria);CriteriaCompo pro více podmínek
Sekce “CriteriaCompo pro více podmínek”$criteria = new CriteriaCompo();$criteria->add(new Criteria('category_id', (int)$categoryId));$criteria->add(new Criteria('status', 'published'));$criteria->add(new Criteria('uid', (int)$userId));
// Optional: Add ordering and limits$criteria->setSort('created');$criteria->setOrder('DESC');$criteria->setLimit(10);$criteria->setStart(0);
$items = $itemHandler->getObjects($criteria);Operátoři kritérií
Sekce “Operátoři kritérií”// Equal (default)$criteria->add(new Criteria('status', 'active'));
// Not equal$criteria->add(new Criteria('status', 'deleted', '!='));
// Greater than$criteria->add(new Criteria('count', 100, '>'));
// Less than or equal$criteria->add(new Criteria('price', 50, '<='));
// LIKE (for partial matching)$criteria->add(new Criteria('title', '%' . $searchTerm . '%', 'LIKE'));
// IN (multiple values)$criteria->add(new Criteria('id', '(' . implode(',', $ids) . ')', 'IN'));NEBO Podmínky
Sekce “NEBO Podmínky”$criteria = new CriteriaCompo();$criteria->add(new Criteria('status', 'published'));
// OR condition$orCriteria = new CriteriaCompo();$orCriteria->add(new Criteria('uid', (int)$userId), 'OR');$orCriteria->add(new Criteria('is_public', 1), 'OR');
$criteria->add($orCriteria);Předpony tabulky
Sekce “Předpony tabulky”Vždy používejte systém předpon tabulky XOOPS:
// Correct - using prefix$table = $xoopsDB->prefix('mytable');$sql = "SELECT * FROM {$table} WHERE id = ?";
// Also correct$sql = "SELECT * FROM " . $xoopsDB->prefix('mytable') . " WHERE id = ?";Dotazy INSERT
Sekce “Dotazy INSERT”Použití připravených výpisů
Sekce “Použití připravených výpisů”$sql = "INSERT INTO " . $xoopsDB->prefix('mytable') . " (title, content, uid, created) VALUES (?, ?, ?, ?)";
$result = $xoopsDB->query($sql, [ $title, $content, (int)$userId, time()]);
if ($result) { $newId = $xoopsDB->getInsertId();}Použití XOOPSObject
Sekce “Použití XOOPSObject”// Create new object$item = $itemHandler->create();
// Set values - handler escapes automatically$item->setVar('title', $title);$item->setVar('content', $content);$item->setVar('uid', (int)$userId);$item->setVar('created', time());
// Insertif ($itemHandler->insert($item)) { $newId = $item->getVar('itemid');}Dotazy UPDATE
Sekce “Dotazy UPDATE”Použití připravených výpisů
Sekce “Použití připravených výpisů”$sql = "UPDATE " . $xoopsDB->prefix('mytable') . " SET title = ?, content = ?, updated = ? WHERE id = ?";
$result = $xoopsDB->query($sql, [ $title, $content, time(), (int)$id]);Použití XOOPSObject
Sekce “Použití XOOPSObject”// Get existing object$item = $itemHandler->get((int)$id);
if ($item) { $item->setVar('title', $title); $item->setVar('content', $content); $item->setVar('updated', time());
$itemHandler->insert($item);}Dotazy DELETE
Sekce “Dotazy DELETE”Použití připravených výpisů
Sekce “Použití připravených výpisů”$sql = "DELETE FROM " . $xoopsDB->prefix('mytable') . " WHERE id = ?";$result = $xoopsDB->query($sql, [(int)$id]);Použití XOOPSObject
Sekce “Použití XOOPSObject”$item = $itemHandler->get((int)$id);if ($item) { $itemHandler->delete($item);}Hromadné mazání s kritérii
Sekce “Hromadné mazání s kritérii”$criteria = new Criteria('status', 'deleted');$itemHandler->deleteAll($criteria);Útěk, když je to nutné
Sekce “Útěk, když je to nutné”Pokud nemůžete použít připravené příkazy, použijte správné escapování:
// Using mysqli_real_escape_string$safe_value = $xoopsDB->escape($value);$sql = "SELECT * FROM " . $xoopsDB->prefix('mytable') . " WHERE title = '" . $safe_value . "'";Před escapováním však vždy preferujte připravené výpisy.
Bezpečné vytváření dynamických dotazů
Sekce “Bezpečné vytváření dynamických dotazů”Názvy bezpečných dynamických sloupců
Sekce “Názvy bezpečných dynamických sloupců”Názvy sloupců nelze parametrizovat. Ověření proti bílé listině:
$allowed_columns = ['title', 'created', 'updated', 'status'];$sort = $_GET['sort'] ?? 'created';
if (!in_array($sort, $allowed_columns)) { $sort = 'created'; // Default safe value}
$sql = "SELECT * FROM " . $xoopsDB->prefix('mytable') . " ORDER BY {$sort} DESC";Bezpečné názvy dynamických tabulek
Sekce “Bezpečné názvy dynamických tabulek”Podobně ověřte názvy tabulek:
$allowed_tables = ['items', 'categories', 'comments'];$table = $_GET['table'] ?? 'items';
if (!in_array($table, $allowed_tables)) { $table = 'items';}
$sql = "SELECT * FROM " . $xoopsDB->prefix($table) . " WHERE id = ?";Dynamické vytváření klauzulí WHERE
Sekce “Dynamické vytváření klauzulí WHERE”$criteria = new CriteriaCompo();
// Add conditions based on inputif (!empty($_GET['category'])) { $criteria->add(new Criteria('category_id', (int)$_GET['category']));}
if (!empty($_GET['status'])) { $allowed_statuses = ['draft', 'published', 'archived']; if (in_array($_GET['status'], $allowed_statuses)) { $criteria->add(new Criteria('status', $_GET['status'])); }}
if (!empty($_GET['search'])) { $search = '%' . $_GET['search'] . '%'; $criteria->add(new Criteria('title', $search, 'LIKE'));}
$items = $itemHandler->getObjects($criteria);Dotazy LIKE
Sekce “Dotazy LIKE”Buďte opatrní s dotazy LIKE, abyste se vyhnuli vložení zástupných znaků:
// Escape special characters in search term$searchTerm = str_replace(['%', '_'], ['\%', '\_'], $searchTerm);
// Then use in LIKE$criteria->add(new Criteria('title', '%' . $searchTerm . '%', 'LIKE'));Klauzule IN
Sekce “Klauzule IN”Při použití klauzulí IN se ujistěte, že jsou všechny hodnoty správně napsány:
// Array of IDs from user input$inputIds = $_POST['ids'] ?? [];
// Sanitize: ensure all are integers$safeIds = array_map('intval', $inputIds);$safeIds = array_filter($safeIds, function($id) { return $id > 0; });
if (!empty($safeIds)) { $idList = implode(',', $safeIds); $sql = "SELECT * FROM " . $xoopsDB->prefix('mytable') . " WHERE id IN ({$idList})"; $result = $xoopsDB->query($sql);}Nebo s kritérii:
if (!empty($safeIds)) { $criteria = new Criteria('id', '(' . implode(',', $safeIds) . ')', 'IN'); $items = $itemHandler->getObjects($criteria);}Bezpečnost transakcí
Sekce “Bezpečnost transakcí”Při provádění více souvisejících dotazů:
// Start transaction$xoopsDB->query("START TRANSACTION");
try { // Query 1 $sql1 = "INSERT INTO " . $xoopsDB->prefix('items') . " (title) VALUES (?)"; $result1 = $xoopsDB->query($sql1, [$title]);
if (!$result1) { throw new Exception('Insert failed'); }
$itemId = $xoopsDB->getInsertId();
// Query 2 $sql2 = "INSERT INTO " . $xoopsDB->prefix('item_meta') . " (item_id, meta_key, meta_value) VALUES (?, ?, ?)"; $result2 = $xoopsDB->query($sql2, [$itemId, 'author', $author]);
if (!$result2) { throw new Exception('Meta insert failed'); }
// Commit $xoopsDB->query("COMMIT");
} catch (Exception $e) { // Rollback on error $xoopsDB->query("ROLLBACK"); throw $e;}Zpracování chyb
Sekce “Zpracování chyb”Nikdy uživatelům nevystavujte chyby SQL:
$sql = "SELECT * FROM " . $xoopsDB->prefix('mytable') . " WHERE id = ?";$result = $xoopsDB->query($sql, [(int)$id]);
if (!$result) { // Log the actual error for debugging error_log('Database error: ' . $xoopsDB->error());
// Show generic message to user redirect_header('index.php', 3, 'An error occurred. Please try again.'); exit();}Běžné chyby, kterým je třeba se vyhnout
Sekce “Běžné chyby, kterým je třeba se vyhnout”Chyba 1: Přímá proměnná interpolace
Sekce “Chyba 1: Přímá proměnná interpolace”// WRONG$sql = "SELECT * FROM {$table} WHERE id = {$id}";
// RIGHT$sql = "SELECT * FROM " . $xoopsDB->prefix('mytable') . " WHERE id = ?";$result = $xoopsDB->query($sql, [(int)$id]);Chyba 2: Použití lomítek ()
Sekce “Chyba 2: Použití lomítek ()”// WRONG - addslashes is NOT sufficient$safe = addslashes($_GET['input']);
// RIGHT - use parameterized queries or proper escaping$sql = "SELECT * FROM table WHERE col = ?";$result = $xoopsDB->query($sql, [$_GET['input']]);Chyba 3: Důvěřování číselným ID
Sekce “Chyba 3: Důvěřování číselným ID”// WRONG - assuming input is numeric$id = $_GET['id'];$sql = "SELECT * FROM table WHERE id = " . $id;
// RIGHT - explicitly cast to integer$id = (int)$_GET['id'];$sql = "SELECT * FROM table WHERE id = ?";$result = $xoopsDB->query($sql, [$id]);Chyba 4: Injekce druhého řádu
Sekce “Chyba 4: Injekce druhého řádu”// Data from database is NOT automatically safe$userData = $itemHandler->get($id);$username = $userData->getVar('username');
// WRONG - trusting data from database$sql = "SELECT * FROM log WHERE username = '" . $username . "'";
// RIGHT - always use parameters$sql = "SELECT * FROM log WHERE username = ?";$result = $xoopsDB->query($sql, [$username]);Testování zabezpečení
Sekce “Testování zabezpečení”Otestujte své dotazy
Sekce “Otestujte své dotazy”Otestujte své formuláře pomocí těchto vstupů, abyste zkontrolovali vstřikování SQL:
' OR '1'='11; DROP TABLE users--1 UNION SELECT * FROM users--admin'--' OR 1=1#
Pokud některá z těchto příčin způsobí neočekávané chování nebo chyby, jedná se o chybu zabezpečení.
Automatické testování
Sekce “Automatické testování”Během vývoje používejte automatizované nástroje pro testování vstřikování SQL:
- SQLMap
- Burp Suite
- OWASP ZAP
Shrnutí osvědčených postupů
Sekce “Shrnutí osvědčených postupů”- Vždy používejte parametrizované dotazy (připravené příkazy)
- Pokud je to možné, použijte XOOPSObject/XOOPSObjectHandler
- Použijte třídy Criteria pro vytváření dotazů
- Povolené hodnoty na seznamu povolených hodnot pro názvy sloupců a tabulek
- Přenášejte číselné hodnoty explicitně pomocí
(int)nebo(float) - Nikdy nevystavujte uživatelům chyby databáze
- Použijte transakce pro více souvisejících dotazů
- Test pro vstřikování SQL během vývoje
- Escape LIKE zástupné znaky ve vyhledávacích dotazech
- Dezinfikujte hodnoty klauzule IN jednotlivě
#security #sql-injection #database #xoops #prepared-statements #Criteria