| 属性 | 值 |
|---|---|
| 规则 ID | CA2100 |
| 标题 | 检查 SQL 查询是否存在安全漏洞 |
| 类别 | 安全性 |
| 修复是中断修复还是非中断修复 | 非中断 |
| 在 .NET 9 中默认启用 | 否 |
原因
方法 System.Data.IDbCommand.CommandText 通过使用从字符串参数生成的字符串将属性设置为该方法。
默认情况下,此规则会分析整个代码库,但这是可配置的。
规则说明
此规则假定在编译时无法确定其值的任何字符串可能包含用户输入。 用户输入生成的 SQL 命令字符串容易受到 SQL 注入攻击。 在 SQL 注入攻击中,恶意用户会提供改变查询设计的输入,企图破坏基础数据库或对该数据库进行未经授权的访问。 典型方法包括注入一个单引号或撇号(这是 SQL 文本字符串分隔符)、两个短划线(表示 SQL 注释)和一个分号(指示后跟一个新命令)。 如果用户输入必须是查询的一部分,请按照以下方法之一(按有效性排列)来降低遭受攻击的风险。
- 使用存储过程。
- 使用参数化命令字符串。
- 在生成命令字符串之前,先验证用户输入的类型和内容。
以下 .NET 类型实现 CommandText 该属性或提供使用字符串参数设置属性的构造函数:
- System.Data.Odbc.OdbcCommand 和 System.Data.Odbc.OdbcDataAdapter
- System.Data.OleDb.OleDbCommand 和 System.Data.OleDb.OleDbDataAdapter
- System.Data.OracleClient.OracleCommand 和 System.Data.OracleClient.OracleDataAdapter
- System.Data.SqlClient.SqlCommand 和 System.Data.SqlClient.SqlDataAdapter
在某些情况下,此规则可能不会在编译时确定字符串的值,即使你可以这样做。 在这些情况下,当使用这些字符串作为 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
有关详细信息,请参阅如何禁止显示代码分析警告。
配置代码以进行分析
使用下面的选项来配置代码库的哪些部分要运行此规则。
此外,以下数据流分析相关选项适用于此规则:
- interprocedural_analysis_kind
- max_interprocedural_lambda_or_local_function_call_chain
- max_interprocedural_method_call_chain
- points_to_analysis_kind
- copy_analysis
- sufficient_IterationCount_for_weak_KDF_algorithm
可以仅针对此规则、针对所应用于的所有规则或针对此类别(安全)中的所有规则配置这些选项。 有关详细信息,请参阅代码质量规则配置选项。
排除特定符号
可以通过设置 excluded_symbol_names 选项从分析中排除特定符号,例如类型和方法。 例如,若要指定规则不应针对名为 MyType 的类型中的任何代码运行,请将以下键值对添加到项目中的 .editorconfig 文件:
dotnet_code_quality.CAXXXX.excluded_symbol_names = MyType
注意
将 XXXX 的 CAXXXX 部分替换为适用规则的 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 |
匹配名为 MyType1 或 MyType2 的所有符号。 |
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) |
匹配带有各自的完全限定签名的特定方法 MyMethod1 和 MyMethod2。 |
排除特定类型及其派生类型
可以通过设置 excluded_type_names_with_derived_types 选项从分析中排除特定类型及其派生类型。 例如,若要指定规则不应针对名为 MyType 的类型及其派生类型中的任何代码运行,请将以下键值对添加到项目中的 .editorconfig 文件:
dotnet_code_quality.CAXXXX.excluded_type_names_with_derived_types = MyType
注意
将 XXXX 的 CAXXXX 部分替换为适用规则的 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 |
匹配名为 MyType1 或 MyType2 的所有类型及其所有派生类型。 |
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 |
匹配带有各自的完全限定名称的特定类型 MyType1 和 MyType2 及其所有派生类型。 |
示例
下面的示例演示了违反规则的方法 UnsafeQuery。 它还显示一个方法, SaferQuery该方法使用参数化命令字符串满足规则。
Imports System.Data
Imports System.Data.OleDb
Imports System.Runtime.Versioning
Namespace ca2100
Public Class SqlQueries
<SupportedOSPlatform("windows")>
Function UnsafeQuery(connection As String,
name As String, password As String) As Object
Dim someConnection As New OleDbConnection(connection)
Dim someCommand As New OleDbCommand With {
.Connection = someConnection,
.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
<SupportedOSPlatform("windows")>
Function SaferQuery(connection As String,
name As String, password As String) As Object
Dim someConnection As New OleDbConnection(connection)
Dim someCommand As New OleDbCommand With {
.Connection = someConnection
}
someCommand.Parameters.AddWithValue(
"@username", OleDbType.Char).Value = name
someCommand.Parameters.AddWithValue(
"@password", OleDbType.Char).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
<SupportedOSPlatform("windows")>
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
[SupportedOSPlatform("Windows")]
public class OleDbQueries
{
public object UnsafeQuery(
string connection, string name, string password)
{
using OleDbConnection someConnection = new(connection);
using OleDbCommand someCommand = new();
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)
{
using OleDbConnection someConnection = new(connection);
using OleDbCommand someCommand = new();
someCommand.Connection = someConnection;
someCommand.Parameters.Add(
"@username", OleDbType.Char).Value = name;
someCommand.Parameters.Add(
"@password", OleDbType.Char).Value = password;
someCommand.CommandText = "SELECT AccountNumber FROM Users " +
"WHERE Username=@username AND Password=@password";
someConnection.Open();
object accountNumber = someCommand.ExecuteScalar();
someConnection.Close();
return accountNumber;
}
}
[SupportedOSPlatform("Windows")]
class MaliciousCode
{
static void Main2100(string[] args)
{
OleDbQueries queries = new();
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 资源的托管标识这种身份验证方法。