Zapobieganie wstrzykiwaniu SQL
Wstrzykiwanie SQL jest jedną z najpotężniejszych i najpowszechniejszych luk bezpieczeństwa w aplikacjach internetowych. Ten przewodnik obejmuje ochronę modułów XOOPS przed atakami SQL injection.
Dokumentacja pokrewna
Dział zatytułowany „Dokumentacja pokrewna”- Security-Best-Practices - Kompleksowy przewodnik bezpieczeństwa
- CSRF-Protection - System tokenów i klasa XoopsSecurity
- Input-Sanitization - MyTextSanitizer i walidacja
Zrozumienie wstrzykiwania SQL
Dział zatytułowany „Zrozumienie wstrzykiwania SQL”Wstrzykiwanie SQL występuje, gdy dane wejściowe użytkownika są zawarte bezpośrednio w zapytaniach SQL bez prawidłowej sanityzacji lub parametryzacji.
Przykład podatnego kodu
Dział zatytułowany „Przykład podatnego kodu”// DANGEROUS - DO NOT USE$id = $_GET['id'];$sql = "SELECT * FROM " . $xoopsDB->prefix('items') . " WHERE id = " . $id;$result = $xoopsDB->query($sql);Jeśli użytkownik przesle 1 OR 1=1 jako ID, zapytanie staje się:
SELECT * FROM xoops_items WHERE id = 1 OR 1=1To zwraca wszystkie rekordy zamiast tylko jednego.
Korzystanie z zapytań sparametryzowanych
Dział zatytułowany „Korzystanie z zapytań sparametryzowanych”Najskuteczniejszą obroną przed wstrzykiwaniem SQL jest użycie zapytań sparametryzowanych (instrukcji przygotowanych).
Podstawowe zapytanie sparametryzowane
Dział zatytułowany „Podstawowe zapytanie sparametryzowane”// 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']]);Wiele parametrów
Dział zatytułowany „Wiele parametrów”$sql = "SELECT * FROM " . $xoopsDB->prefix('mytable') . " WHERE username = ? AND status = ?";$result = $xoopsDB->query($sql, [$username, $status]);Parametry nazwane
Dział zatytułowany „Parametry nazwane”Niektóre abstrakcje baz danych obsługują parametry nazwane:
$sql = "SELECT * FROM " . $xoopsDB->prefix('mytable') . " WHERE username = :username AND status = :status";$result = $xoopsDB->query($sql, [ ':username' => $username, ':status' => $status]);Korzystanie z XoopsObject i XoopsObjectHandler
Dział zatytułowany „Korzystanie z XoopsObject i XoopsObjectHandler”XOOPS zapewnia dostęp do bazy danych zorientowany obiektowo, który pomaga zapobiegać wstrzykiwaniu SQL poprzez system Criteria.
Podstawowe użycie Criteria
Dział zatytułowany „Podstawowe użycie Criteria”// 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 dla wielu warunków
Dział zatytułowany „CriteriaCompo dla wielu warunków”$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);Operatory Criteria
Dział zatytułowany „Operatory Criteria”// 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'));Warunki OR
Dział zatytułowany „Warunki OR”$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);Prefiksy tabel
Dział zatytułowany „Prefiksy tabel”Zawsze używaj systemu prefiksu tabel XOOPS:
// Correct - using prefix$table = $xoopsDB->prefix('mytable');$sql = "SELECT * FROM {$table} WHERE id = ?";
// Also correct$sql = "SELECT * FROM " . $xoopsDB->prefix('mytable') . " WHERE id = ?";Zapytania INSERT
Dział zatytułowany „Zapytania INSERT”Korzystanie z instrukcji przygotowanych
Dział zatytułowany „Korzystanie z instrukcji przygotowanych”$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();}Korzystanie z XoopsObject
Dział zatytułowany „Korzystanie z 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');}Zapytania UPDATE
Dział zatytułowany „Zapytania UPDATE”Korzystanie z instrukcji przygotowanych
Dział zatytułowany „Korzystanie z instrukcji przygotowanych”$sql = "UPDATE " . $xoopsDB->prefix('mytable') . " SET title = ?, content = ?, updated = ? WHERE id = ?";
$result = $xoopsDB->query($sql, [ $title, $content, time(), (int)$id]);Korzystanie z XoopsObject
Dział zatytułowany „Korzystanie z 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);}Zapytania DELETE
Dział zatytułowany „Zapytania DELETE”Korzystanie z instrukcji przygotowanych
Dział zatytułowany „Korzystanie z instrukcji przygotowanych”$sql = "DELETE FROM " . $xoopsDB->prefix('mytable') . " WHERE id = ?";$result = $xoopsDB->query($sql, [(int)$id]);Korzystanie z XoopsObject
Dział zatytułowany „Korzystanie z XoopsObject”$item = $itemHandler->get((int)$id);if ($item) { $itemHandler->delete($item);}Hurtowe usuwanie z Criteria
Dział zatytułowany „Hurtowe usuwanie z Criteria”$criteria = new Criteria('status', 'deleted');$itemHandler->deleteAll($criteria);Escapowanie, gdy jest konieczne
Dział zatytułowany „Escapowanie, gdy jest konieczne”Jeśli nie możesz użyć instrukcji przygotowanych, użyj prawidłowego escapowania:
// Using mysqli_real_escape_string$safe_value = $xoopsDB->escape($value);$sql = "SELECT * FROM " . $xoopsDB->prefix('mytable') . " WHERE title = '" . $safe_value . "'";Jednak zawsze preferuj instrukcje przygotowane zamiast escapowania.
Bezpieczne budowanie dynamicznych zapytań
Dział zatytułowany „Bezpieczne budowanie dynamicznych zapytań”Bezpieczne dynamiczne nazwy kolumn
Dział zatytułowany „Bezpieczne dynamiczne nazwy kolumn”Nazwy kolumn nie mogą być parametryzowane. Waliduj względem listy dozwolonych:
$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";Bezpieczne dynamiczne nazwy tabel
Dział zatytułowany „Bezpieczne dynamiczne nazwy tabel”Podobnie waliduj nazwy tabel:
$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 = ?";Budowanie klauzul WHERE dynamicznie
Dział zatytułowany „Budowanie klauzul WHERE dynamicznie”$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);Zapytania LIKE
Dział zatytułowany „Zapytania LIKE”Bądź ostrożny z zapytaniami LIKE, aby uniknąć wstrzykiwania symboli wieloznacznych:
// Escape special characters in search term$searchTerm = str_replace(['%', '_'], ['\%', '\_'], $searchTerm);
// Then use in LIKE$criteria->add(new Criteria('title', '%' . $searchTerm . '%', 'LIKE'));Klauzule IN
Dział zatytułowany „Klauzule IN”Podczas korzystania z klauzul IN, upewnij się, że wszystkie wartości są prawidłowo wpisane:
// 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);}Lub z Criteria:
if (!empty($safeIds)) { $criteria = new Criteria('id', '(' . implode(',', $safeIds) . ')', 'IN'); $items = $itemHandler->getObjects($criteria);}Bezpieczeństwo transakcji
Dział zatytułowany „Bezpieczeństwo transakcji”Podczas wykonywania wielu powiązanych zapytań:
// 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;}Obsługa błędów
Dział zatytułowany „Obsługa błędów”Nigdy nie ujawniaj błędów SQL użytkownikom:
$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();}Typowe błędy do uniknięcia
Dział zatytułowany „Typowe błędy do uniknięcia”Błąd 1: Bezpośrednia interpolacja zmiennych
Dział zatytułowany „Błąd 1: Bezpośrednia interpolacja zmiennych”// WRONG$sql = "SELECT * FROM {$table} WHERE id = {$id}";
// RIGHT$sql = "SELECT * FROM " . $xoopsDB->prefix('mytable') . " WHERE id = ?";$result = $xoopsDB->query($sql, [(int)$id]);Błąd 2: Użycie addslashes()
Dział zatytułowany „Błąd 2: Użycie addslashes()”// 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']]);Błąd 3: Ufanie numerycznym ID
Dział zatytułowany „Błąd 3: Ufanie numerycznym 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]);Błąd 4: Wstrzykiwanie drugiego rzędu
Dział zatytułowany „Błąd 4: Wstrzykiwanie drugiego rzę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]);Testy bezpieczeństwa
Dział zatytułowany „Testy bezpieczeństwa”Testuj swoje zapytania
Dział zatytułowany „Testuj swoje zapytania”Testuj swoje formularze za pomocą tych danych wejściowych, aby sprawdzić wstrzykiwanie SQL:
' OR '1'='11; DROP TABLE users--1 UNION SELECT * FROM users--admin'--' OR 1=1#
Jeśli którykolwiek z nich powoduje nieoczekiwane zachowanie lub błędy, masz podatność.
Testy automatyczne
Dział zatytułowany „Testy automatyczne”Używaj zautomatyzowanych narzędzi do testowania wstrzykiwania SQL podczas opracowywania:
- SQLMap
- Burp Suite
- OWASP ZAP
Streszczenie najlepszych praktyk
Dział zatytułowany „Streszczenie najlepszych praktyk”- Zawsze używaj zapytań sparametryzowanych (instrukcji przygotowanych)
- Używaj XoopsObject/XoopsObjectHandler, gdy jest to możliwe
- Używaj klas Criteria do budowania zapytań
- Lista dozwolonych wartości dla nazw kolumn i tabel
- Rzutuj wartości numeryczne jawnie za pomocą
(int)lub(float) - Nigdy nie ujawniaj błędów bazy danych użytkownikom
- Używaj transakcji dla wielu powiązanych zapytań
- Testuj wstrzykiwanie SQL podczas opracowywania
- Escapuj symbole wieloznaczne LIKE w zapytaniach wyszukiwania
- Sanityzuj wartości klauzuli IN indywidualnie
#security #sql-injection #database #xoops #prepared-statements #Criteria