Как подготовленные операторы могут защитить от атак SQL-инъекций?

172

Как подготовленные операторы помогают нам предотвратить атаки с использованием SQL-инъекций ?

Википедия говорит:

Подготовленные операторы устойчивы к внедрению SQL, потому что значения параметров, которые передаются позже с использованием другого протокола, не должны быть корректно экранированы. Если исходный шаблон оператора не является производным от внешнего ввода, внедрение SQL невозможно.

Я не очень хорошо вижу причину. Каким было бы простое объяснение на легком английском и некоторые примеры?

Aan
источник

Ответы:

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 по умолчанию, в прошлый раз, когда я проверял.
Цао
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);. Разве они не сделают то же самое?
Юха Унтинен,
14
@Juha Untinen Данные могут быть чем угодно. Он не будет анализировать данные. Это ДАННЫЕ, а не команда. Таким образом, даже если $ data содержит команды sql, он не будет выполнен. Кроме того, если идентификатор является числом, то содержимое строки будет генерировать отчет или нулевое значение.
Соли
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. Запрос динамически вставляется вместе с пользовательским вводом. Целью запроса было показать информацию о Бобе. Заработная плата или бонус, в зависимости от ввода пользователя. Но злонамеренный пользователь манипулирует вводом, повреждая запрос, прикрепляя эквивалент выражения «или true» к предложению where, чтобы все возвращалось, включая информацию об Аароне, который должен был быть скрыт.

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 снижается, тогда как в примере связывания всегда выполняется один и тот же оператор).

Вот пример Binding, чтобы избежать такого рода инъекций:

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"));
        }
    }
}

Выполнение этого с тем же вводом, что и в предыдущем примере, показывает, что вредоносный код не работает, потому что нет платежного типа, соответствующего этой строке:

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=?
Гленн
источник
Имеет ли использование подготовленного оператора из программы, подключающейся к базе данных, такой же эффект, как использование подготовленного оператора, являющегося частью базы данных? Например, у 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", никогда не будет выполнено в случае выше.

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

N.Vegeta
источник
В самом деле? Принятый ответ не говорит именно это?
Ваш здравый смысл
@Your Здравый смысл Принятый ответ наполнен большим количеством ценной информации, но это заставило меня задуматься о том, что влечет за собой детали реализации разделения данных и запроса. Принимая во внимание, что акцент на том, что злонамеренно введенные данные (если они были) никогда не будут выполнены, ударяет по гвоздю.
Н.Вегета
А какие "детали реализации" приведены в вашем ответе, которых там нет?
Ваш здравый смысл
если вы попытаетесь понять, откуда я пришел, вы поймете, что моя точка зрения такова: краткое желание увидеть детали реализации проистекает из необходимости понять явную причину, по которой злонамеренный ввод пользователя не вызовет какой-либо вред. Не так много нужно, чтобы увидеть детали реализации. Вот почему, понимая, что детали реализации таковы, что ни в коем случае злонамеренно введенный SQL не будет выполнен, отправили домой сообщение. Ваш ответ отвечает на вопрос, как (по запросу) ?, но я думаю, что другие люди (как я) будут удовлетворены кратким ответом на вопрос, почему?
Н.Вегета
Считайте, что это обогащение, которое объясняет суть, а не подразумеваемую критику (недавно понял, кто был автором принятого ответа).
Н.Вегета
5

Когда вы создаете и отправляете подготовленный оператор в СУБД, он сохраняется как запрос SQL для выполнения.

Позже вы связываете свои данные с запросом, так что СУБД использует эти данные в качестве параметров запроса для выполнения (параметризации). СУБД не использует данные, которые вы связываете, в качестве дополнения к уже скомпилированному запросу SQL; это просто данные.

Это означает, что принципиально невозможно выполнить SQL-инъекцию, используя подготовленные операторы. Этому препятствует сама природа подготовленных заявлений и их связь с СУБД.

wulfgarpro
источник
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-инъекций ...

lloydom
источник
3

Ключевая фраза есть need not be correctly escaped. Это означает, что вам не нужно беспокоиться о людях, которые пытаются добавить тире, апострофы, цитаты и т. Д.

Это все обрабатывается для вас.

Feisty Mango
источник
2
ResultSet rs = statement.executeQuery("select * from foo where value = " + httpRequest.getParameter("filter");

Давайте предположим, что вы имеете это в сервлете, вы правы. Если злонамеренный человек передал неправильное значение для «фильтра», вы можете взломать вашу базу данных.

MeBigFatGuy
источник
0

Причина № 1 - Проблема с разделителем

Внедрение Sql возможно потому, что мы используем кавычки для разделения строк, а также для того, чтобы быть частью строк, что иногда делает невозможным их интерпретацию. Если бы у нас были разделители, которые нельзя было использовать в строковых данных, SQL-инъекция никогда бы не произошла. Решение проблемы с разделителем устраняет проблему внедрения SQL. Структурные запросы делают это.

Причина № 2 - человеческая природа, люди лукавые, а некоторые лукавые люди злые, и все люди совершают ошибки

Другой основной причиной инъекции sql является человеческая природа. Люди, в том числе программисты, делают ошибки. Когда вы делаете ошибку в структурированном запросе, это не делает вашу систему уязвимой для внедрения SQL. Если вы не используете структурированные запросы, ошибки могут привести к уязвимости SQL инъекций.

Как структурированные запросы устраняют первопричины SQL-инъекций

Структурированные запросы Решают проблему разделителя, помещая команды sql в один оператор и помещая данные в отдельный оператор программирования. Заявления программирования создают необходимое разделение.

Структурированные запросы помогают предотвратить человеческие ошибки от создания критических дыр в безопасности. Что касается людей, совершающих ошибки, SQL-инъекция не может произойти при использовании структурных запросов. Существуют способы предотвращения внедрения SQL, которые не включают структурированные запросы, но обычная человеческая ошибка в таких подходах обычно приводит к некоторому риску внедрения SQL. Структурированные запросы отказоустойчивы от внедрения SQL. Вы можете совершать все ошибки в мире, почти с помощью структурированных запросов, как и любое другое программирование, но ни одна из возможных ошибок не может быть превращена в систему, перенесенную с помощью SQL-инъекции. Вот почему люди любят говорить, что это правильный способ предотвратить инъекцию SQL.

Итак, у вас есть это, причины внедрения SQL и характер структурированных запросов, которые делают их невозможными при их использовании.

DanAllen
источник