實體框架核心 - .Contains() - 為什麼要轉義而不是參數化?

asp.net-core c# entity-framework-core linq sql

在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 '' 。此行為似乎與“安全保證:LINQ查詢使用參數化和轉義”下的文檔中描述的內容一致, 該文檔聲明查詢將被參數化或轉義。

但是,我想知道在查詢參數化以及何時轉義值時如何確定?選擇其中一個的原因是什麼?而且我認為逃避從未100%安全,現在這有什麼不同嗎?

一般承認的答案

好問題。我相信答案是,如果元素可以轉換為常量並且它不是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>

對於它是否會處理常量,我們可以在同一個類的其他地方查找:

有評論在這裡 ,上面寫著:

//常量將作為生成的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%確定沒有什麼好笑時,我只會選擇轉義:也就是說,當價值不可能直接來自用戶時 。因此,您可能希望對您的實施進行更多測試,並根據訪問限制,風險承受能力,數據敏感性和用戶角色等其他因素決定是否需要進一步確保實施。



Related

許可下: CC-BY-SA with attribution
不隸屬於 Stack Overflow
這個KB合法嗎? 是的,了解原因
許可下: CC-BY-SA with attribution
不隸屬於 Stack Overflow
這個KB合法嗎? 是的,了解原因