Почему бы просто не заставить непараметрические запросы возвращать ошибку?

22

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

Мне кажется, что очевидный способ исправить это - отключить очевидную (но неправильную) опцию: исправить механизм базы данных так, чтобы любой полученный запрос, использующий жестко закодированные значения в своем предложении WHERE вместо параметров, возвращал хороший, описательный сообщение об ошибке, указывающее вам использовать параметры вместо. Очевидно, что для этого потребуется опция отказа, чтобы такие вещи, как специальные запросы от административных инструментов, по-прежнему выполнялись легко, но по умолчанию они должны быть включены.

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

Мейсон Уилер
источник
22
bad_ideas_sql = 'SELECT title FROM idea WHERE idea.status == "bad" AND idea.user == :mwheeler'будет иметь жестко запрограммированные и параметризованные значения в одном запросе - попробуйте поймать это! Я думаю, что есть допустимые варианты использования для таких смешанных запросов.
Амон
6
Как насчет выбора записей с сегодняшнего дняSELECT * FROM jokes WHERE date > DATE_SUB(NOW(), INTERVAL 1 DAY) ORDER BY score DESC;
Jaydee
10
@MasonWheeler извините, я имел в виду «попытаться это разрешить». Обратите внимание, что он отлично параметризован и не подвержен внедрению SQL. Однако драйвер базы данных не может определить, "bad"является ли литерал действительно литералом или результатом конкатенации строк. Два решения, которые я вижу, это либо избавление от SQL и других встроенных в строки DSL (да, пожалуйста), либо продвижение языков, в которых конкатенация строк более раздражает, чем использование параметризованных запросов (мм, нет).
Амон
4
и как СУБД обнаружит, делать ли это? В одночасье было бы невозможно получить доступ к СУБД с помощью интерактивной подсказки SQL ... Вы больше не сможете вводить команды DDL или DML с помощью какого-либо инструмента вообще.
jwenting
8
В некотором смысле вы можете сделать это: вообще не создавайте SQL-запросы во время выполнения, вместо этого используйте ORM или какой-либо другой уровень абстракции, который избавляет вас от необходимости создавать SQL-запросы. ORM не имеет функций, которые вам нужны? Тогда SQL - это язык, предназначенный для людей, которые хотят писать SQL, поэтому в целом он позволяет им писать SQL. Основная проблема заключается в том, что динамически генерировать код сложнее, чем кажется, но люди все равно захотят это сделать и будут недовольны продуктами, которые им не позволяют.
Стив Джессоп

Ответы:

45

Слишком много случаев, когда использование литерала является правильным подходом.

С точки зрения производительности, в ваших запросах иногда требуются литералы. Представьте, что у меня есть баг-трекер, где, как только он станет достаточно большим, чтобы беспокоиться о производительности, я ожидаю, что 70% ошибок в системе будут «закрыты», 20% будут «открыты», 5% будут «активными» и 5 % будет в другом статусе. Я могу разумно хотеть иметь запрос, который возвращает все активные ошибки, чтобы быть

SELECT *
  FROM bug
 WHERE status = 'active'

вместо передачи в statusкачестве переменной связывания. Я хочу другой план запроса в зависимости от значения, переданного для status- я хотел бы сделать сканирование таблицы, чтобы вернуть закрытые ошибки и сканирование индекса наstatusстолбец для возврата активных кредитов. Теперь разные базы данных и разные версии имеют разные подходы (более или менее успешно), позволяющие одному и тому же запросу использовать другой план запроса в зависимости от значения переменной bind. Но это имеет тенденцию вносить приличную сложность, которой необходимо управлять, чтобы сбалансировать решение о том, стоит ли повторять разбор запроса или повторно использовать существующий план для нового значения переменной связывания. Для разработчика может иметь смысл иметь дело с этой сложностью. Или может иметь смысл использовать другой путь, когда у меня есть больше информации о том, как будут выглядеть мои данные, чем оптимизатор.

С точки зрения сложности кода, есть много раз, когда имеет смысл иметь литералы в операторах SQL. Например, если у вас есть zip_codeстолбец с 5-символьным почтовым индексом и иногда с дополнительными 4 цифрами, имеет смысл сделать что-то вроде

SELECT substr( zip_code, 1, 5 ) zip,
       substr( zip_code, 7, 4 ) plus_four

вместо того, чтобы передавать 4 отдельных параметра для числовых значений. Это не вещи, которые когда-либо будут меняться, поэтому связывание их с переменными только делает код потенциально более трудным для чтения и создает вероятность того, что кто-то будет связывать параметры в неправильном порядке и в результате получит ошибку.

Джастин Кейв
источник
12

Инъекция SQL происходит, когда запрос строится путем объединения текста из ненадежного и неподтвержденного источника с другими частями запроса. Хотя такое часто случается со строковыми литералами, это не единственный способ, которым это может произойти. Запрос числовых значений может принимать введенную пользователем строку (которая должна содержать только цифры) и объединяться с другим материалом для формирования запроса без кавычек, обычно связанных со строковыми литералами; код, который чрезмерно доверяет проверке на стороне клиента, может иметь такие вещи, как имена полей, взятые из строки запроса HTML. Нет никакого способа, которым код, смотрящий на строку запроса SQL, может видеть, как она была собрана.

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

Supercat
источник
1
В качестве приближения для "является ли это литералом" вы можете проверить, интернирована ли строка.
CodesInChaos
1
@CodesInChaos: Верно, и такой тест может быть достаточно точным для этой цели, при условии, что любой, у кого была причина для генерации строки во время выполнения, использовал метод, который принимал не-литеральную строку, а не интернировал строку, генерируемую во время выполнения, и использовал это (присвоение методу, не являющемуся литеральной строкой, другим именем, для рецензентов кода будет легко проверить все его применения).
суперкат
Обратите внимание, что, хотя в C # нет способа сделать это, некоторые другие языки имеют возможности, которые делают это возможным (например, модуль испорченных строк Perl).
Жюль
Вкратце, это проблема клиента , а не проблема сервера.
Blrfl
7
SELECT count(ID)
FROM posts
WHERE deleted = false

Если вы хотите поместить результаты этих исследований в нижний колонтитул вашего форума, вам нужно будет добавить фиктивный параметр, чтобы каждый раз произносить false. Или наивный веб-программист ищет, как отключить это предупреждение, и затем продолжает.

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

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

чокнутый урод
источник
2
Если ваше решение «слишком легко забыть - или вообще не знать - использовать параметризованные запросы» - это «заставить всех вспомнить - и знать в первую очередь - использовать хранимые процессы», тогда вы пропустили всю суть вопроса.
Мейсон Уилер
5
Я видел инъекцию SQL через хранимые процедуры на моей работе. Оказывается, обязательные хранимые процедуры для всего ПЛОХО. Всегда есть те 0,5%, которые являются настоящими динамическими запросами (вы не можете параметризовать весь оператор where, не говоря уже о соединении таблиц).
Джошуа
В примере в этом ответе вы можете заменить deleted = falseна NOT deleted, что позволяет избежать литерала. Но дело действительно в общем.
psmears
5

TL; DR : Вы должны ограничить все литералы, а не только те, которые WHEREуказаны в пунктах. По причинам, по которым они этого не делают, база данных остается отделенной от других систем.

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

SELECT
    COUNT(CASE WHEN item_type = 'blender' THEN 1 END) as type1_count,
    COUNT(CASE WHEN item_type = 'television' THEN 1 END) AS type2_count)
FROM item

Это в равной степени уязвимо для внедрения SQL:

SELECT
    COUNT(CASE WHEN item_type = 'blender' THEN 1 END) FROM item; DROP TABLE user_info; SELECT CASE(WHEN item_type = 'blender' THEN 1 END) as type1_count,
    COUNT(CASE WHEN item_type = 'television' THEN 1 END) AS type2_count)
FROM item

Таким образом, вы не можете просто ограничить литералы в WHEREпредложении. Вы должны ограничить все литералы.

Теперь у нас остался вопрос: «Зачем вообще разрешать литералы?» Помните об этом: хотя реляционные базы данных используются под приложением, написанным на другом языке, большой процент времени, не требуется, чтобы вы использовали код приложения для использования базы данных. И здесь у нас есть ответ: для написания кода нужны литералы. Единственной альтернативой может быть требование написания всего кода на каком-либо языке, независимом от базы данных. Таким образом, наличие их дает вам возможность писать «код» (SQL) непосредственно в базе данных. Это ценная развязка, и без литералов это было бы невозможно. (Попробуйте написать на своем любимом языке иногда без литералов. Я уверен, что вы можете себе представить, насколько это будет сложно.)

В качестве общего примера, литералы часто используются в заполнении таблиц списка значений / поиска:

CREATE TABLE user_roles (role_id INTEGER, role_name VARCHAR(50));
INSERT INTO user_roles (1, 'normal');
INSERT INTO user_roles (2, 'admin');
INSERT INTO user_roles (3, 'banned');

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

Затем у нас остается еще один вопрос: почему тогда клиентские библиотеки языка программирования не делают этого? И здесь у нас очень простой ответ: им пришлось бы заново реализовать весь анализатор базы данных для каждой поддерживаемой версии базы данных . Зачем? Потому что нет другого способа гарантировать, что вы нашли каждый литерал. Регулярных выражений недостаточно. Например: это содержит 4 отдельных литерала в PostgreSQL:

SELECT $lit1$I'm a literal$lit1$||$lit2$I'm another literal $$ with nested string delimiters$$ $lit2$||'I''m ANOTHER literal'||$$I'm the last literal$$;

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

jpmc26
источник