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("]", "]]"));

После того, как он заключен в скобки и экранирован, единственное, что не будет работать как идентификатор, это пустая/нулевая строка.