Sanitize имя таблицы/столбца в динамическом SQL в .NET? (Предотвратите атаки SQL-инъекций)
Я генерирую некоторый Dynamic SQL и хочу, чтобы мой код был безопасным из SQL injection.
Для аргумента здесь приведен минимальный пример того, как он генерируется:
var sql = string.Format("INSERT INTO {0} ({1}) VALUES (@value)",
tableName, columnName);
В приведенном выше тексте tableName
, columnName
и все, что связано с @value
, происходят из ненадежного источника. Поскольку используются теги, используемые @value
безопасны от атак SQL-инъекций и могут быть проигнорированы. (Команда выполняется через SqlCommand.)
Однако tableName
и columnName
не могут быть привязаны как заполнители и поэтому уязвимы для инъекционных атак. Поскольку это "действительно динамичный" сценарий, нет белого списка tableName
или columnName
.
Возникает вопрос:
Есть ли стандартный, встроенный способ проверки и/или дезинфекции tableName
и columnName
? (SqlConnection, или вспомогательный класс и т.д.). Если нет, то какой хороший способ выполнить эту задачу без использования сторонней библиотеки?
Примечания:
- Все идентификаторы SQL, включая схему, должны приниматься: например.
[schema].[My Table].column
так же безопасен, как table1
.
- Можно либо дезинфицировать идентификаторы, либо обнаружить недопустимый идентификатор. (Нет необходимости гарантировать, что таблица/столбец действительно действительна в контексте, полученный SQL может быть недействительным, но должен быть "безопасным".)
Update:
Просто нашел это и подумал, что это несколько интересно: в .NET4 (EF4?) есть функция SqlFunctions.QuoteName. Ладно, мне это действительно не помогает...
Ответы
Ответ 1
Поскольку вы используете SqlConnection, предполагается, что это база данных SQL Server.
Учитывая это предположение, вы можете проверить имена таблиц и полей, используя регулярное выражение, которое следует за правилами идентификатора SQL Server, как определено в MSDN. Хотя я полный и полный новичок в регулярных выражениях, я нашел этот, который должен приблизиться:
[\p{L}{\p{Nd}}$#_][\p{L}{\p{Nd}}@$#_]*
Однако регулярное выражение не будет обращаться к ключевым словам SQL Server и не гарантирует, что таблица и/или столбец фактически существуют (хотя вы указали, что это не большая проблема).
Если это было мое приложение, я бы сначала удостоверился, что конечный пользователь не пытался выполнить инъекцию, отклонив любой запрос, содержащий полуколоны (;).
Затем я проверил бы существование таблицы, удалив допустимые разделители имен ( ",", [,]), разделив имя таблицы на период, чтобы увидеть, была ли указана схема, и выполнение запроса в отношении INFORMATION_SCHEMA.TABLES для определения существования таблицы.
Например:
SELECT 1
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_NAME = 'tablename'
AND TABLE_SCHEMA = 'tableschema'
Если вы создаете этот запрос с использованием параметров, вам следует дополнительно защитить себя от инъекций.
Наконец, я бы подтвердил существование каждого имени столбца, выполнив аналогичный набор шагов, только используя INFORMATION_SCHEMA.COLUMNS, чтобы определить правильность столбца (столбцов), как только таблица будет определена как действительная.
Я бы, вероятно, извлек список допустимых столбцов для этой таблицы из SQL Server, а затем проверил, что каждый столбец запроса находится в списке моего кода. Таким образом, вы можете точно указать, какие столбцы были в ошибке, и предоставить эту обратную связь пользователю.
Ответ 2
Я не уверен, что вы все еще смотрите на это, но класс DbCommandBuilder
предоставляет для этого метод QuoteIdentifier
. Основными преимуществами этого являются то, что он независим от базы данных и не связан с беспорядком RegEx.
Начиная с .NET 4.5, у вас есть все необходимое для дезинфекции имен таблиц и столбцов, просто используя свой объект DbConnection:
DbConnection connection = GetMyConnection(); // Could be SqlConnection
DbProviderFactory factory = DbProviderFactories.GetFactory(connection);
// Sanitize the table name
DbCommandBuilder commandBuilder = factory.CreateCommandBuilder();
string tableName = "This Table Name Is Long And Bad";
string sanitizedTableName = commandBuilder.QuoteIdentifier(tableName);
IDbCommand command = connection.CreateCommand();
command.CommandText = "SELECT * FROM " + sanitizedTableName;
// Becomes 'SELECT * FROM [This Table Name Is Long And Bad]' in MS-SQL,
// 'SELECT * FROM "This Table Name Is Long And Bad"' in Oracle, etc.
(Pre-4.5, вам понадобится другой способ получить ваш DbProviderFactory - возможно, от имени поставщика данных в конфигурации вашего приложения или где-то с жестким кодом).
Ответ 3
Для SQL Server довольно просто очистить идентификатор:
// To make a string safe to use as an SQL identifier :
// 1. Escape single closing bracket with double closing bracket
// 2. Wrap in square brackets
string.Format("[{0}]", identifier.Replace("]", "]]"));
После того, как он заключен в скобки и экранирован, единственное, что не будет работать как идентификатор, это пустая/нулевая строка.