Sql注入攻击是Web应用和桌面应用中常见的安全风险,当程序直接将用户输入拼接到SQL语句中执行时,攻击者可以输入特殊的字符串改变SQL原本的逻辑,比如绕过登录验证、获取敏感数据甚至删除表数据。C#作为常用的后端开发语言,处理Sql注入的核心方案是使用参数化查询,避免将用户输入直接嵌入SQL字符串。

Sql注入的产生原因
很多开发者在初期编写数据库操作代码时,会采用字符串拼接的方式构造SQL语句,比如下面的示例代码:
// 存在Sql注入风险的代码示例 string username = "admin"; string password = "123456"; // 直接拼接用户输入到SQL中 string sql = "SELECT * FROM Users WHERE UserName = '" + username + "' AND Password = '" + password + "'"; // 执行SQL查询
如果攻击者在用户名输入框中输入admin' OR '1'='1,拼接后的SQL会变成:
SELECT * FROM Users WHERE UserName = 'admin' OR '1'='1' AND Password = '123456'
此时OR '1'='1'条件恒成立,即使密码错误也能查询到用户数据,造成登录绕过的安全问题。更危险的输入甚至可以执行删表、拖库等恶意操作。
参数化查询的原理
参数化查询的核心是将SQL语句的结构和参数值分开传递,数据库引擎会先编译SQL语句的结构,再将参数值作为独立的数据部分处理,不会把参数值解析为SQL指令的一部分。这样即使用户输入包含SQL关键字,也只会被当作普通的字符串值处理,无法改变SQL的逻辑。
C#中实现参数化查询的方法
1. 使用ADO.NET原生方式
ADO.NET是最基础的数据库操作方式,通过SqlCommand对象的Parameters集合添加参数即可实现参数化查询,以下是查询和新增的示例:
using System.Data.SqlClient;
// 查询示例
public User GetUserById(int userId)
{
string connectionString = "Server=.;Database=TestDB;Integrated Security=true";
string sql = "SELECT Id, UserName, Email FROM Users WHERE Id = @UserId";
using (SqlConnection conn = new SqlConnection(connectionString))
{
conn.Open();
using (SqlCommand cmd = new SqlCommand(sql, conn))
{
// 添加参数,指定参数名和值,类型会自动匹配
cmd.Parameters.AddWithValue("@UserId", userId);
using (SqlDataReader reader = cmd.ExecuteReader())
{
if (reader.Read())
{
return new User
{
Id = reader.GetInt32(0),
UserName = reader.GetString(1),
Email = reader.GetString(2)
};
}
}
}
}
return null;
}
// 新增示例
public void AddUser(string userName, string email)
{
string connectionString = "Server=.;Database=TestDB;Integrated Security=true";
string sql = "INSERT INTO Users (UserName, Email) VALUES (@UserName, @Email)";
using (SqlConnection conn = new SqlConnection(connectionString))
{
conn.Open();
using (SqlCommand cmd = new SqlCommand(sql, conn))
{
// 可以显式指定参数类型,避免类型转换问题
cmd.Parameters.Add("@UserName", System.Data.SqlDbType.NVarChar, 50).Value = userName;
cmd.Parameters.Add("@Email", System.Data.SqlDbType.NVarChar, 100).Value = email;
cmd.ExecuteNonQuery();
}
}
}
public class User
{
public int Id { get; set; }
public string UserName { get; set; }
public string Email { get; set; }
}
2. 使用Dapper ORM框架
Dapper是轻量级的ORM框架,支持自动参数化,编写代码更简洁,以下是使用Dapper实现参数化查询的示例:
using Dapper;
using System.Data.SqlClient;
using System.Collections.Generic;
public List<User> GetUsersByAge(int minAge, int maxAge)
{
string connectionString = "Server=.;Database=TestDB;Integrated Security=true";
string sql = "SELECT Id, UserName, Age FROM Users WHERE Age BETWEEN @MinAge AND @MaxAge";
using (SqlConnection conn = new SqlConnection(connectionString))
{
// 直接传递匿名对象作为参数,Dapper会自动处理参数化
return conn.Query<User>(sql, new { MinAge = minAge, MaxAge = maxAge }).AsList();
}
}
其他辅助防护措施
除了使用参数化查询,还可以配合以下措施进一步提升安全性:
- 对用户输入做合法性校验,比如限制输入长度、过滤特殊字符,只接受符合预期格式的输入
- 数据库账号遵循最小权限原则,不要使用sa等最高权限账号连接数据库,只给应用账号分配必要的查询、新增等操作权限
- 避免将详细的数据库错误信息直接返回给用户,防止攻击者通过错误信息推断数据库结构
- 定期更新数据库驱动和框架版本,修复已知的安全漏洞
总结
参数化查询是C#中处理Sql注入攻击最有效、最通用的方案,无论是使用原生ADO.NET还是Dapper等ORM框架,都可以通过简单的参数配置实现。开发者需要养成不拼接SQL字符串的习惯,所有涉及用户输入的数据库操作都优先使用参数化查询,再配合其他辅助防护措施,就能大幅降低应用遭受Sql注入攻击的风险。