Чи може параметризований оператор зупинити всі введення SQL?


80

Якщо так, чому все ще так багато успішних ін’єкцій SQL? Тільки тому, що деякі розробники занадто тупі, щоб використовувати параметризовані оператори?


6
Це чудове запитання з абсолютно жахливими відповідями (на той час, що я коментую)
Ібу,

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

4
Для отримання додаткової інформації на цю тему дивіться розмову та слайди Біллі Карвіна про ін’єкції скв . Він пояснює, що таке SQL-ін'єкція, як екранінг зазвичай недостатньо хороший, і як можна порушити збережені процедури та параметризовані оператори.
Mike

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 було б просто поставити ім'я користувача як "АБО 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)

де ім'я користувача та пароль - це змінні, що вказують на пов'язані введені ім'я користувача та пароль

Зараз на цьому етапі ви можете думати, це взагалі нічого не змінює. Звичайно, ви все ще можете просто ввести в поле імені користувача щось на зразок Ніхто АБО 1 = 1 '-, ефективно роблячи запит:

sqlQuery='SELECT * FROM custTable WHERE User=Nobody OR 1=1'-- AND Pass=?'

І це здавалося б вагомим аргументом. Але ти помилишся.

Параметризовані запити працюють так, що sqlQuery надсилається як запит, і база даних точно знає, що буде робити цей запит, і лише тоді вона буде вставляти ім’я користувача та паролі просто як значення. Це означає, що вони не можуть впливати на запит, оскільки база даних вже знає, що буде робити запит. Тож у цьому випадку він буде шукати ім’я користувача "Ніхто АБО 1 = 1 '-" та порожній пароль, який повинен бути помилковим.

Це, однак, не є повноцінним рішенням, і перевірку вводу все одно потрібно буде зробити, оскільки це не призведе до інших проблем, таких як атаки XSS, оскільки ви все одно можете помістити javascript у базу даних. Потім, якщо це прочитано на сторінці, воно відображатиметься як звичайний javascript, залежно від будь-якої перевірки вихідних даних. Тож найкраще все-таки використовувати перевірку вводу, але використовувати параметризовані запити або збережені процедури для зупинки будь-яких атак SQL.


1
Це багато додає до того, що я шукав, але чи можете ви пояснити більше про те, що ви зробите для "перевірки вводу?" Ви також згадали, що з цим запитом можуть бути інші атаки, тобто XSS, але чи можете ви пояснити, як це може статися? Отже, по суті, як би ми в повній мірі захистили від SQL Injection, чи ми розглядаємо всі типи ін’єкцій? Дякую.
XaolingBao

3
@JosipIvic: Враховуючи, скільки людей запитували, як працюють параметризовані висловлювання, вражає бачити так мало - якщо взагалі - справді - розбийте відповідь, як ви. Дякуємо за написання такого чіткого пояснення на досить інтуїтивному прикладі.
daOnlyBG

Блискуче. Приклад малює тисячу слів, як вони кажуть!
Дренай

10

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

У мережі є багато посилань, які пропонують нам використовувати параметри в наших запитах або використовувати збережену процедуру з параметрами, щоб уникнути SQL Injection (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
Тім

@Tim Привіт тим. Я новачок у динамічному 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, принаймні теоретично. На практиці параметризовані оператори можуть не бути реальними підготовленими операторами, наприклад, PDOPHP емулює їх за замовчуванням, тому він відкритий для крайової атаки .

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

Якщо так, чому все ще так багато успішних ін’єкцій SQL? Тільки тому, що деякі розробники занадто тупі, щоб використовувати параметризовані оператори?

Так, освіта - це головне, а основи застарілого коду. У багатьох підручниках використовується екранування, і їх, на жаль, неможливо легко видалити з Інтернету.


1
Відповідна відповідь насправді не має нічого спільного з підготовленими висловлюваннями.
Ваш здоровий глузд

1
@YourCommonSense: Йдеться про параметризовані запити, вони можуть бути не фактичними, а емульованими залежно від використовуваного драйвера. Важливо знати і дуже пов’язані ...
kelunik

1
Інші відповіді на тій самій сторінці мають дуже хороший коментар: "Якщо ВСІ ваші запити параметризовані, ви також захищені від ін'єкції другого порядку. Ін'єкція 1-го порядку - це забування того, що дані користувача ненадійні. Ін'єкція 2-го порядку - це забуття того, що дані бази даних є ненадійний (оскільки спочатку він надійшов від користувача). "
Родріго

@kelunik пов'язана відповідь теж не стосується параметризованих запитів, це бібліотека, яка по суті підробляє їх. Параметризований запит - це той, який надсилається на сервер з окремими значеннями параметрів.
Панайотис Канавос

@PanagiotisKanavos: Я досить добре знаю зміст цієї відповіді. Це лише приклад (і досить поширений), що параметризовані запити, які ви використовуєте, насправді не можуть бути реалізовані як підготовлені оператори ...
kelunik

3

Я уникаю абсолютів у програмуванні; завжди є виняток. Я настійно рекомендую збережені процедури та об'єкти команд. Більшість моїх попередніх досліджень стосується SQL Server, але я час від часу граю з MySql. Зберігаються процедури мають багато переваг, включаючи кешовані плани запитів; так, це можна зробити за допомогою параметрів та вбудованого SQL, але це відкриває більше можливостей для ін'єкційних атак і не допомагає розділити проблеми. Для мене також набагато простіше захистити базу даних, оскільки мої програми, як правило, мають лише дозвіл на виконання вказаних збережених процедур. Без прямого доступу до таблиці / перегляду набагато складніше ввести щось. Якщо користувач додатків скомпрометований, один має дозвіл виконувати саме те, що було попередньо визначено.

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


Як це стосується питання? Як ви збираєтеся викликати та передавати параметри збереженій процедурі? За допомогою конкатенації рядків або за допомогою параметризованого запиту? Крім того - що, якщо хтось використовує конкатенацію рядків усередині збереженої процедури для створення "динамічного" запиту? Те, що це збережена процедура, не означає, що вона безпечніша
Панайотис Канавос

Як правило, я використовую командний об'єкт, а також уникаю запуску "динамічних запитів" за проектом.
Дерек,

2

Я б не сказав "німий".

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


2
Це недостатньо. Чому люди не використовують фреймворк чи якусь орму? Чому вони не тестують на «тупу ін’єкцію» за допомогою якогось дурного тестера? Тому що іноді бос погано платить вам, або він платить вам X грошей за проект, і вам потрібно переходити від проекту до іншого проекту і від одного до іншого, щоб отримати гроші. Ви повинні бути швидшими і швидшими. Кодер перебуває під напругою та надмірно натискає, тому код працює, але написаний погано.
jedi

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

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

Це дещо більше роботи (і не так просто програмувати), особливо для деяких "швидких і брудних" робіт, які часто виявляються дуже довгоживучими ...

З найкращими побажаннями,

Коробка


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

@Fahad Я міг би переформатувати №2 як "Використовувати параметризовані оператори в запитах і в збережених процедурах." +1 до коментаря Новелократа про те, що використання збережених процедур без параметрів вам не надто допомагає.
Матвій

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

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

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