2016-05-06 3 views
1

Я добавил быстрый и грязный поиск всей базы данных, используя хранимую процедуру, основанную на this SO question. Он отлично работает, но я беспокоюсь о SQL-инъекции. Конечно, я использую параметр SQL:Как использовать временную таблицу и параметры с помощью sp_executesql?

string query = GetUserInput(); 
using (SqlConnection con = new SqlConnection(conString)) 
{ 
     using (SqlCommand cmd = new SqlCommand("SearchAllTables", con)) 
     { 
       cmd.CommandType = CommandType.StoredProcedure; 

       cmd.Parameters.Add("@SearchStr", SqlDbType.VarChar).Value = query; 

       con.Open(); 
       SqlDataReader reader = cmd.ExecuteReader(); 

Однако, поскольку SP строит запрос и выполнить его, инъекции SQL по-прежнему возможно. Соответствующая часть:

CREATE PROC [dbo].[SearchAllTables] 
(
@SearchStr nvarchar(100) 
) 
AS 
BEGIN 

CREATE TABLE #Results (TableName nvarchar(370), ColumnName nvarchar(370), ColumnValue nvarchar(3630), TableId int) 
--snip boring part 

SET @SearchStr2 = QUOTENAME('%' + @SearchStr + '%','''') 
INSERT INTO #Results 
EXEC 
(
    'SELECT ''' + @TableName +''', ''' + @TableName + '.' + @ColumnName + ''', LEFT(CONVERT(varchar(max), ' + @ColumnName + '), 3630), [id] 
    FROM ' + @TableName + ' (NOLOCK) ' + 
    ' WHERE CONVERT(varchar(max), ' + @ColumnName + ') LIKE ' + @SearchStr2 --This is vulnerable 
) 

--snip all the END 

SELECT TableName, ColumnName, ColumnValue, TableId FROM #Results 

Я хочу изменить его, чтобы использовать sp_executesql с параметрами для предотвращения инъекции SQL. Я сменил его на:

declare @query nvarchar(1000) 
set @query= 'SELECT ''' + @TableName +''', ''' + @TableName + '.' + @ColumnName + ''', LEFT(CONVERT(varchar(max), ' 
      + @ColumnName + '), 3630), [id] FROM ' + @TableName + ' (NOLOCK) ' + 
      ' WHERE CONVERT(varchar(max), ' + @ColumnName + ') LIKE @term' 
INSERT INTO #Results    
EXEC sp_executesql @query, N'@term nvarchar(100)', @[email protected]; 

Однако я не получаю никаких результатов. Я попытался включить «вставить в» в запрос и использовать глобальные временные таблицы, но не кубики. Что я могу сделать, чтобы предотвратить SQL-инъекцию и вернуть результаты? Или я ошибаюсь в своем подходе?

+1

Почему вы позволяете пользователям осуществлять поиск через любую таблицу в системе? Почему пользователи даже знают имена таблиц? Вы можете обернуть свое имя таблицы в QUOTENAME, чтобы предотвратить внедрение sql, но, честно говоря, вся концепция кажется мне немного от меня. И если вы собираетесь использовать NOLOCK, вам нужно включить ключевое слово WITH. Опущение это устарело. Конечно, если вы хотите получить точные результаты в своем поиске, вы должны использовать этот намек, поскольку он иногда может пропустить данные. –

+0

Пользователи не предоставляют имена таблиц и столбцов, эти переменные заполняются ранее в процедуре. Термин поиска пользователей уже завернут QUOTENAME. – 0xFF

+1

Способ построения запроса выглядит так, как будто это не сработает, потому что вы завершаете значение поиска с помощью QUOTENAME. Это используется для имен объектов, а не значений. То, как вы его закодировали в нижней части выглядит правильно, просто избавиться от той части, где вы используете quotename вокруг предоставленного условия поиска. Единственный способ отладки этого типа динамического sql - использовать команды print/select. Если вы просмотрите свой @query, вы увидите ошибку, введенную с использованием имени. –

ответ

0

Некоторые вещи, например table names, и имена столбцов не могут быть отправлены в качестве параметров в динамическом запросе, поэтому их необходимо добавить. Конечно, это довольно грязный (и склонный к ошибкам).

Поскольку вы используете C#, я советую взглянуть на Dynamic Linq library. Он предоставляет некоторые расширения, которые позволяют использовать строковые запросы в запросах LINQ. Другие способы генерации динамических запросов показаны here.

Хорошо, возвращаясь к вашей первоначальной проблеме.

1) Динамический Linq позволит вам легко писать запросы, такие как:

// this requires C# 6.0 to use interpolated strings. String.Format can be used instead 
someRepository.GetAll.Where($"{col1} = {value1} And {col2} = {value2}"); 

Таким образом, у вас есть динамические столбцы и значение, но вы должны динамические таблицы. Один из способов иметь динамический способ получения Repository, основанную на типе при условии:

// this contains repositories for all types mapped to used tables 
public class UnitOfWork : IUnitOfWork 
{ 
    public IRepository<Table1> Table1Repository { get; private set; } 
    public IRepository<Table2> Table2Repository { get; private set; } 
    // other come here 

    // all these are injected 
    public UnitOfWork(IDbContext context, IRepository<Table1> table1Repository, IRepository<Table2> table2Repository 
    { 
     Table1Repository = table1Repository; 
     Table2Repository = table2Repository; 
     // other initializations 
    } 

    // typed version 
    public IRepository<T> GetRepository<T>() 
     where T: class 
    { 
     Type thisType = this.GetType(); 
     foreach (var prop in thisType.GetProperties()) 
     { 
      var propType = prop.PropertyType; 

      if (!typeof(IRepository).IsAssignableFrom(propType)) 
       continue; 

      var repoType = propType.GenericTypeArguments[0]; 
      if (repoType == typeof(T)) 
       return (IRepository<T>) prop.GetValue(this); 
     } 

     throw new ArgumentException(String.Format("No repository of type {0} found", typeof(T).FullName)); 
    } 

    // dynamic type version (not tested, just written here) 
    public IRepository GetRepository(Type type) 
     where T: class 
    { 
     Type thisType = this.GetType(); 
     foreach (var prop in thisType.GetProperties()) 
     { 
      var propType = prop.PropertyType; 

      if (!typeof(IRepository).IsAssignableFrom(propType)) 
       continue; 

      var repoType = propType.GenericTypeArguments[0]; 
      if (repoType == type) 
       return (IRepository) prop.GetValue(this); 
     } 

     throw new ArgumentException(String.Format("No repository of type {0} found", typeof(T).FullName)); 
    } 
} 

Чтобы динамически получить хранилище, вам необходимо иметь отображение между именем таблицы (или ваш идентификатор таблицы в значения фильтра и тип). Что-то вроде:

var filterTableMap = new Dictionary<String, Type>() 
    { 
     { "Table1", typeof(Table1Repository) }, 
     { "Table2", typeof(Table2Repository) }, 
     // and so on 
    }; 

Ваше состояние будет выглядеть следующим образом:

var query = filterTableMap["Table1"].GetAll.Where($"{col1} = {value1}"); 

Однако, это довольно сложно, если вы хотите применить несколько условий одновременно.

2) Интересный подход заключается в использовании reflection:

// this is a slightly changed and not tested version from the source 
public static IEnumerable<T> WhereQuery(this IEnumerable<T> source, string columnName, string propertyValue) 
{ 
    return source.Where(m => { 
     return m.GetType().GetProperty(columnName).GetValue(m, null).ToString().Contains(propertyValue); 
    }); 
} 

Это должно позволить цепи, где условия, как это:

var query = filterTableMap["Table1"].GetAll.WhereQuery("col1", value1); 
if (value2 != null) 
    query = query.WhereQuery("col2", value2); 

Однако, я не думаю, что Linq2SQL может генерировать SQL для этого Где, поэтому источник должен быть списком объектов. Это серьезная проблема, если данные не фильтруются заранее, чтобы уменьшить длину.

3) Деревья выражений кажутся наилучшим выбором, как указано here.Что-то вроде:

var param = Expression.Parameter(typeof(String)); 
var condition = 
    Expression.Lambda<Func<String, bool>>(
     Expression.Equal(
      Expression.Property(param, "Col1"), 
      Expression.Constant("Value1", typeof(String)) 
     ), 
     param 
    ).Compile(); 
// for LINQ to SQL/Entities skip Compile() call 

var query = source.Where(condition); 

Для Содержит раствор более запутанным, как показано here.

преимущества я вижу в моделировании фильтрации в C# является:

  1. нет грязного кода
  2. не инъекции SQL (но не могут привести к LINQ injection)
  3. , если используются хранилища и инъекция зависимостей, модуль тестирование способствует
  4. проще обслуживание

Неудобство:

  1. больше сложность
  2. возможных проблем с производительностью
Смежные вопросы