CA2100:检查 SQL 查询是否存在安全漏洞

属性
规则 ID CA2100
标题 检查 SQL 查询是否存在安全漏洞
类别 安全性
修复是中断修复还是非中断修复 非中断
在 .NET 8 中默认启用

原因

一种方法使用按该方法的字符串参数生成的字符串设置 System.Data.IDbCommand.CommandText 属性。

默认情况下,此规则会分析整个代码库,但这是可配置的。

规则说明

此规则假定无法在编译时确定值的任何字符串都可能包含用户输入。 基于用户输入生成的 SQL 命令字符串易于受到 SQL 注入式攻击。 在 SQL 注入攻击中,恶意用户会提供改变查询设计的输入,企图破坏基础数据库或对该数据库进行未经授权的访问。 典型方法包括注入一个单引号或撇号(这是 SQL 文本字符串分隔符)、两个短划线(表示 SQL 注释)和一个分号(指示后跟一个新命令)。 如果用户输入必须是查询的一部分,请按照以下方法之一(按有效性排列)来降低遭受攻击的风险。

  • 使用存储过程。

  • 使用参数化命令字符串。

  • 在生成命令字符串之前,先验证用户输入的类型和内容。

下面的 .NET 类型实现 CommandText 属性,或提供使用字符串参数设置属性的构造函数。

在某些情况下,此规则可能不会在编译时确定字符串的值,即使你可以这样做。 在这些情况下,当使用这些字符串作为 SQL 命令时,此规则将产生误报。 以下是这种字符串的一个示例。

int x = 10;
string query = "SELECT TOP " + x.ToString() + " FROM Table";

当隐式使用 ToString() 时,会出现相同的情况。

int x = 10;
string query = String.Format("SELECT TOP {0} FROM Table", x);

如何解决冲突

若要解决此规则的冲突,请使用参数化查询。

何时禁止显示警告

如果命令文本不包含任何用户输入,可禁止显示此规则的警告。

抑制警告

如果只想抑制单个冲突,请将预处理器指令添加到源文件以禁用该规则,然后重新启用该规则。

#pragma warning disable CA2100
// The code that's violating the rule is on this line.
#pragma warning restore CA2100

若要对文件、文件夹或项目禁用该规则,请在配置文件中将其严重性设置为 none

[*.{cs,vb}]
dotnet_diagnostic.CA2100.severity = none

有关详细信息,请参阅如何禁止显示代码分析警告

配置代码以进行分析

使用下面的选项来配置代码库的哪些部分要运行此规则。

可以仅为此规则、为适用的所有规则或为适用的此类别(安全性)中的所有规则配置这些选项。 有关详细信息,请参阅代码质量规则配置选项

排除特定符号

可以从分析中排除特定符号,如类型和方法。 例如,若要指定规则不应针对名为 MyType 的类型中的任何代码运行,请将以下键值对添加到项目中的 .editorconfig 文件:

dotnet_code_quality.CAXXXX.excluded_symbol_names = MyType

选项值中允许的符号名称格式(用 | 分隔):

  • 仅符号名称(包括具有相应名称的所有符号,不考虑包含的类型或命名空间)。
  • 完全限定的名称,使用符号的文档 ID 格式。 每个符号名称都需要带有一个符号类型前缀,例如表示方法的 M:、表示类型的 T:,以及表示命名空间的 N:
  • .ctor 表示构造函数,.cctor 表示静态构造函数。

示例:

选项值 总结
dotnet_code_quality.CAXXXX.excluded_symbol_names = MyType 匹配名为 MyType 的所有符号。
dotnet_code_quality.CAXXXX.excluded_symbol_names = MyType1|MyType2 匹配名为 MyType1MyType2 的所有符号。
dotnet_code_quality.CAXXXX.excluded_symbol_names = M:NS.MyType.MyMethod(ParamType) 匹配带有指定的完全限定签名的特定方法 MyMethod
dotnet_code_quality.CAXXXX.excluded_symbol_names = M:NS1.MyType1.MyMethod1(ParamType)|M:NS2.MyType2.MyMethod2(ParamType) 匹配带有各自的完全限定签名的特定方法 MyMethod1MyMethod2

排除特定类型及其派生类型

可以从分析中排除特定类型及其派生类型。 例如,若要指定规则不应针对名为 MyType 的类型及其派生类型中的任何代码运行,请将以下键值对添加到项目中的 .editorconfig 文件:

dotnet_code_quality.CAXXXX.excluded_type_names_with_derived_types = MyType

选项值中允许的符号名称格式(用 | 分隔):

  • 仅类型名称(包括具有相应名称的所有类型,不考虑包含的类型或命名空间)。
  • 完全限定的名称,使用符号的文档 ID 格式,前缀为 T:(可选)。

示例:

选项值 总结
dotnet_code_quality.CAXXXX.excluded_type_names_with_derived_types = MyType 匹配名为 MyType 的所有类型及其所有派生类型。
dotnet_code_quality.CAXXXX.excluded_type_names_with_derived_types = MyType1|MyType2 匹配名为 MyType1MyType2 的所有类型及其所有派生类型。
dotnet_code_quality.CAXXXX.excluded_type_names_with_derived_types = M:NS.MyType 匹配带有给定的完全限定名称的特定类型 MyType 及其所有派生类型。
dotnet_code_quality.CAXXXX.excluded_type_names_with_derived_types = M:NS1.MyType1|M:NS2.MyType2 匹配带有各自的完全限定名称的特定类型 MyType1MyType2 及其所有派生类型。

示例

下面的示例演示了违反规则的 UnsafeQuery 方法以及符合规则的 SaferQuery 方法(使用参数化命令字符串)。

Imports System
Imports System.Data
Imports System.Data.SqlClient

Namespace ca2100

    Public Class SqlQueries

        Function UnsafeQuery(connection As String,
         name As String, password As String) As Object

            Dim someConnection As New SqlConnection(connection)
            Dim someCommand As New SqlCommand()
            someCommand.Connection = someConnection

            someCommand.CommandText = "SELECT AccountNumber FROM Users " &
            "WHERE Username='" & name & "' AND Password='" & password & "'"

            someConnection.Open()
            Dim accountNumber As Object = someCommand.ExecuteScalar()
            someConnection.Close()
            Return accountNumber

        End Function

        Function SaferQuery(connection As String,
         name As String, password As String) As Object

            Dim someConnection As New SqlConnection(connection)
            Dim someCommand As New SqlCommand()
            someCommand.Connection = someConnection

            someCommand.Parameters.Add(
            "@username", SqlDbType.NChar).Value = name
            someCommand.Parameters.Add(
            "@password", SqlDbType.NChar).Value = password
            someCommand.CommandText = "SELECT AccountNumber FROM Users " &
            "WHERE Username=@username AND Password=@password"

            someConnection.Open()
            Dim accountNumber As Object = someCommand.ExecuteScalar()
            someConnection.Close()
            Return accountNumber

        End Function

    End Class

    Class MaliciousCode

        Shared Sub Main2100(args As String())

            Dim queries As New SqlQueries()
            queries.UnsafeQuery(args(0), "' OR 1=1 --", "[PLACEHOLDER]")
            ' Resultant query (which is always true):
            ' SELECT AccountNumber FROM Users WHERE Username='' OR 1=1

            queries.SaferQuery(args(0), "' OR 1=1 --", "[PLACEHOLDER]")
            ' Resultant query (notice the additional single quote character):
            ' SELECT AccountNumber FROM Users WHERE Username=''' OR 1=1 --'
            '                                   AND Password='[PLACEHOLDER]'
        End Sub

    End Class

End Namespace
public class SqlQueries
{
    public object UnsafeQuery(
       string connection, string name, string password)
    {
        SqlConnection someConnection = new SqlConnection(connection);
        SqlCommand someCommand = new SqlCommand();
        someCommand.Connection = someConnection;

        someCommand.CommandText = "SELECT AccountNumber FROM Users " +
           "WHERE Username='" + name +
           "' AND Password='" + password + "'";

        someConnection.Open();
        object accountNumber = someCommand.ExecuteScalar();
        someConnection.Close();
        return accountNumber;
    }

    public object SaferQuery(
       string connection, string name, string password)
    {
        SqlConnection someConnection = new SqlConnection(connection);
        SqlCommand someCommand = new SqlCommand();
        someCommand.Connection = someConnection;

        someCommand.Parameters.Add(
           "@username", SqlDbType.NChar).Value = name;
        someCommand.Parameters.Add(
           "@password", SqlDbType.NChar).Value = password;
        someCommand.CommandText = "SELECT AccountNumber FROM Users " +
           "WHERE Username=@username AND Password=@password";

        someConnection.Open();
        object accountNumber = someCommand.ExecuteScalar();
        someConnection.Close();
        return accountNumber;
    }
}

class MaliciousCode
{
    static void Main2100(string[] args)
    {
        SqlQueries queries = new SqlQueries();
        queries.UnsafeQuery(args[0], "' OR 1=1 --", "[PLACEHOLDER]");
        // Resultant query (which is always true):
        // SELECT AccountNumber FROM Users WHERE Username='' OR 1=1

        queries.SaferQuery(args[0], "' OR 1=1 --", "[PLACEHOLDER]");
        // Resultant query (notice the additional single quote character):
        // SELECT AccountNumber FROM Users WHERE Username=''' OR 1=1 --'
        //                                   AND Password='[PLACEHOLDER]'
    }
}

另请参阅