实体框架核心 - .Contains() - 为啥转义而不是参数化?
Posted
技术标签:
【中文标题】实体框架核心 - .Contains() - 为啥转义而不是参数化?【英文标题】:Entity Framework Core - .Contains() - Why escaping instead of parametrization?实体框架核心 - .Contains() - 为什么转义而不是参数化? 【发布时间】:2016-05-30 18:59:21 【问题描述】:在 Web 应用程序中,我从 ViewModel 以 List<string>
的形式获取用户输入,并使用此信息通过以下代码选择用户的 Id:
var selectedUsersIds = Context.Users
.Where(user => SelectedUsers.Contains(user.Email))
.Select(user => user.Id)
.ToList();
SelectedUsers
是字符串列表(用户电子邮件)。
现在,在查看应用程序日志时,我遇到了以下日志条目:
info: Microsoft.Data.Entity.Storage.Internal.RelationalCommandBuilderFactory[1]
Executed DbCommand (0ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
SELECT [user].[Id]
FROM [AspNetUsers] AS [user]
WHERE [user].[Email] IN ('first@user.com', 'second@user.com')
所以接下来的任务是使用一个rest客户端并使用some@user'--
作为表单参数,我得到了这个结果:
info: Microsoft.Data.Entity.Storage.Internal.RelationalCommandBuilderFactory[1]
Executed DbCommand (1ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
SELECT [user].[Id]
FROM [AspNetUsers] AS [user]
WHERE [user].[Email] IN ('some@user''--')
在这里,single '
被转义为 double ''
。这种行为似乎与
what is described in the docs under "Security Guarantee: LINQ queries use parameterization and escaping",表示查询将被参数化或转义。
但是,我想知道,查询何时参数化以及何时转义值是如何决定的?有什么理由选择其中之一? 而且我认为逃跑从来都不是 100% 安全的,现在情况不同了吗?
【问题讨论】:
【参考方案1】:很好的问题。我相信答案是如果元素可以转换为常量并且它不是 UPDATE 或 INSERT,它将被转义。原因如下:
查看SqlGenerator Souce 会显示一个名为GenerateSql
的方法,如下所示:
internal static string GenerateSql(DbCommandTree tree, SqlVersion sqlVersion, out List<SqlParameter> parameters, out CommandType commandType, out HashSet<string> paramsToForceNonUnicode)
SqlGenerator sqlGen;
commandType = CommandType.Text;
parameters = null;
paramsToForceNonUnicode = null;
switch (tree.CommandTreeKind)
case DbCommandTreeKind.Query:
sqlGen = new SqlGenerator(sqlVersion);
return sqlGen.GenerateSql((DbQueryCommandTree)tree, out paramsToForceNonUnicode);
case DbCommandTreeKind.Insert:
return DmlSqlGenerator.GenerateInsertSql((DbInsertCommandTree)tree, sqlVersion, out parameters);
case DbCommandTreeKind.Delete:
return DmlSqlGenerator.GenerateDeleteSql((DbDeleteCommandTree)tree, sqlVersion, out parameters);
case DbCommandTreeKind.Update:
return DmlSqlGenerator.GenerateUpdateSql((DbUpdateCommandTree)tree, sqlVersion, out parameters);
case DbCommandTreeKind.Function:
sqlGen = new SqlGenerator(sqlVersion);
return GenerateFunctionSql((DbFunctionCommandTree)tree, out commandType);
default:
//We have covered all command tree kinds
Debug.Assert(false, "Unknown command tree kind");
parameters = null;
return null;
如您所见,它是否是一个查询,它返回生成的不带参数的 SQL。对于其他类型,它将填充List<SqlParameter>
。
关于它是否会处理一个常量,我们可以在同一个类的其他地方查看:
有一条评论here说:
// 常量将作为生成的 TSQL 的一部分发送到存储区, 不作为参数
我们已经确定对于 INSERT 或 UPDATE 或 DELETE,它将使用参数。因此,这是用于查询。正如您从日志中看到的那样,您的 List<string>
在传入时会转换为常量。所以,我们只想知道这些字符串会发生什么。
然后就发生了类型的大切换,相关的部分是
case PrimitiveTypeKind.String:
bool isUnicode;
if (!TypeHelpers.TryGetIsUnicode(e.ResultType, out isUnicode))
// If the unicode facet is not specified, if needed force non-unicode, otherwise default to unicode.
isUnicode = !_forceNonUnicode;
result.Append(EscapeSingleQuote(e.Value as string, isUnicode));
break;
哪个简单
private static string EscapeSingleQuote(string s, bool isUnicode)
return (isUnicode ? "N'" : "'") + s.Replace("'", "''") + "'";
关于你的其他问题
有什么理由选择其中之一?我以为 逃跑从来都不是 100% 安全的,现在情况不同了吗?
尽管我不是安全专家,但我倾向于同意“转义不能 100% 安全”,并且有人可能会指出一些有限范围的 100% 安全方法。只有当我 100% 确定没有什么有趣的事情发生时,我才会选择转义:也就是说,当值不可能直接来自用户时。因此,您可能希望对您的实施进行更多测试,并根据访问限制、风险承受能力、数据敏感性和用户角色等其他因素决定是否需要进一步保护它。
【讨论】:
以上是关于实体框架核心 - .Contains() - 为啥转义而不是参数化?的主要内容,如果未能解决你的问题,请参考以下文章
实体框架EF.Functions.Like vs string.Contains