Если да, то почему до сих пор так много успешных SQL-инъекций? Просто потому, что некоторые разработчики слишком глупы, чтобы использовать параметризованные операторы?
Если да, то почему до сих пор так много успешных SQL-инъекций? Просто потому, что некоторые разработчики слишком глупы, чтобы использовать параметризованные операторы?
Ответы:
Ссылки, которые я разместил в своих комментариях к вопросу, очень хорошо объясняют проблему. Я резюмировал свои ощущения относительно того, почему проблема не исчезла, ниже:
Те, кто только начинает, могут не знать о SQL-инъекции.
Некоторые знают о SQL-инъекции, но думают, что экранирование - это (единственное?) Решение. Если вы выполните быстрый поиск в Google php mysql query
, первая страница, которая появится, будет mysql_query
страницей, на которой есть пример, показывающий интерполяцию экранированного ввода пользователя в запрос. Нет упоминания (по крайней мере, я не вижу) использования вместо этого подготовленных операторов. Как уже говорили другие, существует так много руководств, которые используют интерполяцию параметров, что неудивительно, как часто она все еще используется.
Непонимание того, как работают параметризованные операторы. Некоторые думают, что это просто причудливый способ избежать ценностей.
Другие знают о параметризованных операторах, но не используют их, потому что слышали, что они слишком медленные. Я подозреваю, что многие люди слышали, насколько невероятно медленными являются параметризованные утверждения, но на самом деле не проводили собственного тестирования. Как отметил Билл Карвин в своем выступлении, разницу в производительности редко следует использовать как фактор при рассмотрении использования подготовленных операторов. Преимущества подготовки один раз и выполнения многих часто забывают, равно как и о повышении безопасности и удобстве сопровождения кода.
Некоторые используют параметризованные операторы везде, но с интерполяцией непроверенных значений, таких как имена таблиц и столбцов, ключевые слова и условные операторы. Динамический поиск, такой как тот, который позволяет пользователям указывать несколько различных полей поиска, условия сравнения и порядок сортировки, являются яркими примерами этого.
Ложное чувство безопасности при использовании ORM. ORM по-прежнему позволяют интерполировать части оператора SQL - см. 5.
Программирование - это большая и сложная тема, управление базами данных - большая и сложная тема, безопасность - большая и сложная тема. Разработать безопасное приложение для базы данных непросто - даже опытные разработчики могут попасть в ловушку.
Многие ответы на stackoverflow не помогают. Когда люди пишут вопросы, использующие динамический SQL и интерполяцию параметров, часто не хватает ответов, предлагающих вместо этого использовать параметризованные операторы. В нескольких случаях люди отвергали мое предложение использовать подготовленные операторы - обычно из-за неприемлемых накладных расходов на производительность. Я серьезно сомневаюсь, что те, кто задает большинство из этих вопросов, находятся в положении, когда дополнительные несколько миллисекунд, потраченные на подготовку параметризованного оператора, будут иметь катастрофический эффект для их приложения.
Когда в статьях говорится о параметризованных запросах, предотвращающих атаки SQL, они на самом деле не объясняют, почему. Часто бывает так: «Да, так что не спрашивайте почему» - возможно, потому, что они сами не знают. Верный признак плохого педагога - тот, кто не может признать, что чего-то не знает. Но я отвлекся. Когда я говорю, что совершенно понятно запутаться, это просто. Представьте себе динамический SQL-запрос
sqlQuery='SELECT * FROM custTable WHERE User=' + Username + ' AND Pass=' + password
так что простая инъекция sql будет просто помещать имя пользователя как 'OR 1 = 1 - это эффективно сделало бы запрос sql:
sqlQuery='SELECT * FROM custTable WHERE User='' OR 1=1-- ' AND PASS=' + password
Это говорит о том, что выберите всех клиентов, у которых их имя пользователя пустое ('') или 1 = 1, что является логическим значением, равным истине. Затем он использует -, чтобы закомментировать остальную часть запроса. Таким образом, он просто распечатает всю таблицу клиентов или сделает с ней все, что вы хотите, при входе в систему он войдет в систему с привилегиями первого пользователя, которым часто может быть администратор.
Теперь параметризованные запросы делают это иначе, с таким кодом, как:
sqlQuery='SELECT * FROM custTable WHERE User=? AND Pass=?'
parameters.add("User", username)
parameters.add("Pass", password)
где имя пользователя и пароль - это переменные, указывающие на связанные введенные имя пользователя и пароль
Теперь вы можете подумать, что это вообще ничего не меняет. Конечно, вы все равно можете просто ввести в поле имени пользователя что-то вроде Nobody OR 1 = 1 '-, эффективно выполняя запрос:
sqlQuery='SELECT * FROM custTable WHERE User=Nobody OR 1=1'-- AND Pass=?'
И это, казалось бы, веский аргумент. Но вы ошибаетесь.
Принцип работы параметризованных запросов заключается в том, что sqlQuery отправляется как запрос, и база данных точно знает, что будет делать этот запрос, и только после этого она вставит имя пользователя и пароли просто как значения. Это означает, что они не могут повлиять на запрос, потому что база данных уже знает, что будет делать запрос. Таким образом, в этом случае он будет искать имя пользователя «Никто ИЛИ 1 = 1 '-» и пустой пароль, который должен оказаться ложным.
Однако это не полное решение, и проверка ввода все равно потребуется, так как это не повлияет на другие проблемы, такие как атаки XSS, поскольку вы все равно можете поместить javascript в базу данных. Затем, если это зачитывается на странице, он будет отображать его как обычный javascript, в зависимости от любой проверки вывода. Поэтому на самом деле лучше всего по-прежнему использовать проверку ввода, но использовать параметризованные запросы или хранимые процедуры для предотвращения любых атак SQL.
Ну хороший вопрос. Ответ скорее стохастический, чем детерминированный, и я попытаюсь объяснить свою точку зрения на небольшом примере.
В сети много ссылок, которые предлагают нам использовать параметры в наших запросах или использовать хранимую процедуру с параметрами, чтобы избежать внедрения SQL (SQLi). Я покажу вам, что хранимые процедуры (например) не являются волшебной палочкой против SQLi. Ответственность по-прежнему остается на программисте.
Рассмотрим следующую хранимую процедуру SQL Server, которая получит строку пользователя из таблицы «Пользователи»:
create procedure getUser
@name varchar(20)
,@pass varchar(20)
as
declare @sql as nvarchar(512)
set @sql = 'select usrID, usrUName, usrFullName, usrRoleID '+
'from Users '+
'where usrUName = '''+@name+''' and usrPass = '''+@pass+''''
execute(@sql)
Вы можете получить результаты, передав в качестве параметров имя пользователя и пароль. Предположим, что пароль находится в виде произвольного текста (для простоты этого примера), нормальный вызов будет:
DECLARE @RC int
DECLARE @name varchar(20)
DECLARE @pass varchar(20)
EXECUTE @RC = [dbo].[getUser]
@name = 'admin'
,@pass = '!@Th1siSTheP@ssw0rd!!'
GO
Но здесь у нас есть плохая техника программирования, используемая программистом внутри хранимой процедуры, поэтому злоумышленник может выполнить следующее:
DECLARE @RC int
DECLARE @name varchar(20)
DECLARE @pass varchar(20)
EXECUTE @RC = [TestDB].[dbo].[getUser]
@name = 'admin'
,@pass = 'any'' OR 1=1 --'
GO
Вышеупомянутые параметры будут переданы в качестве аргументов хранимой процедуре, а команда SQL, которая будет выполнена в конце, будет следующей:
select usrID, usrUName, usrFullName, usrRoleID
from Users
where usrUName = 'admin' and usrPass = 'any' OR 1=1 --'
..который вернет все строки от пользователей
Проблема здесь в том, что даже если мы следуем принципу «Создайте хранимую процедуру и передайте поля для поиска в качестве параметров», SQLi все равно выполняется. Это потому, что мы просто копируем наши плохие методы программирования внутри хранимой процедуры. Решение проблемы - переписать нашу хранимую процедуру следующим образом:
alter procedure getUser
@name varchar(20)
,@pass varchar(20)
as
select usrID, usrUName, usrFullName, usrRoleID
from Users
where usrUName = @name and usrPass = @pass
Я пытаюсь сказать, что разработчики должны сначала узнать, что такое SQLi-атака и как ее проводить, а затем соответствующим образом защитить свой код. Слепое следование «лучшим практикам» не всегда является более безопасным способом ... и, возможно, именно поэтому у нас так много «лучших практик» - неудач!
Да, использование подготовленных операторов останавливает все SQL-инъекции, по крайней мере, теоретически. На практике параметризованные операторы могут не быть настоящими подготовленными операторами, например, PDO
в PHP их эмулируют по умолчанию, поэтому он открыт для атаки крайнего случая .
Если вы используете настоящие подготовленные операторы, все в безопасности. Ну, по крайней мере, до тех пор, пока вы не добавляете небезопасный SQL в свой запрос в качестве реакции, например, на невозможность подготовить имена таблиц.
Если да, то почему до сих пор так много успешных SQL-инъекций? Просто потому, что некоторые разработчики слишком глупы, чтобы использовать параметризованные операторы?
Да, здесь главное образование и устаревшие кодовые базы. Многие учебники используют экранирование, и, к сожалению, их нелегко удалить из Интернета.
Я избегаю абсолютов в программировании; всегда есть исключение. Я настоятельно рекомендую хранимые процедуры и объекты команд. По большей части я работал с SQL Server, но время от времени я играю с MySql. Хранимые процедуры имеют много преимуществ, включая кэшированные планы запросов; да, это можно сделать с помощью параметров и встроенного SQL, но это открывает больше возможностей для атак с помощью инъекций и не помогает в разделении проблем. Для меня также намного проще защитить базу данных, поскольку мои приложения обычно имеют разрешение на выполнение только для указанных хранимых процедур. Без прямого доступа к таблице / просмотру гораздо труднее что-либо внедрить. Если пользователь приложения скомпрометирован, у него есть разрешение только на выполнение того, что было предварительно определено.
Мои два цента.
Я бы не сказал «тупой».
Я думаю, что уроки - это проблема. Большинство руководств по SQL, книг и всего, что объясняет SQL со встроенными значениями, вообще не говоря уже о параметрах привязки. Люди, которые учатся на этих уроках, не имеют возможности усвоить это правильно.
Поскольку большая часть кода написана не с учетом безопасности и управления, при выборе между добавлением функций (особенно видимых, которые можно продать) и безопасностью / стабильностью / надежностью (что гораздо сложнее продать), они почти всегда будут выбирать бывший. Безопасность вызывает беспокойство только тогда, когда становится проблемой.
Может ли параметризованный оператор остановить все SQL-инъекции?
Да, если ваш драйвер базы данных предлагает заполнитель для всех возможных литералов SQL. Большинство подготовленных драйверов операторов этого не делают. Скажем, вы никогда не найдете заполнитель для имени поля или для массива значений. Это заставит разработчика вернуться к настройке запроса вручную, используя конкатенацию и ручное форматирование. С прогнозируемым исходом.
Вот почему я сделал свою оболочку Mysql для PHP, которая поддерживает большинство литералов, которые могут быть добавлены в запрос динамически, включая массивы и идентификаторы.
Если да, то почему до сих пор так много успешных SQL-инъекций? Просто потому, что некоторые разработчики слишком глупы, чтобы использовать параметризованные операторы?
Как видите, в реальности параметризовать все запросы просто невозможно , даже если вы не тупица.
Сначала мой ответ на ваш первый вопрос: да, насколько мне известно, с помощью параметризованных запросов SQL-инъекции больше не будут возможны. Что касается ваших следующих вопросов, я не уверен и могу высказать только свое мнение о причинах:
Я думаю, что проще «просто» написать строку SQL-запроса, объединив несколько разных частей (возможно, даже в зависимости от некоторых логических проверок) вместе со значениями, которые нужно вставить. Он просто создает запрос и выполняет его. Еще одно преимущество заключается в том, что вы можете распечатать (эхо, вывод или что-то еще) строку запроса sql, а затем использовать эту строку для ручного запроса к ядру базы данных.
При работе с подготовленными операторами у вас всегда есть как минимум на один шаг больше: вы должны создать свой запрос (включая, конечно, параметры). Необходимо подготовить запрос на сервере. Необходимо привязать параметры к фактическим значениям, которые вы хотите. для использования в вашем запросе Вы должны выполнить запрос.
Это немного больше работы (и ее не так просто программировать), особенно для некоторых "быстрых и грязных" работ, которые часто оказываются очень долгими ...
С уважением,
Коробка
Внедрение SQL - это подмножество более крупной проблемы внедрения кода, когда данные и код предоставляются по одному и тому же каналу, а данные ошибочно принимаются за код. Параметризованные запросы предотвращают это, формируя запрос с использованием контекста о том, что такое данные и что такое код.
В некоторых конкретных случаях этого недостаточно. Во многих СУБД можно динамически выполнять SQL с хранимыми процедурами, что приводит к ошибкам внедрения SQL на уровне СУБД. Вызов такой хранимой процедуры с использованием параметризованных запросов не предотвратит использование SQL-инъекции в процедуру. Другой пример можно увидеть в этом сообщении в блоге .
Чаще всего разработчики неправильно используют функциональность. Обычно код выглядит примерно так, если все сделано правильно:
db.parameterize_query("select foo from bar where baz = '?'", user_input)
Некоторые разработчики объединяют строки вместе, а затем используют параметризованный запрос, который на самом деле не делает вышеупомянутого различия данных / кода, что обеспечивает гарантии безопасности, которые мы ищем:
db.parameterize_query("select foo from bar where baz = '" + user_input + "'")
Правильное использование параметризованных запросов обеспечивает очень надежную, но не непроницаемую защиту от атак с использованием SQL-инъекций.
Чтобы защитить ваше приложение от SQL-инъекции, выполните следующие действия:
Шаг 1. Ограничьте ввод. Шаг 2. Используйте параметры с хранимыми процедурами. Шаг 3. Используйте параметры с динамическим SQL.
даже если подготовленные операторы правильно используются во всем собственном коде веб-приложения, недостатки внедрения SQL могут все еще существовать, если компоненты кода базы данных создают запросы из пользовательского ввода небезопасным образом. Ниже приведен пример хранимой процедуры, уязвимой для SQL-инъекции в параметре @name:
CREATE PROCEDURE show_current_orders
(@name varchar(400) = NULL)
AS
DECLARE @sql nvarchar(4000)
SELECT @sql = ‘SELECT id_num, searchstring FROM searchorders WHERE ‘ +
‘searchstring = ‘’’ + @name + ‘’’’;
EXEC (@sql)
GO
Даже если приложение передает указанное пользователем значение имени в хранимую процедуру безопасным способом, сама процедура объединяет его непосредственно в динамический запрос и, следовательно, уязвима.