SQL 注入漏洞代码审计:常见危险函数与修复方案

SQL 注入(SQL Injection)长期位居 OWASP Top 10 之首,是 Web 应用中最危险、最普遍的漏洞类型之一。对于安全审计人员而言,快速识别代码中的危险函数和易受攻击的写法,是发现 SQL 注入漏洞的核心能力。本文将从代码审计的视角出发,系统梳理常见危险函数、典型漏洞模式,并给出切实可行的修复方案。

一、SQL 注入的本质

SQL 注入的本质是:用户可控的输入被拼接进 SQL 语句中,并被数据库当作代码执行。这意味着两个条件同时成立时,漏洞即存在:

  1. 数据来源可控(如 GET/POST 参数、Cookie、HTTP Header);
  2. 数据流向 SQL 执行函数,且未做充分的参数化处理。

代码审计的核心工作,就是追踪“污点数据”从入口到执行点的完整链路。

二、常见危险函数与写法

1. 字符串拼接 + 查询执行

这是最经典、最直观的漏洞模式。在 Java、PHP、Python、C# 等语言中均广泛存在。

PHP 示例:

$id = $_GET['id'];
$sql = "SELECT * FROM users WHERE id = $id";
$result = mysqli_query($conn, $sql);

Java 示例:

String id = request.getParameter("id");
String sql = "SELECT * FROM users WHERE id = " + id;
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sql);

Python 示例:

id = request.args.get('id')
cursor.execute("SELECT * FROM users WHERE id = %s" % id)

以上代码均未对输入做参数化处理,攻击者只需传入 1 OR 1=11; DROP TABLE users-- 即可实现注入。

2. 危险函数清单

不同语言和框架中,需要重点关注的执行函数包括:

  • PHPmysql_querymysqli_queryPDO::queryPDO::exec(当拼接字符串时)
  • JavaStatement.executeStatement.executeQueryStatement.executeUpdateprepareStatement 中仍拼接字符串的写法
  • Pythoncursor.execute(配合 %format+ 拼接)、pymysqlsqlite3 的原始执行
  • C#/.NETSqlCommand 配合字符串拼接、ExecuteNonQueryExecuteReader
  • Node.jsmysql.querysequelize.query(原始 SQL 模式)

3. 容易被忽视的注入点

除常见的 WHERE 条件外,以下位置同样容易产生注入:

  • ORDER BY / GROUP BY:无法使用预编译占位符,需白名单校验;
  • LIMIT / OFFSET:部分数据库驱动不支持参数化;
  • 表名、列名:动态拼接时极易注入;
  • IN 子句:拼接 IN (1,2,3) 时若未做类型转换;
  • LIKE 语句LIKE '%$keyword%' 中的通配符拼接。

4. 二次注入

二次注入(Second-Order Injection)更为隐蔽:恶意数据先被“安全地”存入数据库,之后在另一处被取出并拼接进 SQL 语句执行。审计时需关注数据的完整生命周期,而非单点检查。

三、修复方案

1. 首选:参数化查询(预编译)

参数化查询是防御 SQL 注入最有效的手段。它将 SQL 语句结构与数据分离,使数据永远不会被解析为代码。

Java 正确写法:

String sql = "SELECT * FROM users WHERE id = ?";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, Integer.parseInt(id));
ResultSet rs = pstmt.executeQuery();

PHP 正确写法(PDO):

$stmt = $pdo->prepare("SELECT * FROM users WHERE id = :id");
$stmt->execute([':id' => $id]);

Python 正确写法:

cursor.execute("SELECT * FROM users WHERE id = %s", (id,))

注意:Python 中必须使用参数元组,而非 % 字符串格式化。

2. 输入验证与类型强制

对于无法参数化的场景(如 ORDER BY 列名),采用白名单校验

Set<String> allowed = Set.of("id", "name", "created_at");
if (!allowed.contains(sortField)) {
    throw new IllegalArgumentException("Invalid sort field");
}
String sql = "SELECT * FROM users ORDER BY " + sortField;

数字型参数应强制转换为整型:

$id = intval($_GET['id']);

3. 最小权限原则

数据库账户应遵循最小权限原则:Web 应用使用的账户不应具备 DROPFILEGRANT 等高危权限,从而在注入发生时限制损害范围。

4. 统一数据访问层

在项目架构中封装统一的 DAO 层,禁止业务代码直接拼接 SQL。通过代码规范和静态扫描工具(如 SonarQube、Fortify、CodeQL)在 CI 阶段拦截危险写法。

5. ORM 框架的正确使用

MyBatis、Hibernate、SQLAlchemy 等 ORM 框架本身具备防注入能力,但错误使用仍会引入漏洞:

  • MyBatis 中 ${} 是字符串拼接,#{} 才是预编译占位符,应优先使用 #{}
  • Hibernate 的 HQL 拼接、createSQLQuery 原始 SQL 需谨慎;
  • SQLAlchemy 的 text() 中拼接变量同样危险。

四、审计思路小结

进行 SQL 注入代码审计时,可遵循以下流程:

  1. 定位入口:找出所有接收外部输入的位置(Request、Cookie、Header);
  2. 追踪污点:沿数据流追踪变量传递路径;
  3. 识别执行点:定位所有 SQL 执行函数;
  4. 判断过滤:检查是否使用参数化、白名单或转义;
  5. 验证利用:构造 PoC 确认漏洞可被触发。

五、结语

SQL 注入虽然“古老”,但因开发习惯、历史代码和框架误用,至今仍是 Web 安全的主要威胁之一。防御的核心原则只有一条:永远不要相信用户输入,永远不要拼接 SQL。参数化查询配合最小权限、白名单校验和统一数据访问层,可以构建起多层次的纵深防御体系。对于安全从业者而言,掌握危险函数的识别能力和污点追踪的审计思路,是高效发现并修复 SQL 注入漏洞的关键。

未经允许不得转载:任鹏个人博客 » SQL 注入漏洞代码审计:常见危险函数与修复方案

赞 (0) 打赏

评论 0

取消
  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏