我使用受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注入并获得结果?或者我的方法错了吗?
答案 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#建模过滤时看到的优点是:
缺点: