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[недоступне посилання з лютого 2019]), то виконається такий SQL-запит:

SELECT * FROM news WHERE id_news = 5

Але якщо зловмисник передасть як параметр id рядок -1 OR 1=1 (наприклад, так: http://example.org/script.php?id=-1+OR+1=1[недоступне посилання з лютого 2019]), то виконається запит:

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[недоступне посилання з лютого 2019] ми отримаємо виконання такого 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 [Архівовано 9 лютого 2010 у Wayback Machine.]. Це надає зловмисникові можливість отримати несанкціонований доступ до даних.

Розглянемо скрипт відображення новини (ідентифікатор новини, яку необхідно відобразити, передається в параметрі 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.

Використання UNION + group_concat()

[ред. | ред. код]

У деяких випадках хакер може провести атаку, але не може бачити більше однієї колонки. У разі MySQL зломщик може скористатися функцією:

group_concat(col, symbol, col)

яка об'єднує кілька колонок в одну. Наприклад, для прикладу цього вище виклик функції буде таким:

-1 UNION SELECT group_concat(username, 0x3a, password) FROM admin

Екранування хвоста запиту

[ред. | ред. код]

Скрипт

$res = mysql_query("SELECT author FROM news WHERE id=". $_REQUEST['id']." AND author LIKE ('a%') ");

відображає ім'я автора новини згідно ідентифікатора id лише за умови, що ім'я починається з літери а, і впровадження коду з використанням оператора UNION складне.

У таких випадках, зловмисниками використовується метод екранування частини запиту за допомогою символів коментаря (/*або--в залежності від типу СКБД).

У цьому прикладі, зловмисник може передати в скрипт параметр id зі значенням-1 UNION SELECT password FROM admin/*, виконавши таким чином запит

SELECT author FROM news WHERE id=-1 UNION SELECT password FROM admin/* AND author LIKE ('a%')

в якому частина запиту ( AND author LIKE ('a%') ) позначена як коментар і не впливає на виконання.

Розщеплення SQL-запиту

[ред. | ред. код]

Для розділення команд в мові SQL використовується символ ; (крапка з комою), впроваджуючи цей символ до запиту, зловмисник отримує можливість виконати декілька команд в одному запиті, однак не всі діалекти SQL підтримують таку можливість.

Наприклад, якщо в параметри скрипту

$id = $_REQUEST['id'];
$res = mysql_query("SELECT * FROM news WHERE id_news = $id");

зловмисником передається конструкція, що містить крапку з комою, наприклад 12;INSERT INTO admin (username, password) VALUES ('HaCkEr', 'foo'); то в одному запиті будуть виконані 2 команди

SELECT * FROM news WHERE id_news = 12;
INSERT INTO admin (username, password) VALUES ('HaCkEr', 'foo');

і в таблицю admin буде несанкціоновано доданий ​​запис HaCkEr.

Методика атак типу впровадження SQL-коду

[ред. | ред. код]

Пошук скриптів, уразливих для атаки

[ред. | ред. код]

На цьому етапі зловмисник вивчає поведінку скриптів сервера при маніпуляції вхідними параметрами з метою виявлення їх аномальної поведінки. Маніпуляція відбувається всіма можливими параметрами:

  • Даними, переданими через методи POST і GET
  • Значеннями [HTTP-Cookie]
  • HTTP_REFERER (для скриптів)
  • AUTH_USER та AUTH_PASSWORD (при використанні аутентифікації)

Як правило, маніпуляція зводиться до підстановки в параметри символу одинарної (рідше подвійний або зворотної) лапки.

Аномальною поведінкою вважається будь-яка поведінка, при якому сторінки, одержувані до і після підстановки лапок, розрізняються (і при цьому немає повідомлення неправильний форматі параметрів).

Найчастіші приклади аномальної поведінки:

  • виводиться повідомлення про різні помилки;
  • при запиті даних (наприклад, новини або списку продукції) запитувані дані не виводяться взагалі, хоча сторінка відображається

і т. д.

Слід враховувати, що відомі випадки, коли повідомлення про помилки, в силу специфіки розмітки сторінки, не видно в браузері, хоча і присутні в її HTML-коді.

Захист від атак типу впровадження SQL-коду

[ред. | ред. код]

Для захисту від цього типу атак необхідно ретельно фільтрувати вхідні параметри, значення яких будуть використані для побудови SQL-запиту.

Фільтрація рядкових параметрів

[ред. | ред. код]

Припустимо, що код, який генерує запит (на мові програмування Паскаль), виглядає так:

statement:= 'SELECT * FROM users WHERE name = "' + userName + '";';

Щоб впровадження коду було неможливо, для деяких СКБД, в тому числі, для MySQL, потрібно брати в лапки всі рядкові параметри. У самому параметрі замінюють лапки на \", апостроф на \', зворотну косу риску на \\ (це називається «екранувати спецсимволи»). Це можна робити таким кодом:

statement:= 'SELECT * FROM users WHERE name = ' + QuoteParam(userName) + ';';
function QuoteParam(s: string) : string;
{ на вході — рядок; на виході — рядок в лапках та із заміненими спецсимволами } 
var 
 i: integer;
 Dest: string;
begin
 Dest:= '"';
 for i:=1 to length(s) do
   case s[i] of
     '''': Dest:= Dest +'\''';
     '"': Dest:= Dest + '\"';
     '\': Dest:= Dest + '\\';
   else Dest:= Dest + s[i];
   end; 
 QuoteParam:= Dest + '"';
end;

для PHP фільтрація може бути такою:

<?php 
$query = "SELECT * FROM users WHERE user='".mysql_real_escape_string($user)."';";
?>

Фільтрація цілочисельних параметрів

[ред. | ред. код]

Візьмемо інший запит:

statement:= 'SELECT * FROM users WHERE id = ' + id + ';';

У цьому випадку поле id має числовий тип, і його найчастіше не беруть в лапки. У такому випадку допомагає перевірка - якщо зміннаid не є числом, запит взагалі не повинен виконуватися.

Наприклад, на Delphi для протидії таким ін'єкціям допомагає код:

id_int:= StrToInt(id);
statement:= 'SELECT * FROM users WHERE id = ' + IntToStr(id_int) + ';';

у випадку помилки функція StrToInt викличе виняток EConvertError, і в його обробнику можна буде вивести повідомлення про помилку. Подвійне перетворення забезпечує коректну реакцію на числа у форматі $132AB (шістнадцяткова система числення). На стандартному Паскалі, який не вміє обробляти виняток, код дещо складніший.

Для PHP цей метод буде виглядати так:

 
$query = 'SELECT * FROM users WHERE id = '. intval($id);

Усікання вхідних параметрів

[ред. | ред. код]

Для внесення змін в логіку виконання SQL-запиту потрібно впровадження достатньо довгих рядків. Так, мінімальна довжина такого рядка у наведених вище прикладах становить 8 символів («1 OR 1=1»). Якщо максимальна довжина коректного значення параметра невелика, то одним з методів захисту може бути максимальне усікання значень вхідних параметрів.

Наприклад, якщо відомо, що полеid у вищенаведених прикладах може приймати значення не більше 9999, можна «відрізати зайві» символи, залишивши не більше чотирьох:

statement:= 'SELECT * FROM users WHERE id = ' + LeftStr(id, 4) + ';';


Використання параметризованих запитів

[ред. | ред. код]

Багато серверів баз даних підтримують можливість відправки параметризованих запитів (підготовлені вирази). При цьому параметри зовнішнього походження відправляються на сервер окремо від самого запиту або автоматично екрануються клієнтською бібліотекою. Для цього використовують

  • на Delphi  — властивість TQuery.Params;

Наприклад

var 
 sql, param: string;
begin
 sql:= 'select:text as value from dual';
 param:= 'alpha'; 
 Query1.Sql.Text:= sql;
 Query1.ParamByName('text').AsString:= param;
 Query1.Open;
 ShowMessage(Query1['value']); 
end;
  • на Perl  — через DBI::quoteабоDBI::prepare;
  • на Java  — через класPreparedStatement;
  • на C#  — властивість SqlCommand.Parameters;
  • на PHP  — MySQLi (при роботі з MySQL), PDO.

Див. також

[ред. | ред. код]

Посилання

[ред. | ред. код]