Этот код работает правильно, потому что это:
- Параметризованный и
- Не делать динамический SQL
Чтобы SQL-инъекция работала, вы должны построить строку запроса (которую вы не делаете) и не переводить одиночные апострофы ( '
) в escape-апострофы ( ''
) (экранированные через входные параметры).
В вашей попытке передать «скомпрометированное» значение, 'Male; DROP TABLE tblActor'
строка - это просто обычная строка.
Теперь, если вы делаете что-то вроде:
DECLARE @SQL NVARCHAR(MAX);
SET @SQL = N'SELECT fields FROM table WHERE field23 = '
+ @InputParam;
EXEC(@SQL);
тогда это будет восприимчиво к SQL-инъекции, потому что этот запрос не находится в текущем, предварительно проанализированном контексте; этот запрос - просто еще одна строка на данный момент. Таким образом, значение @InputParam
может быть, '2015-10-31'; SELECT * FROM PlainTextCreditCardInfo;
и это может представлять проблему, потому что этот запрос будет представлен и выполнен, как:
SELECT fields FROM table WHERE field23 = '2015-10-31'; SELECT * FROM PlainTextCreditCardInfo;
Это одна (из нескольких) основных причин использования хранимых процедур: по своей природе более безопасная (ну, пока вы не обойдете эту безопасность, создавая запросы, как я показал выше, без проверки значений каких-либо используемых параметров). Хотя, если вам нужно построить динамический SQL, предпочтительным способом является его параметризация, используя sp_executesql
:
DECLARE @SQL NVARCHAR(MAX);
SET @SQL = N'SELECT fields FROM table WHERE field23 = @SomeDate_tmp';
EXEC sp_executesql
@SQL,
N'SomeDate_tmp DATETIME',
@SomeDate_tmp = @InputParam;
Используя этот подход, кто - то пытается передать '2015-10-31'; SELECT * FROM PlainTextCreditCardInfo;
для DATETIME
входного параметра получить ошибку при выполнении хранимой процедуры. Или даже если хранимая процедура принята @InputParameter
как NVARCHAR(100)
, она должна быть преобразована в a DATETIME
, чтобы перейти к этому sp_executesql
вызову. И даже если параметр в динамическом SQL является строковым типом, то при входе в хранимую процедуру в первую очередь любой отдельный апостроф автоматически превращается в двойной апостроф.
Существует менее известный тип атаки, при котором злоумышленник пытается заполнить поле ввода апострофами, так что строка внутри хранимой процедуры, которая будет использоваться для создания динамического SQL, но которая объявлена слишком маленькой, не может соответствовать всем и выталкивает заключительный апостроф и каким-то образом заканчивается правильным количеством апострофов, чтобы больше не «ускользать» внутри строки. Это называется усечением SQL, о котором говорилось в статье журнала MSDN под названием «Новые атаки с усечением SQL и как их избежать», автор Bala Neerumalla, но эта статья больше не находится в сети. Выпуск, содержащий эту статью - выпуск MSDN Magazine за ноябрь 2006 года - доступен только в виде файла справки Windows (в .chmформат). Если вы загрузите его, он может не открыться из-за настроек безопасности по умолчанию. Если это произойдет, щелкните правой кнопкой мыши файл MSDNMagazineNovember2006en-us.chm и выберите «Свойства». На одной из этих вкладок будет опция «Доверять этому типу файла» (или что-то в этом роде), которую необходимо проверить / включить. Нажмите кнопку «ОК» и попробуйте снова открыть файл .chm .
Еще один вариант атаки усечения заключается в том, что предполагается, что локальная переменная используется для хранения «безопасного» пользовательского значения, поскольку в нем были одинарные кавычки, удвоенные для экранирования, чтобы заполнить эту локальную переменную и поместить одинарные кавычки. в конце. Идея заключается в том, что если локальная переменная имеет неправильный размер, в конце не хватит места для второй одинарной кавычки, оставьте переменную, заканчивающуюся одинарной кавычкой, которая затем объединяется с одинарной кавычкой, которая завершает литеральное значение в динамическом SQL, превращая эту конечную одинарную кавычку во встроенную экранированную одинарную кавычку, а строковый литерал в динамическом SQL затем заканчивается следующей одинарной кавычкой, которая должна была начинать следующий строковый литерал. Например:
-- Parameters:
DECLARE @UserID INT = 37,
@NewPassword NVARCHAR(15) = N'Any Value ....''',
@OldPassword NVARCHAR(15) = N';Injected SQL--';
-- Stored Proc:
DECLARE @SQL NVARCHAR(MAX),
@NewPassword_fixed NVARCHAR(15) = REPLACE(@NewPassword, N'''', N''''''),
@OldPassword_fixed NVARCHAR(15) = REPLACE(@OldPassword, N'''', N'''''');
SELECT @NewPassword AS [@NewPassword],
REPLACE(@NewPassword, N'''', N'''''') AS [REPLACE output],
@NewPassword_fixed AS [@NewPassword_fixed];
/*
@NewPassword REPLACE output @NewPassword_fixed
Any Value ....' Any Value ....'' Any Value ....'
*/
SELECT @OldPassword AS [@OldPassword],
REPLACE(@OldPassword, N'''', N'''''') AS [REPLACE output],
@OldPassword_fixed AS [@OldPassword_fixed];
/*
@OldPassword REPLACE output @OldPassword_fixed
;Injected SQL-- ;Injected SQL-- ;Injected SQL--
*/
SET @SQL = N'UPDATE dbo.TableName SET [Password] = N'''
+ @NewPassword_fixed + N''' WHERE [TableNameID] = '
+ CONVERT(NVARCHAR(10), @UserID) + N' AND [Password] = N'''
+ @OldPassword_fixed + N''';';
SELECT @SQL AS [Injected];
Здесь, динамический SQL для выполнения теперь:
UPDATE dbo.TableName SET [Password] = N'Any Value ....'' WHERE [TableNameID] = 37 AND [Password] = N';Injected SQL--';
Тот же самый динамический SQL в более читаемом формате:
UPDATE dbo.TableName SET [Password] = N'Any Value ....'' WHERE [TableNameID] = 37 AND [Password] = N';
Injected SQL--';
Исправить это легко. Просто выполните одно из следующих действий:
- НЕ ИСПОЛЬЗУЙТЕ ДИНАМИЧЕСКИЙ SQL, ЕСЛИ НЕ СЛЕДУЕТ НЕОБХОДИМЫ! (Я перечисляю это сначала, потому что это действительно должно быть первым, что нужно рассмотреть).
- Правильный размер локальной переменной (т. Е. Должен быть в два раза больше размера входного параметра, на случай, если все переданные символы являются одинарными кавычками).
Не используйте локальную переменную для хранения «фиксированного» значения; Просто поместите REPLACE()
непосредственно в создание динамического SQL:
SET @SQL = N'UPDATE dbo.TableName SET [Password] = N'''
+ REPLACE(@NewPassword, N'''', N'''''') + N''' WHERE [TableNameID] = '
+ CONVERT(NVARCHAR(10), @UserID) + N' AND [Password] = N'''
+ REPLACE(@OldPassword, N'''', N'''''') + N''';';
SELECT @SQL AS [No SQL Injection here];
Динамический SQL больше не скомпрометирован:
UPDATE dbo.TableName SET [Password] = N'Any Value ....''' WHERE [TableNameID] = 37 AND [Password] = N';Injected SQL--';
Замечания о примере Trunction выше:
- Да, это очень надуманный пример. Не так много можно сделать, используя только 15 символов для ввода. Конечно, может
DELETE tableName
быть разрушительным, но с меньшей вероятностью добавит секретного пользователя или сменит пароль администратора.
- Этот тип атаки, вероятно, требует знания кода, имен таблиц и т. Д. Менее вероятно, что это сделает случайный незнакомец / script-kiddie, но я работал в месте, которое было атаковано довольно расстроенным бывшим сотрудником, который знал об уязвимости на одной конкретной веб-странице, о которой никто не знал. Это означает, что иногда злоумышленники имеют глубокие знания о системе.
- Конечно, восстановление пароля каждого пользователя, вероятно, будет расследовано, что может предупредить компанию о том, что происходит атака, но может все же предоставить достаточно времени для внедрения второстепенного пользователя или, возможно, получить дополнительную информацию для использования / использования позже.
- Даже если этот сценарий в основном академический (т. Е. Вряд ли произойдет в реальном мире), это все же не невозможно.
Для получения более подробной информации, связанной с SQL-инъекцией (охватывающей различные РСУБД и сценарии), см. Следующее в открытом проекте безопасности веб-приложений (OWASP):
Тестирование на SQL-инъекцию
Ответ по переполнению стека по SQL-инъекции и усечению SQL:
Насколько безопасен T-SQL после замены «escape-символа»?
EXEC usp_actorBirthdays 'Tom', 'Male''; DROP TABLE tblActor'