在C#项目里,安全风险往往藏在看似正常的业务代码中。很多漏洞并不是因为用了多高明的攻击手法,而是开发阶段对边界数据和执行权限的疏忽。下面我们先看一个常见场景的示意图。

一、输入校验与SQL注入风险
SQL注入是C#后端开发里最经典的安全问题之一。不少开发者认为只要使用了Entity Framework等ORM框架就绝对安全,但实际上当业务中需要写原生SQL或使用Dapper的裸查询时,拼接字符串或格式化字符串都会引入漏洞。攻击者可利用单引号闭合、注释符绕过逻辑,直接读取或修改敏感表。
除了传统的字符串拼接,使用string.Format构造SQL命令也属于危险做法。正确方式应始终使用参数化查询,把用户数据当作值而非可执行文本处理。参数化不仅能防注入,还能提升数据库执行计划缓存命中率。
// 错误示例:字符串拼接导致注入
string sql = "SELECT * FROM Users WHERE Name = '" + userName + "'";
using (var cmd = new SqlCommand(sql, conn)) {
// 执行查询
}
// 正确示例:参数化查询
string safeSql = "SELECT * FROM Users WHERE Name = @name";
using (var cmd = new SqlCommand(safeSql, conn)) {
cmd.Parameters.AddWithValue("@name", userName);
// 执行查询
}
校验层的补充手段
在Controller入口处使用特性标注或模型绑定校验,可以拦截明显非法输入。例如利用[RegularExpression]限制手机号格式,用[StringLength]控制长度,避免超长数据冲击下游逻辑。这种前置拦截能减少业务层重复判断。
不过特性校验不能替代数据库层参数化。两者是互补关系:入口校验提升体验与健壮性,参数化保障数据存储安全。遗漏任何一层都可能被绕过,比如攻击者用脚本直接调接口绕过前端与MVC校验。
二、反序列化与远程代码执行
C#中的BinaryFormatter在反序列化不可信数据时会触发严重漏洞。它会在内部调用目标类型的构造与属性设置器,攻击者可构造特殊字节流,让程序加载恶意程序集并执行命令。微软已明确将BinaryFormatter标记为不安全,新项目不应继续使用。
如果必须处理跨系统对象传输,建议采用基于契约的序列化方式,如System.Text.Json或Newtonsoft.Json,并配合类型白名单。对于Json,应禁用TypeNameHandling.Auto之类自动加载类型的选项,防止借类型名实例化危险类。
// 不安全的旧写法
BinaryFormatter bf = new BinaryFormatter();
object obj = bf.Deserialize(networkStream); // 可能执行恶意代码
// 安全的Json做法(System.Text.Json)
var options = new JsonSerializerOptions {
MaxDepth = 32
};
// 仅反序列化为已知DTO,不加载外部类型
var dto = JsonSerializer.Deserialize<UserDto>(jsonString, options);
第三方数据包的信任边界
很多系统会接收合作方推送的二进制或Json消息。开发者容易默认内网流量可信,但内网也可能有嗅探或中间人。因此对所有入站反序列化操作都要假定数据可能伪造,加上签名校验与来源鉴权。
实践中可以用HMAC对消息体签名,接收端用共享密钥验证,再进入反序列化流程。这样即便被截获,攻击者也无法生成合法伪造包,从传输层补上了应用层反序列化的短板。
三、异常信息与敏感数据泄露
未经处理的异常会向调用方返回堆栈跟踪,其中包含数据库连接串、内部路径、第三方密钥等。攻击者利用这些信息可精准定位薄弱点。C#的异常体系虽完善,但默认ASP.NET中间件会输出详细错误页。
生产环境应关闭开发者异常页,统一在全局异常过滤器里记录日志并返回通用错误码。同时加密存储连接串,不要明文写在appsettings.json,可借助用户机密或环境变量注入。
// 全局异常捕获示例
app.UseExceptionHandler(errApp => {
errApp.Run(async context => {
var ex = context.Features.Get<IExceptionHandlerFeature>()?.Error;
// 写日志,不向外暴露细节
Log.Error(ex, "unhandled");
context.Response.StatusCode = 500;
await context.Response.WriteAsync("系统繁忙,请稍后重试");
});
});
日志中的脱敏处理
即便捕获了异常,若直接把实体对象序列化进日志,也可能把用户密码、身份证号落盘。应定义日志专用视图模型,只挑选非敏感字段输出。对必须记录的凭证,采用哈希或掩码处理。
风险管控不是一次性改造,而是贯穿编码、评审、上线的习惯。把上述校验、序列化、异常处理规则沉淀为团队代码模板与扫描规则,才能持续降低C#项目的安全负债。