Как подготовленные операторы могут защитить от атак SQL-инъекций?
Как подготовленные операторы помогают нам предотвратить атаки с использованием SQL-инъекций?
Википедия говорит:
Подготовленные операторы устойчивы к внедрению SQL, потому что значения параметров, которые передаются позже с использованием другого протокола, не должны быть правильно экранированы. Если исходный шаблон оператора не является производным от внешнего ввода, внедрение SQL невозможно.
Я не очень хорошо вижу причину. Каким было бы простое объяснение на легком английском и некоторые примеры?
10 ответов
Идея очень проста - запрос и данные отправляются на сервер базы данных отдельно.
Это все.
Корень проблемы внедрения 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);
таким образом, это не может изменить нашу программу и причинить какой-либо вред.
Довольно просто - не так ли?
Однако стоит отметить, что не каждый раз, когда вы используете заполнитель, он обрабатывается как подготовленное утверждение.
Заполнитель - это общая идея для замены фактических данных переменной для дальнейшей обработки (см. printf()
например), в то время как подготовленное утверждение является единственным его подмножеством.
Существуют случаи (в частности, это может сделать PDO в PHP), когда подготовленный оператор можно эмулировать, а запрос фактически составляется вместе с данными и отправляется на сервер в одном запросе. Но важно понимать, что этот подход одинаково безопасен, потому что каждый бит данных должным образом отформатирован в соответствии с его типом, и поэтому ничего плохого не может произойти.
Единственное, что я должен добавить, это всегда опускается в каждом руководстве:
Подготовленные операторы могут защитить только данные, но не могут защитить саму программу.
Так что, как только мы добавим, скажем, динамический идентификатор - например, имя поля, подготовленные операторы не могут нам помочь. Я недавно объяснил этот вопрос, поэтому я не буду повторяться.
Вот 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=?
По сути, с подготовленными утверждениями данные, поступающие от потенциального хакера, обрабатываются как данные - и нет никакого способа, которым они могут быть смешаны с SQL вашего приложения и / или интерпретированы как SQL (что может случиться, когда передаваемые данные помещаются непосредственно в ваш приложение SQL).
Это связано с тем, что подготовленные операторы сначала "подготавливают" запрос SQL, чтобы найти эффективный план запроса, и позже отправляют фактические значения, которые, вероятно, поступают из формы, - тогда запрос фактически выполняется.
Более подробная информация здесь:
Я прочитал ответы и все еще чувствовал необходимость подчеркнуть ключевой момент, который освещает суть подготовленных заявлений. Рассмотрим два способа сделать запрос к базе данных, в которой участвует пользовательский ввод:
Наивный подход
Один объединяет пользовательский ввод с некоторой частичной строкой 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"
никогда не будет выполнен в случае выше.
Короче говоря, с подготовленными заявлениями вредоносный код, введенный через пользовательский ввод, не будет выполнен!
Когда вы создаете и отправляете подготовленный оператор в СУБД, он сохраняется как запрос SQL для выполнения.
Позже вы привязываете свои данные к запросу, так что СУБД использует эти данные в качестве параметров запроса для выполнения (параметризации). СУБД не использует данные, которые вы связываете, в качестве дополнения к уже скомпилированному запросу SQL; это просто данные.
Это означает, что принципиально невозможно выполнить SQL-инъекцию, используя подготовленные операторы. Сама природа подготовленных заявлений и их связь с СУБД предотвращает это.
В 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-инъекций...
Ключевая фраза need not be correctly escaped
, Это означает, что вам не нужно беспокоиться о людях, которые пытаются добавить тире, апострофы, цитаты и т. Д.
Это все обрабатывается для вас.
Основная причина № 1 - Проблема разделителя
Внедрение Sql возможно потому, что мы используем кавычки для разделения строк, а также для того, чтобы быть частью строк, что иногда делает невозможным их интерпретацию. Если бы у нас были разделители, которые нельзя было использовать в строковых данных, SQL-инъекция никогда бы не произошла. Решение проблемы с разделителем устраняет проблему внедрения SQL. Структурные запросы делают это.
Причина № 2 - человеческая природа, люди лукавые, а некоторые лукавые люди злые, и все люди совершают ошибки
Другая основная причина инъекций sql - человеческая природа. Люди, в том числе программисты, делают ошибки. Когда вы делаете ошибку в структурированном запросе, это не делает вашу систему уязвимой для внедрения SQL. Если вы не используете структурированные запросы, ошибки могут привести к уязвимости SQL инъекций.
Как структурированные запросы решают первопричины SQL-инъекций
Структурированные запросы Решают проблему разделителя, помещая команды sql в один оператор и помещая данные в отдельный оператор программирования. Заявления программирования создают необходимое разделение.
Структурированные запросы помогают предотвратить возникновение критических дыр в безопасности от человеческих ошибок. Что касается людей, совершающих ошибки, внедрение SQL не может произойти при использовании структурных запросов. Существуют способы предотвращения внедрения SQL, которые не включают структурированные запросы, но обычная человеческая ошибка в таких подходах обычно приводит к некоторому риску внедрения SQL. Структурированные запросы отказоустойчивы от внедрения SQL. Вы можете совершать все ошибки в мире, практически, с помощью структурированных запросов, как и любое другое программирование, но ни одна из возможных ошибок не может быть превращена в систему, перенесенную с помощью SQL-инъекции. Вот почему люди любят говорить, что это правильный способ предотвратить инъекцию SQL.
Итак, у вас есть это, причины внедрения sql и характер структурированных запросов, которые делают их невозможными при их использовании.
Простой пример:
"select * from myTable where name = " + condition;
И если пользовательский ввод:
'123'; delete from myTable; commit;
Запрос будет выполнен следующим образом:
select * from myTable where name = '123'; delete from myTable; commit;
ResultSet rs = statement.executeQuery("select * from foo where value = " + httpRequest.getParameter("filter");
Давайте предположим, что вы имеете это в сервлете, вы правы. Если злонамеренный человек передал неправильное значение для "фильтра", вы можете взломать вашу базу данных.