Як підготовлені заяви захищають від атак на ін'єкції SQL?


172

Як підготовлені заяви допомагають нам запобігти атакам ін'єкції SQL ?

У Вікіпедії сказано:

Підготовлені оператори стійкі до введення SQL, тому що значення параметрів, які передаються пізніше за допомогою іншого протоколу, не повинні бути правильними. Якщо оригінальний шаблон заяви не отриманий із зовнішнього введення, ін'єкція SQL не може відбутися.

Я не дуже добре бачу причину. Що було б простим поясненням на легкій англійській мові та кількох прикладах?

Відповіді:


290

Ідея дуже проста - запит і дані надсилаються на сервер бази даних окремо .
Це все.

Корінь проблеми введення SQL полягає в змішуванні коду та даних.

Насправді наш SQL-запит є законною програмою . І ми створюємо таку програму динамічно, додаючи деякі дані під час польоту. Таким чином, дані можуть перешкоджати коду програми і навіть змінювати його, як показує кожен приклад ін'єкції SQL (усі приклади в PHP / Mysql):

$expected_data = 1;
$query = "SELECT * FROM users where id=$expected_data";

видасть звичайний запит

SELECT * FROM users where id=1

при цьому цей код

$spoiled_data = "1; DROP TABLE users;"
$query        = "SELECT * FROM users where id=$spoiled_data";

створить шкідливу послідовність

SELECT * FROM users where id=1; DROP TABLE users;

Це працює, тому що ми додаємо дані безпосередньо до програми програми і вони стають частиною програми, тому дані можуть змінювати програму, і залежно від даних, що передаються, у нас буде або регулярний вихід, або таблиця usersвидалена.

Хоча у випадку підготовлених заяв ми не змінюємо нашу програму, вона залишається недоторканою.
Це справа.

Ми відправляємо програму спочатку на сервер

$db->prepare("SELECT * FROM users where id=?");

де дані заміщені деякою змінною, що називається параметром або заповнювачем.

Зауважте, що точно такий же запит надсилається на сервер, без будь-яких даних у ньому! А потім ми надсилаємо дані з другим запитом, по суті відокремленим від самого запиту:

$db->execute($data);

тому він не може змінити нашу програму і не заподіє шкоди.
Досить просто - чи не так?

Єдине, що я маю додати, що завжди пропущено у кожному посібнику:

Підготовлені оператори можуть захищати лише літерали даних , але їх не можна використовувати з будь-якою іншою частиною запиту.
Отже, як тільки ми повинні додати, скажімо, динамічний ідентифікатор - наприклад, назва поля, - підготовлені заяви не можуть нам допомогти. Я нещодавно пояснив це питання , тому не повторюся.


2
"наприклад, PDO за замовчуванням не використовує підготовлені оператори" - це не зовсім так, оскільки PDO емулює підготовлені заяви лише для драйверів, які не підтримують таку функцію.
pinepain

3
@ zaq178miami: "PDO емулює підготовлені заяви лише для драйверів, які не підтримують цю функцію" - не зовсім вірно. MySQL вже давно підтримує підготовлені заяви. Драйвер PDO також є. Але все ж запити MySQL все ще були підготовлені PDO за замовчуванням, востаннє я перевіряв.
cHao

9
Що відрізняється між $spoiled_data = "1; DROP TABLE users;"-> $query = "SELECT * FROM users where id=$spoiled_data";, порівняно з: $db->prepare("SELECT * FROM users where id=?");-> $data = "1; DROP TABLE users;"-> $db->execute($data);. Чи не будуть вони робити те саме?
Juha Untinen

14
@Juha Untinen Дані можуть бути будь-якими. Він не буде аналізувати дані. Це DATA не команда. Тож навіть якщо дані $ містять sql команди, вони не виконуватимуться. Також, якщо ідентифікатор - це число, то вміст рядка генерує звіт або значення нульове.
Soley

21

Ось SQL для налаштування прикладу:

CREATE TABLE employee(name varchar, paymentType varchar, amount bigint);

INSERT INTO employee VALUES('Aaron', 'salary', 100);
INSERT INTO employee VALUES('Aaron', 'bonus', 50);
INSERT INTO employee VALUES('Bob', 'salary', 50);
INSERT INTO employee VALUES('Bob', 'bonus', 0);

Клас Inject вразливий до ін'єкції SQL. Запит динамічно вставляється разом із введенням користувача. Завданням запиту було показати інформацію про Боба. Або заробітна плата, або бонус, залежно від даних користувачів. Але зловмисний користувач маніпулює вхідним пошкодженням запиту, встановлюючи еквівалент "або істинного" до пункту "де", щоб повернути все, включаючи інформацію про Аарона, яка повинна була бути прихованою.

import java.sql.*;

public class Inject {

    public static void main(String[] args) throws SQLException {

        String url = "jdbc:postgresql://localhost/postgres?user=user&password=pwd";
        Connection conn = DriverManager.getConnection(url);

        Statement stmt = conn.createStatement();
        String sql = "SELECT paymentType, amount FROM employee WHERE name = 'bob' AND paymentType='" + args[0] + "'";
        System.out.println(sql);
        ResultSet rs = stmt.executeQuery(sql);

        while (rs.next()) {
            System.out.println(rs.getString("paymentType") + " " + rs.getLong("amount"));
        }
    }
}

Виконуючи це, перший випадок із звичайним використанням, а другий із шкідливою ін'єкцією:

c:\temp>java Inject salary
SELECT paymentType, amount FROM employee WHERE name = 'bob' AND paymentType='salary'
salary 50

c:\temp>java Inject "salary' OR 'a'!='b"
SELECT paymentType, amount FROM employee WHERE name = 'bob' AND paymentType='salary' OR 'a'!='b'
salary 100
bonus 50
salary 50
bonus 0

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

Ось приклад зв'язування, щоб уникнути подібного роду ін'єкцій:

import java.sql.*;

public class Bind {

    public static void main(String[] args) throws SQLException {

        String url = "jdbc:postgresql://localhost/postgres?user=postgres&password=postgres";
        Connection conn = DriverManager.getConnection(url);

        String sql = "SELECT paymentType, amount FROM employee WHERE name = 'bob' AND paymentType=?";
        System.out.println(sql);

        PreparedStatement stmt = conn.prepareStatement(sql);
        stmt.setString(1, args[0]);

        ResultSet rs = stmt.executeQuery();

        while (rs.next()) {
            System.out.println(rs.getString("paymentType") + " " + rs.getLong("amount"));
        }
    }
}

Запустивши це з тим же входом, що і в попередньому прикладі, показано, що зловмисний код не працює, оскільки немає PaymentType, що відповідає цій рядку:

c:\temp>java Bind salary
SELECT paymentType, amount FROM employee WHERE name = 'bob' AND paymentType=?
salary 50

c:\temp>java Bind "salary' OR 'a'!='b"
SELECT paymentType, amount FROM employee WHERE name = 'bob' AND paymentType=?

Чи використовує підготовлений оператор програми, що підключається до бази даних, такий же ефект, як і використання підготовленого оператора, що є частиною db? Наприклад, Postgres має власну підготовлену заяву і чи не використовує її запобігання ін'єкції SQL? postgresql.org/docs/9.2/static/sql-prepare.html
Celeritas

@Celeritas Я не маю остаточної відповіді на Postgresql. Дивлячись на документи, здається, ефект такий же. PREPAREстворює фіксований названий оператор, який вже розібраний (тобто оператор більше не буде змінюватися незалежно від введення), в той час як EXECUTEбуде запущений названий оператор, що зв'язує параметри. Оскільки PREPAREтривалість сеансу має лише тривалість сеансу, вона справді виглядає так, що вона призначена з міркувань продуктивності, а не для запобігання ін'єкції через скрипти psql Для доступу до psql можна надати дозволи для збережених процедур та прив’язати параметри в документах.
Гленн

@Celeritas Я спробував вищевикладений код, використовуючи PostgreSQL 11.1 на x86_64 і вищевказаний приклад SQLi.
Крішна Пандей

15

В основному, з підготовленими висловлюваннями дані, що надходять від потенційного хакера, трактуються як дані - і немає ніякого способу їх змішування з SQL програми та / або інтерпретувати як SQL (що може статися, коли дані, що передаються, розміщуються безпосередньо у вашій програмі додаток SQL).

Це тому, що підготовлені висловлювання "готують" SQL-запит спочатку до пошуку ефективного плану запитів і надсилають фактичні значення, які, ймовірно, надходять із форми пізніше - у цей час запит фактично виконується.

Більше чудової інформації тут:

Підготовлені заяви та ін'єкція SQL


6

Я прочитав відповіді і все ще відчував потребу підкреслити ключовий момент, який висвітлює суть підготовлених заяв. Розглянемо два способи запиту до бази даних, де йдеться про введення користувача:

Наївний підхід

Один об'єднує введення користувача з деякою частковою рядком SQL, щоб генерувати оператор SQL. У цьому випадку користувач може вставити шкідливі команди SQL, які потім будуть відправлені в базу даних для виконання.

String SQLString = "SELECT * FROM CUSTOMERS WHERE NAME='"+userInput+"'"

Наприклад, зловмисне введення користувача може призвести до SQLStringрівності"SELECT * FROM CUSTOMERS WHERE NAME='James';DROP TABLE CUSTOMERS;'

Завдяки шкідливому користувачеві SQLStringмістить 2 заяви, де 2-й ( "DROP TABLE CUSTOMERS") заподіє шкоду.

Підготовлені заяви

У цьому випадку через розділення запиту та даних введення користувача ніколи не трактується як оператор SQL і, таким чином, ніколи не виконується . Саме з цієї причини будь-який введений шкідливий код SQL не завдасть ніякої шкоди. Тож "DROP TABLE CUSTOMERS"ніколи не було б виконано у випадку, зазначеному вище.

Коротше кажучи, з підготовленими операторами зловмисний код, введений через введення користувача, не виконуватиметься!


Дійсно? Прийнята відповідь не говорить саме про це?
Твій здоровий глузд

@Your Common Sense Прийнята відповідь наповнена великою кількістю цінної інформації, але це змусило мене замислитись, що передбачає деталізація впровадження розділення даних і запитів. Тоді як акцентування уваги на тому, що зловмисне введені дані (якби вони були) ніколи не будуть виконані, вдаряють про цвях по голові.
Н.Вегета

І які "деталі впровадження" містяться у вашій відповіді, які там відсутні?
Твій здоровий глузд

якщо ви спробуєте побачити, звідки я родом, ви зрозумієте, що моя суть полягає в наступному: Коротке бажання ознайомитись із деталями реалізації випливає з необхідності зрозуміти явну причину, чому зловмисне введення користувача не спричинить жодного шкода. Не стільки потрібно бачити деталі реалізації. Ось чому зрозумівши, що відомості про реалізацію були такими, що жоден момент не буде зловмисно введеним SQL виконуватись, надіслав повідомлення додому. Ваша відповідь відповідає на питання, як (як вимагається) ?, але я б уявив, що інші люди (як я) будуть задоволені лаконічною відповіддю на те, чому?
Н.Вегета

Розглянемо це збагачення, яке з'ясовує суть, а не як натяк на критику (переглянуто зрозумів, хто автор прийнятої відповіді).
Н.Вегета

5

Коли ви створюєте та відправляєте підготовлену заяву до СУБД, вона зберігається як запит SQL для виконання.

Пізніше ви прив’язуєте свої дані до запиту таким чином, що СУБД використовує ці дані як параметри запиту для виконання (параметризації). СУБД не використовує дані, які ви прив'язуєте, як доповнення до вже складеного SQL запиту; це просто дані.

Це означає, що виконувати інжекцію SQL за допомогою підготовлених операторів принципово неможливо. Сама природа підготовлених заяв та їх зв’язок із СУБД перешкоджає цьому.


4

У SQL Server використання готового оператора, безумовно, є захищеним від ін'єкцій, оскільки вхідні параметри не формують запит. Це означає, що виконаний запит не є динамічним запитом. Приклад заяви про вразливу ін'єкцію SQL.

string sqlquery = "select * from table where username='" + inputusername +"' and password='" + pass + "'";

Тепер, якщо значення в змінній inoutusername щось на зразок 'або 1 = 1 -, цей запит тепер стає:

select * from table where username='a' or 1=1 -- and password=asda

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

Sqlcommand command = new sqlcommand("select * from table where username = @userinput and password=@pass");
command.Parameters.Add(new SqlParameter("@userinput", 100));
command.Parameters.Add(new SqlParameter("@pass", 100));
command.prepare();

Тож фактично ви не можете надсилати інший параметр, уникаючи тим самим ін'єкції SQL ...


3

Ключова фраза - need not be correctly escaped. Це означає, що ви не турбуєтесь про те, що люди намагаються кинути тире, апострофи, цитати тощо ...

З вами все обробляється.


2
ResultSet rs = statement.executeQuery("select * from foo where value = " + httpRequest.getParameter("filter");

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


0

Причинна причина №1 - проблема з роздільником

Ін'єкція Sql можлива, оскільки ми використовуємо лапки для розмежування рядків, а також є частинами рядків, що робить їх неможливим їх інтерпретацію іноді. Якби у нас були роздільники, які не можна було б використовувати в рядкових даних, введення sql ніколи б не відбулося. Вирішення проблеми з роздільником обмежує проблему введення sql. Це роблять запити до структури.

Причина № 2 - людська природа, люди хитрі, а деякі хитрі люди злісні, і всі люди роблять помилки

Інша першопричина ін'єкції sql - це природа людини. Люди, включаючи програмістів, роблять помилки. Коли ви робите помилку в структурованому запиті, це не робить вашу систему вразливою до sql введення. Якщо ви не використовуєте структуровані запити, помилки можуть призвести до вразливості sql введення.

Як структуровані запити вирішують основні причини ін'єкції SQL

Структуровані запити вирішують проблему розділення, вводячи команди sql в одне твердження та розміщуючи дані в окремому операційному програмі. Програмування операторів створює необхідний поділ.

Структуровані запити допомагають запобігти помилкам людини від створення критичних прорізів у безпеці. Що стосується людей, які роблять помилки, введення sql не може відбуватися, коли використовуються запити структури. Існують способи запобігання ін'єкції sql, які не передбачають структурованих запитів, але звичайна помилка людини в таких підходах, як правило, призводить до хоча б деякого впливу на sql ін'єкції. Структуровані запити не захищені від введення sql. Ви можете зробити всі помилки у світі, майже за допомогою структурованих запитів, як і будь-яке інше програмування, але жодна, яку ви можете зробити, не може бути перетворена в ssstem, захоплений sql-ін'єкцією. Ось чому люди люблять говорити, що це правильний спосіб запобігти ін'єкції sql.

Отож, у вас це є, причини ін'єкції sql та структуровані за характером запити, що унеможливлює їх використання.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.