In-band SQLi (Classic)
Atakujacy używa tego samego kanału do przeprowadzenia ataku i odczytu wynikow. Najprostsza i najczestsza forma.
SQL Injection (SQLi) to jeden z najstarszych, ale wciaz najgrozniejszych atakow na aplikacje webowe. Polega na wstrzyknieciu złośliwego kodu SQL do zapytania bazodanowego poprzez dane wejsciowe użytkownika. Skuteczny atak SQLi może prowadzic do wycieku całej bazy danych, modyfikacji lub usuniecia danych, a nawet przejecia kontroli nad serwerem.
Według OWASP, SQL Injection od lat zajmuje czołowe miejsca wsrod najczestszych podatnosci aplikacji webowych, mimo że metody ochrony sa dobrze znane i łatwe do wdrozenia.
In-band SQLi (Classic)
Atakujacy używa tego samego kanału do przeprowadzenia ataku i odczytu wynikow. Najprostsza i najczestsza forma.
Blind SQLi
Wyniki nie sa bezposrednio widoczne. Atakujacy wnioskuje o strukturze bazy na podstawie zachowania aplikacji (Boolean-based lub Time-based).
Out-of-band SQLi
Dane sa eksfiltrowane przez inny kanał (np. DNS, HTTP). Stosowany gdy inne metody zawodza.
| Skutek | Opis |
|---|---|
| Wyciek danych | Kradziez loginow, haseł, danych osobowych, kart kredytowych |
| Modyfikacja danych | Zmiana sald, uprawnien, statusow zamowien |
| Usuniecie danych | DROP TABLE, DELETE - utrata danych |
| Obejscie autentykacji | Logowanie bez znajomosci hasła |
| Przejecie serwera | W skrajnych przypadkach - wykonanie komend systemowych |
| Szkody wizerunkowe | Utrata zaufania klientow, kary RODO |
Aplikacja buduje zapytanie SQL z danymi użytkownika
$query = "SELECT * FROM users WHERE username = '$username'";Atakujacy wprowadza złośliwe dane
Zamiast normalnej nazwy użytkownika wpisuje:
admin' OR '1'='1Powstaje zmodyfikowane zapytanie
SELECT * FROM users WHERE username = 'admin' OR '1'='1'Warunek ‘1’=‘1’ jest zawsze prawdziwy
Zapytanie zwraca wszystkich użytkowników lub omija autentykacje.
Formularz logowania:
<form method="POST"> <input name="username" placeholder="Login" /> <input name="password" type="password" placeholder="Hasło" /> <button type="submit">Zaloguj</button></form>Podatny kod PHP:
<?php$username = $_POST['username'];$password = $_POST['password'];
// NIEBEZPIECZNE - konkatenacja stringow$query = "SELECT * FROM users WHERE username = '$username' AND password = '$password'";
$result = $db->query($query);if ($result->rowCount() > 0) { echo "Zalogowano!";}Atak - w polu username wpisujemy:
admin'--Wynikowe zapytanie:
SELECT * FROM users WHERE username = 'admin'--' AND password = ''Znaki -- komentuja reszte zapytania, wiec warunek hasła jest ignorowany.
-- Union-based: odczyt danych z innej tabeli' UNION SELECT username, password FROM users--
-- Stacked queries: wykonanie dodatkowego zapytania'; DROP TABLE users;--
-- Time-based blind: wnioskowanie przez opoznienie' OR IF(1=1, SLEEP(5), 0)--
-- Boolean-based blind: wnioskowanie przez różna odpowiedz' AND 1=1-- (zwraca dane)' AND 1=2-- (nie zwraca danych)
-- Error-based: wyciaganie danych przez komunikaty błędów' AND EXTRACTVALUE(1, CONCAT(0x7e, (SELECT password FROM users LIMIT 1)))--<?php// NIEBEZPIECZNE$search = $_GET['q'];$query = "SELECT * FROM products WHERE name LIKE '%$search%'";$products = $db->query($query);Atak:
%' UNION SELECT id, username, password, email FROM users--<?php// BEZPIECZNE - Prepared Statements z PDO$search = $_GET['q'];
$stmt = $pdo->prepare("SELECT * FROM products WHERE name LIKE :search");$stmt->execute(['search' => '%' . $search . '%']);$products = $stmt->fetchAll();<?php// BEZPIECZNE$username = $_POST['username'];$password = $_POST['password'];
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = :username");$stmt->execute(['username' => $username]);$user = $stmt->fetch();
// Weryfikacja hasła osobno (z użyciem password_verify dla hashowanych haseł)if ($user && password_verify($password, $user['password_hash'])) { echo "Zalogowano!"; $_SESSION['user_id'] = $user['id'];} else { echo "Nieprawidłowe dane logowania";}<?php// BEZPIECZNE - MySQLi prepared statements$stmt = $mysqli->prepare("SELECT * FROM users WHERE id = ?");$stmt->bind_param("i", $userId); // "i" = integer, "s" = string$stmt->execute();$result = $stmt->get_result();// BEZPIECZNE - parametryzowane zapytanieconst mysql = require('mysql2/promise');
async function getUser(userId) { const connection = await mysql.createConnection({/* config */});
// Znak ? jest placeholderem const [rows] = await connection.execute( 'SELECT * FROM users WHERE id = ?', [userId] );
return rows[0];}CREATE PROCEDURE GetUser(IN userId INT)BEGIN SELECT * FROM users WHERE id = userId;END$stmt = $pdo->prepare("CALL GetUser(:id)");$stmt->execute(['id' => $userId]);<?php// Dodatkowa warstwa - walidacja typu$userId = filter_input(INPUT_GET, 'id', FILTER_VALIDATE_INT);if ($userId === false) { die('Nieprawidłowe ID');}
// Whitelist dla dozwolonych wartości$allowedSort = ['name', 'date', 'price'];$sort = in_array($_GET['sort'], $allowedSort) ? $_GET['sort'] : 'name';-- Użytkownik aplikacji nie powinien mieć pełnych uprawnienGRANT SELECT, INSERT, UPDATE ON app_db.* TO 'app_user'@'localhost';-- Bez: DROP, DELETE, ALTER, CREATE// Doctrine ORM - automatyczna ochrona$user = $entityManager->find(User::class, $userId);
// QueryBuilder$qb = $entityManager->createQueryBuilder();$qb->select('u') ->from(User::class, 'u') ->where('u.email = :email') ->setParameter('email', $email);SQL Injection pozostaje jednym z najgroźniejszych atakow mimo prostych metod ochrony. Kluczowe zasady:
Pamiętaj: Jeden podatny endpoint może prowadzic do wycieku całej bazy danych. Koszt wdrozenia prepared statements jest minimalny, a konsekwencje zaniedbania - ogromne.