使用英语阅读

通过


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

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

原因

方法 System.Data.IDbCommand.CommandText 通过使用从字符串参数生成的字符串将属性设置为该方法。

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

规则说明

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

  • 使用存储过程。
  • 使用参数化命令字符串。
  • 在生成命令字符串之前,先验证用户输入的类型和内容。

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

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

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

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

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

如何解决冲突

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

何时禁止显示警告

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

抑制警告

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

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

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

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

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

配置代码以进行分析

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

此外,以下数据流分析相关选项适用于此规则:

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

排除特定符号

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

ini
dotnet_code_quality.CAXXXX.excluded_symbol_names = MyType

备注

CAXXXXXXXX 部分替换为适用规则的 ID。

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

  • 仅符号名称(包括具有相应名称的所有符号,不考虑包含的类型或命名空间)。
  • 完全限定的名称,使用符号的文档 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

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

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

ini
dotnet_code_quality.CAXXXX.excluded_type_names_with_derived_types = MyType

备注

CAXXXXXXXX 部分替换为适用规则的 ID。

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

  • 仅类型名称(包括具有相应名称的所有类型,不考虑包含的类型或命名空间)。
  • 完全限定的名称,使用符号的文档 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该方法使用参数化命令字符串满足规则。

C#
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]'
    }
}

重要

Microsoft 建议使用最安全的可用身份验证流。 如果要连接到 Azure SQL,建议使用 Azure 资源的托管标识这种身份验证方法。

另请参阅