Может ли параметризованный оператор остановить все SQL-инъекции?

80

Если да, то почему до сих пор так много успешных SQL-инъекций? Просто потому, что некоторые разработчики слишком глупы, чтобы использовать параметризованные операторы?

ледяной орел
источник
6
Это отличный вопрос с абсолютно ужасными ответами (на данный момент я комментирую)
Ибу
Я желаю, чтобы кто-то с хорошей репутацией хотя бы 15k или с хорошим опытом внес ценный вклад в этот вопрос.
Ibu
4
См Билл Karwin в Sql Injection Мифы и Заблуждения говорить и слайды для получения дополнительной информации по этому вопросу. Он объясняет, что такое SQL-инъекция, почему экранирование обычно недостаточно и как могут быть скомпрометированы хранимые процедуры и параметризованные операторы.
Майк,
2
Также посмотрите некоторые ответы Билла Карвина на похожие вопросы: Что такое SQL-инъекция?
Mike

Ответы:

66

Ссылки, которые я разместил в своих комментариях к вопросу, очень хорошо объясняют проблему. Я резюмировал свои ощущения относительно того, почему проблема не исчезла, ниже:

  1. Те, кто только начинает, могут не знать о SQL-инъекции.

  2. Некоторые знают о SQL-инъекции, но думают, что экранирование - это (единственное?) Решение. Если вы выполните быстрый поиск в Google php mysql query, первая страница, которая появится, будет mysql_queryстраницей, на которой есть пример, показывающий интерполяцию экранированного ввода пользователя в запрос. Нет упоминания (по крайней мере, я не вижу) использования вместо этого подготовленных операторов. Как уже говорили другие, существует так много руководств, которые используют интерполяцию параметров, что неудивительно, как часто она все еще используется.

  3. Непонимание того, как работают параметризованные операторы. Некоторые думают, что это просто причудливый способ избежать ценностей.

  4. Другие знают о параметризованных операторах, но не используют их, потому что слышали, что они слишком медленные. Я подозреваю, что многие люди слышали, насколько невероятно медленными являются параметризованные утверждения, но на самом деле не проводили собственного тестирования. Как отметил Билл Карвин в своем выступлении, разницу в производительности редко следует использовать как фактор при рассмотрении использования подготовленных операторов. Преимущества подготовки один раз и выполнения многих часто забывают, равно как и о повышении безопасности и удобстве сопровождения кода.

  5. Некоторые используют параметризованные операторы везде, но с интерполяцией непроверенных значений, таких как имена таблиц и столбцов, ключевые слова и условные операторы. Динамический поиск, такой как тот, который позволяет пользователям указывать несколько различных полей поиска, условия сравнения и порядок сортировки, являются яркими примерами этого.

  6. Ложное чувство безопасности при использовании ORM. ORM по-прежнему позволяют интерполировать части оператора SQL - см. 5.

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

  8. Многие ответы на stackoverflow не помогают. Когда люди пишут вопросы, использующие динамический SQL и интерполяцию параметров, часто не хватает ответов, предлагающих вместо этого использовать параметризованные операторы. В нескольких случаях люди отвергали мое предложение использовать подготовленные операторы - обычно из-за неприемлемых накладных расходов на производительность. Я серьезно сомневаюсь, что те, кто задает большинство из этих вопросов, находятся в положении, когда дополнительные несколько миллисекунд, потраченные на подготовку параметризованного оператора, будут иметь катастрофический эффект для их приложения.

Майк
источник
65

Когда в статьях говорится о параметризованных запросах, предотвращающих атаки 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.

Йосип Ивич
источник
1
Это многое добавляет к тому, что я искал, но не могли бы вы объяснить подробнее, что бы вы сделали для «проверки ввода»? Вы также упомянули, что есть и другие атаки, которые могут иметь место с запросом, например XSS, но не могли бы вы объяснить, как это произойдет? Итак, по сути, как мы можем полностью защититься от SQL-инъекций, или мы ищем все типы инъекций? Благодарю.
XaolingBao
3
@JosipIvic: Учитывая, сколько людей спрашивали, как работают параметризованные утверждения, шокирует то, что так мало - если вообще вообще есть - ломают ответ, как это сделали вы. Спасибо, что написали такое ясное объяснение с довольно наглядным примером.
daOnlyBG
Блестяще. Пример раскрашивает тысячу слов как говорится!
Дренай
10

Ну хороший вопрос. Ответ скорее стохастический, чем детерминированный, и я попытаюсь объяснить свою точку зрения на небольшом примере.

В сети много ссылок, которые предлагают нам использовать параметры в наших запросах или использовать хранимую процедуру с параметрами, чтобы избежать внедрения 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-запроса, в котором я использовал конкатенацию параметров. Как вы посоветуете мне это сделать?
TheProvost
@TheProvost, это хороший вопрос. Рассмотрим sp_executesql: msdn.microsoft.com/en-us/library/ms188001.aspx
Тим,
@ Тим Привет, Тим. Я новичок в динамическом sql. В чем разница между sp_executesql и EXECUTE (@SqlQuery)
TheProvost
2
я думаю, что этот пост хорошо объясняет простой пример: codeproject.com/Tips/586207/… - но в основном EXECUTE (@SqlQuery) ничего не делает для предотвращения внедрения sql, однако sp_executesql (@SqlQuery, ..., ...) действительно предотвращает это. Примеры в статье Microsoft должны помочь.
Тим
У Тима есть решение TheProvost ...;) Вы можете использовать sp_executesql (@QUERY, @PARAMETERS, @VARS) ... для случая динамического SQL ...;)
Андреас Веньерис
5

Да, использование подготовленных операторов останавливает все SQL-инъекции, по крайней мере, теоретически. На практике параметризованные операторы могут не быть настоящими подготовленными операторами, например, PDOв PHP их эмулируют по умолчанию, поэтому он открыт для атаки крайнего случая .

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

Если да, то почему до сих пор так много успешных SQL-инъекций? Просто потому, что некоторые разработчики слишком глупы, чтобы использовать параметризованные операторы?

Да, здесь главное образование и устаревшие кодовые базы. Многие учебники используют экранирование, и, к сожалению, их нелегко удалить из Интернета.

келуник
источник
1
Связанный ответ на самом деле не имеет ничего общего с подготовленными операторами.
Ваш здравый смысл
1
@YourCommonSense: речь идет о параметризованных запросах, они могут не быть фактическими подготовками, а эмулироваться в зависимости от используемого драйвера. Это важно знать и очень много связано ...
kelunik
1
Другой ответ на той же странице содержит очень хороший комментарий: «Если ВСЕ ваши запросы параметризованы, вы также защищены от внедрения 2-го порядка. Инъекция 1-го порядка забывает, что данные пользователя ненадежны. Инъекция 2-го порядка забывает, что данные базы данных являются ненадежный (поскольку изначально исходил от пользователя) ".
Родриго
@kelunik связанный ответ тоже не о параметризованных запросах, а о библиотеке, которая по сути их подделывает. Параметризованный запрос - это запрос, который отправляется на сервер с отдельными значениями параметров.
Панайотис Канавос
@PanagiotisKanavos: Я очень хорошо знаю содержание этого ответа. Это просто пример (и довольно распространенный) того, что параметризованные запросы, которые вы используете, на самом деле не могут быть реализованы как подготовленные операторы ...
kelunik
3

Я избегаю абсолютов в программировании; всегда есть исключение. Я настоятельно рекомендую хранимые процедуры и объекты команд. По большей части я работал с SQL Server, но время от времени я играю с MySql. Хранимые процедуры имеют много преимуществ, включая кэшированные планы запросов; да, это можно сделать с помощью параметров и встроенного SQL, но это открывает больше возможностей для атак с помощью инъекций и не помогает в разделении проблем. Для меня также намного проще защитить базу данных, поскольку мои приложения обычно имеют разрешение на выполнение только для указанных хранимых процедур. Без прямого доступа к таблице / просмотру гораздо труднее что-либо внедрить. Если пользователь приложения скомпрометирован, у него есть разрешение только на выполнение того, что было предварительно определено.

Мои два цента.

Дерек
источник
Как это связано с вопросом? Как вы собираетесь вызывать и передавать параметры хранимой процедуре? Используя конкатенацию строк или используя параметризованный запрос? Кроме того - что, если кто-то использует конкатенацию строк внутри хранимой процедуры для создания «динамического» запроса? Просто потому, что это хранимая процедура, не значит, что она безопаснее,
Панайотис Канавос
Обычно я использую командный объект и намеренно избегаю выполнения «динамических запросов».
Дерек
2

Я бы не сказал «тупой».

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

Маркус Винанд
источник
2
Этого недостаточно. Почему люди не используют фреймворк или какой-то орм? Почему не проверяют на "тупую инъекцию" каким-нибудь тупым тестером? Потому что иногда босс плохо вам платит или платит вам X денег за проект, и вам нужно бегать от проекта к проекту и от одного к другому, чтобы получить немного денег. Вы должны быть быстрее и быстрее. Кодер перегружен и перегружен, поэтому код работает, но плохо написан.
джедаи
2

Поскольку большая часть кода написана не с учетом безопасности и управления, при выборе между добавлением функций (особенно видимых, которые можно продать) и безопасностью / стабильностью / надежностью (что гораздо сложнее продать), они почти всегда будут выбирать бывший. Безопасность вызывает беспокойство только тогда, когда становится проблемой.

злой отто
источник
2

Может ли параметризованный оператор остановить все SQL-инъекции?

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

Вот почему я сделал свою оболочку Mysql для PHP, которая поддерживает большинство литералов, которые могут быть добавлены в запрос динамически, включая массивы и идентификаторы.

Если да, то почему до сих пор так много успешных SQL-инъекций? Просто потому, что некоторые разработчики слишком глупы, чтобы использовать параметризованные операторы?

Как видите, в реальности параметризовать все запросы просто невозможно , даже если вы не тупица.

Ваш здравый смысл
источник
Если ВСЕ ваши запросы параметризованы (исходят из пользовательских данных или данных вашей базы данных), то кажется, что вы защищены, как указано в наиболее проголосовавшем комментарии здесь: stackoverflow.com/a/134138/1086511
Родриго,
Я как бы спросил ваше мнение просто потому, что вы казались достаточно разумными. Я не думаю, что ваш метод будет лучшим, если то, что я читал в другом месте, соответствует действительности. В любом случае, я буду рад, если вы улучшите "с помощью обычных инструментов вы не сможете параметризовать ВСЕ ваши запросы".
Родриго
Я начал читать ваш ответ, пока не дойду до идеи определения «литералов SQL». Идея мне показалась не совсем правильной (казалось, перебор). Если верно, что параметризованные запросы избегают инъекций в PHP (я все еще исследую), то мой следующий шаг - избегать инъекций javascript. Затем я вернусь, чтобы изучить ваше решение. Кроме того, я использую postgres, и, может быть, ваше решение специфично для mysql?
Родриго
Хорошо, теперь я прочитал (снова) и не думаю, что «просто невозможно параметризовать все ваши запросы» - это улучшение. Это невозможно в MySQL? Разве это невозможно и в PostgreSQL? Почему? Есть ли какие-либо запросы вне моего php-скрипта? Где? Я думаю, что под идентификатором вы имеете в виду зарезервированное слово, которое вы пытаетесь удалить из своего массива $ _POST? Мне кажется, что это не выход (интуитивно, конечно, я могу ошибаться). Кроме того, я не понял: «Вы когда-нибудь пытались его связать?» Что связывать?
Родриго
Это не так-то просто найти в сети, как я думал. Пожалуйста, добавьте ссылку, если можете.
Родриго
2

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

Я думаю, что проще «просто» написать строку SQL-запроса, объединив несколько разных частей (возможно, даже в зависимости от некоторых логических проверок) вместе со значениями, которые нужно вставить. Он просто создает запрос и выполняет его. Еще одно преимущество заключается в том, что вы можете распечатать (эхо, вывод или что-то еще) строку запроса sql, а затем использовать эту строку для ручного запроса к ядру базы данных.

При работе с подготовленными операторами у вас всегда есть как минимум на один шаг больше: вы должны создать свой запрос (включая, конечно, параметры). Необходимо подготовить запрос на сервере. Необходимо привязать параметры к фактическим значениям, которые вы хотите. для использования в вашем запросе Вы должны выполнить запрос.

Это немного больше работы (и ее не так просто программировать), особенно для некоторых "быстрых и грязных" работ, которые часто оказываются очень долгими ...

С уважением,

Коробка

TomS
источник
2

Внедрение 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-инъекций.

Дэниел Кроули
источник
1

Чтобы защитить ваше приложение от SQL-инъекции, выполните следующие действия:

Шаг 1. Ограничьте ввод. Шаг 2. Используйте параметры с хранимыми процедурами. Шаг 3. Используйте параметры с динамическим SQL.

См. Http://msdn.microsoft.com/en-us/library/ff648339.aspx

Фахад Хуссейн
источник
8
Сами по себе хранимые процедуры на самом деле не помогают. Можно динамически создавать строки запроса в хранимой процедуре, как и в клиентском коде.
Фил Миллер
@Fahad Я мог бы перефразировать №2 как «Используйте параметризованные операторы в запросах и в хранимых процедурах». +1 к комментарию Novelocrat о том, что использование хранимых процедур без параметров мало что дает.
Мэтью,
1

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

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

Хадид Графика
источник