roslyn分析器不能100%捕获sql注入漏洞,因其依赖语义模型且需显式注册所有数据库执行入口;bandit/spotbugs基于字符串匹配的思路不适用c#,因roslyn基于语法树和类型信息,必须精准建模调用链与参数流,否则易漏报或误报。

不能靠 Roslyn 分析器 100% 捕获 SQL 注入漏洞,但能稳定识别最危险的硬编码拼接模式——前提是写对分析逻辑、不依赖字符串匹配。
为什么 Bandit/SpotBugs 的思路在 C# 里不直接适用
Roslyn 分析器不是正则扫描器,它基于语法树(SyntaxTree)和语义模型(SemanticModel),天然能区分 "SELECT * FROM users WHERE id = '" + id + "' 和 "SELECT * FROM users WHERE id = @id" 的结构差异。但这也意味着:你不能像 Bandit 那样只盯住 execute 函数名+字符串拼接就报警;C# 里执行 SQL 的入口太多(SqlCommand.ExecuteNonQuery()、SqlDataAdapter.Fill()、Dapper.Query()、甚至自定义封装的 DbHelper.RunSql()),必须显式注册所有目标调用点。
常见误判场景包括:
- 把
string.Format("SELECT * FROM t WHERE x = {0}", input)当成安全(其实不安全,但 Roslyn 默认不报) - 漏掉
new SqlCommand(query, conn)构造函数这种“SQL 字符串未参数化”但未立即执行的路径 - 对 LINQ to Entities 或 EF Core 的
FromSqlRaw()调用不做特殊处理,导致高危点被跳过
如何编写一个最小可行的 SQL 拼接检测分析器
核心是捕获「用户可控输入参与构建 SQL 字符串字面量」+「该字符串最终流入数据库执行 API」这两段链路。推荐从 SqlCommand 构造和 Execute* 方法切入:
- 在
Initialize()中注册SyntaxKind.InvocationExpression和SyntaxKind.ObjectCreationExpression两种节点 - 对每个调用,用
semanticModel.GetSymbolInfo(node).Symbol判断是否为SqlCommand相关成员(如SqlCommand..ctor(string, SqlConnection)、SqlCommand.ExecuteNonQuery()) - 若命中,向上追溯参数表达式:如果是
BinaryExpressionSyntax(即+拼接),再递归检查左右操作数是否含用户输入变量(通过IdentifierNameSyntax匹配常见命名如userId、input、request.Query等) - 避免直接匹配
"password"或"api_key"——那是硬编码密钥检测的事,和注入无关
示例片段(简化):
private void AnalyzeInvocation(SyntaxNodeAnalysisContext context)
{
var invocation = (InvocationExpressionSyntax)context.Node;
var symbol = context.SemanticModel.GetSymbolInfo(invocation).Symbol;
if (symbol?.ToString() == "int System.Data.SqlClient.SqlCommand.ExecuteNonQuery()" ||
symbol?.ToString().Contains("SqlCommand.Execute") == true)
{
var arg = invocation.ArgumentList.Arguments.FirstOrDefault();
if (arg?.Expression is BinaryExpressionSyntax bin && bin.OperatorToken.Kind() == SyntaxKind.PlusToken)
{
if (IsUserInputReference(bin.Left) || IsUserInputReference(bin.Right))
{
context.ReportDiagnostic(Diagnostic.Create(Rule, arg.Expression.GetLocation()));
}
}
}
}
绕过 false positive 的关键配置点
静态分析器一旦上线,最常被吐槽的是把 ORM 安全调用也标红。这不是代码问题,是分析器没理解上下文:
-
Dapper.Query<t>("SELECT * FROM users WHERE name = @name", new { name = input })</t>必须放行——检测时要识别@name占位符和匿名对象传参模式 -
context.Users.Where(x => x.Name == input).ToList()是安全的,但若分析器只看input出现在字符串附近就报,说明没走语义分析,退化成字符串扫描 - EF Core 的
FromSqlRaw("SELECT * FROM {0}", tableName)是高危的,但FromSqlInterpolated($"SELECT * FROM {tableName}")是安全的——两者 AST 差异极大,必须分别建模 - 所有豁免都应落在具体行,用
// CA2100: SQL query contains user input这类自定义诊断 ID +#pragma warning disable CA2100控制,别用全局NoWarn
真正起效的前提:编译时集成与 CI 卡点
Roslyn 分析器只有在编译流程中生效才有意义。光在 VS 里看到警告没用:
- 确保
.csproj含<packagereference include="Microsoft.CodeAnalysis.NetAnalyzers" version="8.0.0"></packagereference>且<analysismode>AllEnabledByDefault</analysismode> - CI 中跑
dotnet build /warnaserror:CA2100把 SQL 拼接当硬错误拦截,否则开发会习惯性忽略 - 不要指望它发现
var q = "SELECT ..."; cmd.CommandText = q; cmd.ExecuteNonQuery();这种两步拆分——Roslyn 默认不跨语句追踪数据流,需额外引入DataFlowAnalysis,复杂度陡增 - 最易被忽略的一点:
Microsoft.CodeAnalysis.NetAnalyzers默认不包含 SQL 专用规则,CA2100 是 FxCop 时代遗留规则,新版需手动启用或自定义;不配置就等于没开











