实体框架核心 - .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&lt;SqlParameter&gt;

关于它是否会处理一个常量,我们可以在同一个类的其他地方查看:

有一条评论here说:

// 常量将作为生成的 TSQL 的一部分发送到存储区, 不作为参数

我们已经确定对于 INSERT 或 UPDATE 或 DELETE,它将使用参数。因此,这是用于查询。正如您从日志中看到的那样,您的 List&lt;string&gt; 在传入时会转换为常量。所以,我们只想知道这些字符串会发生什么。

然后就发生了类型的大切换,相关的部分是

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

为啥实体框架核心加载实体的关系而不添加包含

为啥不是原生内置的类似框架的功能?

具有 LINQ 的实体框架在 WHERE 子句中使用 CONTAINS 非常慢且具有大整数列表

为啥我不能使用“描述”作为核心数据实体的属性名称?

为啥我不能更新核心数据实体属性的布尔值?