Лекція 14. SQL ін’єкції, оператори DDL та DCL, транзакції
1. SQL ін’єкція
SQL ін’єкція – один з поширених способів злому сайтів та програм, що працюють з базами даних, заснований на впровадженні в запит довільного SQL-коду.
Впровадження SQL, залежно від типу СКБД та умов впровадження, може дати можливість атакуючому виконати довільний запит до бази даних (наприклад, прочитати вміст будь-яких таблиць, видалити, змінити або додати дані), отримати можливість читання та/або запису локальних файлів та виконання довільних команд на сервері.
Атака типу впровадження SQL може бути можлива за некоректної обробки вхідних даних, що використовуються в SQL-запитах.
Розробник додатків, що працюють з базами даних, повинен знати про таку вразливість і вживати заходів протидії впровадженню SQL ін’єкцій.
Принцип атаки
Припустимо, серверне ПЗ, отримавши вхідний параметр id, використовує його для створення SQL-запиту. Розглянемо такий PHP-скрипт:
...
$id = $_REQUEST['id'];
$res = mysql_query("SELECT * FROM news WHERE id_news = $id");
...
Якщо на сервер переданий параметр id, що дорівнює 5 (наприклад так: http://example.org/script.php?id=5), то виконається такий SQL-запит:
SELECT * FROM news WHERE id_news = 5
Але якщо зловмисник передасть як параметр id рядок -1 OR 1=1 (наприклад, так: http://example.org/script.php?id=-1+OR+1=1), то виконається запит:
SELECT * FROM news WHERE id_news =-1 OR 1=1
Таким чином, зміна вхідних параметрів шляхом додавання в них конструкцій мови SQL викликає зміну в логіці виконання SQL-запиту (в цьому прикладі замість новини із заданим ідентифікатором будуть вибрані всі наявні в базі новини, оскільки вираз 1=1 завжди істинний).
Впровадження в рядкові параметри
Припустимо, серверне ПЗ, отримавши запит на пошук даних у новинах параметр search_text, використовує його в наступному SQL-запиті (тут параметри екрануються лапками):
...
$search_text = $_REQUEST['search_text'];
$res = mysql_query("SELECT id_news, news_date, news_caption, news_text, news_id_author
FROM news
WHERE news_caption LIKE('%$search_text%') ");
Зробивши запит виду http://example.org/script.php?search_text=Test ми отримаємо виконання такого SQL-запиту:
SELECT id_news, news_date, news_caption, news_text, news_id_author FROM news
WHERE news_caption LIKE('%Test%')
Але, запровадивши в параметр search_text символ лапки (який використовується в запиті), ми можемо кардинально змінити поведінку SQL-запиту. Наприклад, передавши як параметр search_text значення ') +and+ (news_id_author='1, ми змусимо виконатися запиту:
SELECT id_news, news_date, news_caption, news_text, news_id_author FROM news
WHERE news_caption LIKE('%') and (news_id_author='1%')
Використання UNION
Мова SQL дозволяє об'єднувати результати декількох запитів за допомогою оператора UNION. Це надає зловмисникові можливість отримати несанкціонований доступ до даних.
Розглянемо скрипт відображення новини (ідентифікатор новини, яку необхідно відобразити, передається в параметрі id):
$res = mysql_query("SELECT id_news, header, body, author FROM news WHERE id_news = ". $_REQUEST['id']);
Якщо зловмисник передасть як параметр id конструкцію -1 UNION SELECT 1,username, password,1 FROM admin, це викличе виконання SQL-запиту:
SELECT id_news, header, body, author
FROM news
WHERE id_news =-1
UNION
SELECT 1,username,password,1
FROM admin
Оскільки новини з ідентифікатором -1 завідомо не існує, з таблиці news не буде вибрано жодного запису, проте в результат потраплять записи, несанкціоновано відібрані з таблиці admin внаслідок ін'єкції SQL.
Методика атак типу впровадження SQL-коду
Пошук скриптів, уразливих для атаки
На цьому етапі зловмисник вивчає поведінку скриптів сервера при маніпуляції вхідними параметрами з метою виявлення їх аномальної поведінки. Маніпуляція відбувається всіма можливими параметрами:
- даними, переданими через методи POST і GET;
- значеннями [HTTP-Cookie];
- HTTP_REFERER (для скриптів);
- AUTH_USER та AUTH_PASSWORD (при використанні аутентифікації).
Як правило, маніпуляція зводиться до підстановки в параметри символу одинарної (рідше подвійної або зворотної) лапки.
Аномальною поведінкою вважається будь-яка поведінка, при якому сторінки, одержувані до і після підстановки лапок, розрізняються (і при цьому немає повідомлення про неправильний форматі параметрів).
Найчастіші приклади аномальної поведінки:
- виводиться повідомлення про різні помилки;
- при запиті даних (наприклад, новини або списку продукції) запитувані дані не виводяться взагалі, хоча сторінка відображається і т. д.
Слід враховувати, що відомі випадки, коли повідомлення про помилки, в силу специфіки розмітки сторінки, не видно в браузері, хоча і присутні в її HTML-коді.
Захист від атак типу впровадження SQL-коду
Для захисту від цього типу атак необхідно ретельно фільтрувати вхідні параметри, значення яких будуть використані для побудови SQL-запиту.
Фільтрація стрічкових параметрів
Щоб впровадження коду було неможливо, для деяких СКБД, в тому числі, для MySQL, потрібно брати в лапки всі строкові параметри. У самому параметрі замінюють лапки на \", апостроф на \', зворотну косу риску на \\ (це називається «екрануванням спецсимволів»).
Фільтрація цілочисельних параметрів
У багатьох випадку поле id має числовий тип, і його найчастіше не беруть в лапки. У такому випадку допомагає перевірка – якщо змінна id не є числом, запит взагалі не повинен виконуватися.
Усікання вхідних параметрів
Для внесення змін в логіку виконання SQL-запиту потрібно впровадження достатньо довгих рядків. Так, мінімальна довжина такого рядка у наведених вище прикладах становить 8 символів («1 OR 1=1»). Якщо максимальна довжина коректного значення параметра невелика, то одним з методів захисту може бути максимальне усікання значень вхідних параметрів.
Використання параметризованих запитів
Багато серверів баз даних підтримують можливість відправки параметризованих запитів (підготовлені вирази). При цьому параметри зовнішнього походження відправляються на сервер окремо від самого запиту або автоматично екрануються клієнтською бібліотекою. Для цього використовують:
на Delphi – властивість TQuery.Params;
на Perl – DBI::quote або DBI::prepare;
на Java – клас PreparedStatement;
на C# – властивість SqlCommand.Parameters;
на PHP – MySQLi (при роботі з MySQL), PDO.
2. DDL та DCL
Data Definition Language (DDL) (мова опису даних) – призначена для створення і організації структури як самої БД так і її об'єктів. Із записами (рядками) така група операторів не працює.
CREATE – створення об'єкта (наприклад таблиці, бази даних);
ALTER – використовується для додавання, видалення або зміни стовпців в існуючій таблиці.
DROP TABLE – видалення об'єкта (індекси, таблиці, бази даних);
CREATE TABLE table_name
(
column_name1 data_type(size),
column_name2 data_type(size),
column_name3 data_type(size),
....
);
ALTER TABLE table_name
ADD column_name datatype
ALTER TABLE table_name
DROP COLUMN column_name
DROP TABLE table_name
Data Control Language (робота із правами доступу)
Наступна група служить для розмежування прав користувачів, тому з однією базою швидше за все будуть працювати як мінімум кілька людей / клієнтів. По-замовчуванню у нових користувачів прав ніяких немає.
GRANT – призначення прав користувачу;
DENY – явна заборона для користувача;
REVOKE – скасувати заборону або дозвіл;
-- Призначення права вибірки з таблиці student користувачеві new_user.
GRANT SELECT ON student TO new_user;
-- Заборона права вибірки з таблиці student користувачеві new_user.
DENY SELECT ON student TO new_user;
-- Скасувати заборону.
REVOKE SELECT ON student FROM new_user;
3. Транзакції
Транзакція є однією з фундаментальних концепцій всіх СКБД. Сутність транзакції полягає у зв'язуванні декількох кроків в одну операцію за принципом все-або-нічого. Внутрішні проміжні стани між кроками не помітні для інших конкуруючих транзакцій і якщо під час виконання транзакції трапиться помилка, яка завадить завершенню транзакції, то в базі даних ніяких змін зроблено не буде.
Наприклад, припустимо, що є база даних, яка містить баланси для декількох клієнтів і загальні депозитні баланси для філій. Припустимо, що ми хочемо внести надходження $100.00 від клієнта Alice для клієнта Bob. Найпростіша сукупність команд, яка виконує дану операцію може виглядати так:
UPDATE accounts SET balance = balance - 100.00
WHERE name = 'Alice';
UPDATE branches SET balance = balance - 100.00
WHERE name = (SELECT branch_name FROM accounts WHERE name = Alice');
UPDATE accounts SET balance = balance + 100.00
WHERE name = 'Bob';
UPDATE branches SET balance = balance + 100.00
WHERE name = (SELECT branch_name FROM accounts WHERE name = 'Bob');
Деталі цих команд зараз не важливі; важливо що тут ми маємо справу з декількома окремими оновленнями (оператори оновлення), які реалізують потрібну нам операцію. Наші банківські працівники захочуть зробити так, щоб всі ці оновлення відбувалися відразу або щоб не відбувалася жодна з них. Це обумовлено тим, що в результаті будь-якої системної помилки може вийти так, що Боб отримає $100.00, які не будуть відняті у Alice. Або може статися так, що у Alice буде вирахована ця сума, але Bob її не отримає. Нам потрібна гарантія, що якщо що-небудь піде не так під час операцій оновлення, рахунків, то ніяких змін фактично внесено не буде. Таку гарантію можна отримати, якщо згрупувати оператори оновлення в транзакцію. Транзакція є атомарною дією з точки зору інших транзакцій і або вона завершиться повністю успішно, або ніякі дії, що становлять транзакцію виконані не буде.
Ми також хочемо гарантувати, що транзакція яка повністю завершилася і підтверджена СКБД є дійсно збереженою і не може бути втрачена, навіть якщо після її виконання відбудеться крах системи. Наприклад, якщо ми зберігаємо кеш переводу клієнта Bob, ми не хочемо, щоб гроші клієнта Bob загубилися в результаті краху системи, який, наприклад, може відбутися як тільки Bob вийшов за двері банку. Традиційні СКБД гарантують що всі оновлення, які здійснені в одній транзакції, протоколюються в надійне сховище (тобто на диск) перед тим як СКБД, повідомить про завершення транзакції.
Інша важлива властивість транзакційних СКБД полягає в суворій ізоляції транзакцій: коли кілька транзакцій запускаються конкурентно, кожна з них не бачить тих неповних змін, які робляють інші транзакції. Наприклад, якщо одна транзакція зайнята додаванням усіх балансів філій, вона не повинна враховувати як грошей знятих з рахунку Alice так і грошей, що прийшли на рахунок Bob. Таким чином транзакції повинні виконувати принцип все-або-нічого не тільки в плані непорушності тих змін, які вони виробляють в базі даних, а й також у плані того, що вони бачать в момент роботи. Оновлення, які вносить відкрита транзакція є невидимими для інших транзакцій поки дана транзакція не завершитися, після чого всі внесені їй зміни стануть видимими.
Transaction Control Language (робота з транзакціями)
BEGIN TRANSACTION – розпочати транзакцію;
COMMIT – прийняти зміни внесені поточною транзакцією;
ROLLBACK – відкат;
--Додавання нової стрічки в таблицу books. Пийняти зміни.
BEGIN TRANSACTION;
INSERT INTO books (title, author, year, description)
VALUES ('Нова книга', 'Новий автор', 2015, 'Книга про все нове');
COMMIT WORK;
-- Додавання нової стрічки в таблицю books. Видалення запису. Відмінити всі зміни.
BEGIN TRANSACTION;
INSERT INTO books (title, author, year, description)
VALUES ('Нова книга', 'Новий автор', 2015, 'Книга про все нове');
DELETE FROM books WHERE id=3;
ROLLBACK WORK;