Чому б просто не змусити не параметризовані запити повернути помилку?


22

Інжекція SQL - це дуже серйозна проблема безпеки, значною мірою тому, що це так просто помилитися: очевидний, інтуїтивний спосіб побудови запиту, що включає вхід користувача, залишає вас вразливими, а правильний спосіб його пом'якшення вимагає знати про параметризований спочатку запити та ін'єкція SQL

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

Це дозволило б вимкнути інжекцію SQL холодно, майже протягом ночі, але, наскільки я знаю, жоден RDBMS насправді цього не робить. Чи є якась вагома причина, чому ні?


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;
Джейді

10
@MasonWheeler вибачте, я мав на увазі "спробуйте дозволити це". Зауважте, що він ідеально параметризований і не страждає від ін'єкції SQL. Однак драйвер бази даних не може визначити, чи "bad"справді буквальний чи справді буквальний, чи є результатом конкатенації рядків. Я бачу два рішення: або позбавлення від SQL та інших вбудованих рядків DSL (так, будь ласка), або розкрутка мов, де об'єднання рядків більше дратує, ніж використання параметризованих запитів (umm, ні).
амон

4
і як би RDBMS виявив, чи потрібно це робити? Це зробило б неможливим протягом доби неможливість доступу до RDBMS за допомогою інтерактивного запиту SQL ... Ви більше не зможете вводити команди DDL або DML за допомогою будь-якого інструменту.
липня

8
У певному сенсі ви можете це зробити: взагалі не створюйте SQL-запити під час виконання, замість цього використовуйте ORM або інший шар абстракції, який уникне необхідності побудови SQL-запитів. ORM не має необхідних функцій? Тоді SQL - це мова, призначена для людей, які хочуть писати SQL, тому загалом це дозволяє їм писати SQL. Основоположне питання полягає в тому, що динамічно генерувати код важче, ніж це виглядає, але люди все одно хочуть це зробити і будуть незадоволені продуктами, які їм не дозволяють.
Стів Джессоп

Відповіді:


45

Занадто багато випадків, коли використання буквеного тексту є правильним підходом.

З точки зору продуктивності, у запитах є випадки, коли вам потрібні літерали. Уявіть, що у мене є трекер помилок, коли, як тільки він стане досить великим, щоб турбуватися про продуктивність, я очікую, що 70% помилок у системі будуть "закриті", 20% - "відкритими", 5% будуть "активними" і 5 % буде в іншому статусі. Можливо, я б хотів мати запит, який повертає всі активні помилки

SELECT *
  FROM bug
 WHERE status = 'active'

замість передачі statusзмінної як прив'язки. Я хочу іншого плану запитів залежно від значення, переданого для status- я б хотів зробити сканування таблиці, щоб повернути закриті помилки та індексне сканування наstatusдля повернення активних позик. Тепер різні бази даних та різні версії мають різні підходи до того, що (більш-менш успішно) дозволяють одному і тому ж запиту використовувати інший план запитів залежно від значення змінної змінної. Але це, як правило, вводить пристойну кількість складності, яку потрібно керувати, щоб збалансувати рішення щодо того, чи потрібно турбуватися повторним розбором запиту, чи використовувати повторно існуючий план для нового значення змінної змінної. Для розробника може виникнути сенс розібратися з цією складністю. Або може мати сенс застосовувати інший шлях, коли я маю більше інформації про те, як виглядатимуть мої дані, ніж оптимізатор.

З точки зору складності коду, також існує багато разів, що має сенс мати літерали в операторах 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 # немає можливості писати код, який дозволить рядковому букву, але не дозволить іншим видам рядкового вираження, але може бути правило практики кодування, яке вимагає, щоб запити створювались із використанням класу побудови запитів, а не конкатенація рядків, і кожен, хто передає нелітеральну рядок конструктору запитів, повинен виправдати таку дію.


1
Як наближення до "чи це буквальний", ви можете перевірити, чи є рядок інтернованою.
CodesInChaos

1
@CodesInChaos: Правда, і такий тест може бути досить точним для цієї мети, за умови, що кожен, хто мав причину для створення рядка під час виконання, використовував метод, який приймав нелітеральну рядок, а не інтернування інтерфейсу, створеного під час виконання, та використання що (надання нелітеральному методу рядків іншої назви полегшило б рецензентам коду перевірити всі його використання).
supercat

Зауважте, що, хоча це не можливо зробити у C #, деякі інші мови мають засоби, які це роблять можливим (наприклад, модуль зіпсованого рядка Perl).
Жуль

Більш коротко це проблема клієнта , а не проблема сервера.
Blrfl

7
SELECT count(ID)
FROM posts
WHERE deleted = false

Якщо ви хочете помістити результати цих даних у колонтитул вашого форуму, вам потрібно буде щоразу додавати фіктивний параметр, щоб просто говорити помилково. Або наївний веб-програміст шукає, як відключити це попередження, а потім продовжує роботу.

Тепер ви можете сказати, що ви додали б виняток для перерахунків, але це просто знову відкриє отвір (хоча і менше). Не кажучи вже про людей, які спочатку повинні бути освіченими, щоб не використовувати varcharsїх.

Справжня проблема ін'єкції - це програмне конструювання рядка запиту. Рішення для цього - це механізм збереженої процедури та примусове використання його чи білий список дозволених запитів.


2
Якщо ваше рішення щодо "все занадто просто забути - або не знати в першу чергу - використовувати параметризовані запити" - це "змусити всіх запам'ятати - і знати в першу чергу - використовувати збережені програми", то ви Ви пропускаєте всю суть питання.
Мейсон Уілер

5
Я бачив ін'єкцію SQL через збережені процедури на своїй роботі. Виявляється, обов'язковість збережених процедур для всього - BAD. Завжди є те, що 0,5% - це справжні динамічні запити (ви не можете параметризувати цілий пункт, де додаток, не кажучи вже про таблицю).
Джошуа

У прикладі в цій відповіді можна замінити 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$$;

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.