如何在sp_executesql中使用临时表和参数?

时间:2016-05-06 16:16:18

标签: c# sql-server

我使用受this SO question启发的存储过程添加了快速而肮脏的整个数据库搜索。它工作正常,但我担心SQL注入。当然我使用SQL参数:

 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)', @term=@SearchStr2;

但是我现在没有得到任何结果。我尝试在查询中包含“insert into”并使用全局临时表但没有骰子。我该怎么做才能阻止SQL注入并获得结果?或者我的方法错了吗?

1 个答案:

答案 0 :(得分: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}");

因此,您有动态列和值,但您需要动态表。一种方法是使用动态方式根据提供的类型获取存储库:

// 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可以为Where生成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);

For Contains解决方案更复杂,如here所示。

我在使用C#建模过滤时看到的优点是:

  1. 没有凌乱的代码
  2. 没有SQL注入(但可能导致LINQ injection
  3. 如果使用存储库和依赖注入,则便于单元测试
  4. 更容易维护
  5. 缺点:

    1. 更多复杂性
    2. 可能出现的性能问题