如何选择 SQL 注入扫描工具

Yuta Matsuyama

TECHFUND 株式会社 安全研究员

更新日期:

适合谁阅读: 选择 SQL 注入测试工具的开发与安全团队。

简要回答

要检查SQL注入,首先要确定检查对象是“正在运行的Web应用”还是“源代码”。前者使用ZAP或sqlmap等工具进行动态检测,后者使用代码分析;对重要部分同时采用两种方式确认,是一种实用的做法。

选择扫描器之前,先确定检查入口

例如,假设搜索表单中输入的值被直接拼接成SQL语句的一部分。动态检测会实际发送请求,通过响应差异等线索查找问题。代码分析则会追踪值从哪里进入,以及被传递给了什么样的查询。

动态检测如果无法访问页面或API,就无法进行检查。代码分析也可能因支持的语言、框架及分析对象的配置而漏报。即使其中一种方式没有发现任何问题,也不能据此认定另一种方式不再必要。

代表性工具及适用场景

ZAP:想要检查整个Web应用时,可以考虑的开源工具。它提供SQL注入主动扫描规则,也能一并检查其他Web漏洞。不过,ZAP Baseline默认执行的是被动扫描,因此不能仅凭运行了它,就认为已经完成SQL注入的主动检测。

sqlmap:当某个特定输入点存在疑点,想要重点验证SQL注入时使用的开源工具。除了检测,它还具备自动化利用功能,因此应先明确授权范围,并在能够控制影响的环境中使用。

Burp Suite Professional:适合希望在查看HTTP请求的同时,结合扫描器与手动验证的人员。自动扫描器包含在付费的Professional等版本中,不包含在免费的Community Edition中。

Wapiti:从命令行对Web应用进行黑盒检查的开源工具。官方资料介绍了利用错误、布尔值、时间差进行SQL注入检测,以及使用OpenAPI文件扫描API的方法。对于包含SPA或复杂认证机制的目标,请逐一验证工具能否访问所需的路径。

这些工具都会向正在运行的应用发送请求。应将实际访问到的范围与结果一起记录下来,不仅包括页面,还包括认证后的API、管理页面以及不同用户的权限。基于源代码的静态分析则作为另一种方式配合使用。

引入工具时,检测数量与覆盖范围都要比较

在同一个预发布环境中,准备存在已知问题的输入点和已安全实现参数化的输入点,进行对比。除了确认能否发现前者,还要确认是否会将后者误报为问题。也请检查是否因认证失效而导致检测中途停止,以及是否遍历了包括JSON API在内的必要入口。

扫描可能影响测试数据或可用性。应获得目标所有者的明确授权,就目标范围、执行时间、负载及需要排除的操作达成一致,并尽可能在与生产环境隔离的环境中尝试。

从MOVEit案例看公开访问路径的检查

NVD记录的MOVEit Transfer漏洞CVE-2023-34362指出,SQL注入可能使未经认证的攻击者访问数据库,并记载了2023年5月至6月期间的漏洞利用情况。在开发阶段,不仅需要检查登录后的处理流程,也需要检查公开访问路径。以下是使用TypeScript和node-postgres编写的虚构教学示例,并非MOVEit的实现,也不是该CVE的复现代码。请仅对已获授权的目标进行检查。

发现问题后,将值与SQL结构分离

修复的基本做法是,不将外部传入的值拼接到SQL字符串中,而是将其作为参数传递。不过,排序方式、表名等SQL结构本身,有时无法仅靠值绑定来处理。这种情况下,应从应用端预先准备的固定选项中选择。

下面的示例绑定搜索值,并从两个固定查询中选择排序方式。此外,还应将数据库用户的权限限制在必要的最小范围内,并单独检查使用者是否有权查看这些数据。修复后,需要再次验证原先指出的问题已解决,且正常搜索功能未受破坏。

这是服务端的node-postgres教学示例。值绑定到$1,ORDER BY从两个固定查询中选择,并拒绝未知的排序方式。使用者授权和数据库最小权限仍需另外落实。还要确认引号被作为数据处理、两种排序方式均正常工作,以及无权限的使用者会被拒绝访问。
import type { Pool } from "pg";

export async function findProducts(
  db: Pool, name: string, sort: string,
) {
  const queries = {
    newest: "SELECT id, name FROM products WHERE name = $1 ORDER BY created_at DESC, id DESC LIMIT 50",
    oldest: "SELECT id, name FROM products WHERE name = $1 ORDER BY created_at ASC, id ASC LIMIT 50",
  } as const;
  if (sort !== "newest" && sort !== "oldest") {
    throw new Error("Invalid sort");
  }
  return db.query(queries[sort], [name]);
}
示例流程:将搜索值与 SQL 结构分开
  1. 用户输入搜索值
  2. 应用将值绑定到 $1
  3. 应用向数据库发送固定查询和值并执行

ORDER BY 等 SQL 结构应从允许的固定查询中选择。用户授权和数据库最小权限仍需另行实现;此图不保证安全。

常见问题

扫描工具能保证应用没有 SQL 注入吗?

不能。覆盖范围、配置和测试访问权限都会限制发现能力,应结合代码审查。

可以测试生产应用吗?

必须获得明确授权并约定范围。动态测试可能影响数据或可用性,应尽量使用受控环境。

来源与延伸阅读

Hi AUDIT Agent 如何帮助您

使用 Hi AUDIT Agent 检查代码中的数据流、发现位置和修复建议。请确认对目标环境中 SQL 注入的覆盖范围,并由人员复核结果。

注册 Hi AUDIT Agent