红队实战:用 CodeQL + LLM 打造“自动代码审计机”,我在 GitHub 热门项目里挖到了 3 个 0-day
每一个做过源码审计的人都知道,使用传统工具扫描时,最痛苦的不是没漏洞,而是99% 的误报。工具告诉你:“这里有个 SQL 注入!。——这只是打印日志,根本没进数据库。CodeQL 的强大在于它支持“污点追踪 (Taint Tracking)”:它能确认数据是否真的从Source(入口)流到了Sink(危险函数)。LLM 的强大在于它懂“语义”:它能看懂代码里是否有这种鉴权逻辑,或者这种过滤函数是否
标签: #CodeQL #LLM #RedTeam #0Day #AutomatedAudit #CyberSecurity
🩸 前言:告别“误报地狱”
每一个做过源码审计的人都知道,使用传统工具扫描时,最痛苦的不是没漏洞,而是99% 的误报。
工具告诉你:“这里有个 SQL 注入!”
你一看代码:log.info("User query: " + input)。
——这只是打印日志,根本没进数据库。
CodeQL 的强大在于它支持“污点追踪 (Taint Tracking)”:它能确认数据是否真的从 Source(入口)流到了 Sink(危险函数)。
LLM 的强大在于它懂“语义”:它能看懂代码里是否有 if (isAdmin) 这种鉴权逻辑,或者 sanitize(input) 这种过滤函数是否有效。
把两者结合,就是降维打击。
⚙️ 一、 架构设计:审计机是如何工作的?
我们将整个流程自动化,由 Python 脚本串联。
自动化流水线 (Mermaid):
🔍 二、 第一步:CodeQL 建模(寻找嫌疑人)
CodeQL 将代码视为数据。我们需要编写 QL 脚本来寻找“潜在的罪犯”。
以 JDBC SQL 注入 为例,我们不仅要找 Statement.execute,还要找数据是不是从 HttpServletRequest 来的。
核心 QL 查询脚本 (SqlInjection.ql):
/**
* @name SQL Injection
* @description Finds user-controlled data flowing into SQL queries.
* @kind path-problem
*/
import java
import semmle.code.java.dataflow.FlowSources
import semmle.code.java.security.SqlInjectionQuery
// 定义污点追踪配置
class SqlInjectionConfig extends TaintTracking::Configuration {
SqlInjectionConfig() { this = "SqlInjectionConfig" }
// Source: HTTP 请求参数
override predicate isSource(DataFlow::Node source) {
source instanceof RemoteFlowSource
}
// Sink: SQL 执行函数
override predicate isSink(DataFlow::Node sink) {
sink instanceof SqlInjectionSink
}
}
from SqlInjectionConfig conf, DataFlow::PathNode source, DataFlow::PathNode sink
where conf.hasFlowPath(source, sink)
select sink.getNode(), source, sink, "Potential SQL Injection from $@.", source.getNode(), "user input"
运行这个查询,CodeQL 会吐出一个 SARIF 格式的 JSON 文件,里面包含了几十条可能的注入路径。
这时候,如果人工去看,还是太累。
🧠 三、 第二步:LLM 判决(神探介入)
我们需要写一个 Python 脚本,解析 SARIF 文件,提取出完整的代码上下文(不仅仅是报错的那一行,而是整个函数),然后喂给 LLM。
Prompt 设计 (关键):
Role: 你是一个世界级的网络安全专家。
Task: 我会给你一段代码,CodeQL 认为第 15 行存在 SQL 注入风险。
Input:
- Source:
String id = request.getParameter("id");- Sink:
statement.execute("SELECT * FROM users WHERE id = " + id);- Context Code: [完整函数代码…]
Analysis:
请检查代码中是否存在有效的过滤(Sanitization)或验证。例如,是否使用了Integer.parseInt(id)?是否使用了预编译?
Output:
如果是误报,回复{"status": "FALSE"}。
如果是真漏洞,回复{"status": "TRUE", "reason": "...", "poc_suggestion": "..."}。
Python 胶水代码片段:
import json
import openai
def analyze_vulnerability(code_snippet, source, sink):
prompt = f"""
CodeQL flagged a potential SQL Injection.
Source: {source}
Sink: {sink}
Code:
```java
{code_snippet}
```
Is this exploitable? Analyze the data flow for sanitization.
"""
response = openai.ChatCompletion.create(
model="gpt-4",
messages=[{"role": "user", "content": prompt}]
)
return response.choices[0].message.content
💥 四、 实战复盘:捕获 0-day
在扫描某知名开源 CMS(Star 数 12k+)时,自动化系统发出了警报。
1. CodeQL 发现:
- Source:
AdminController.java中的String sort = req.getParameter("sort"); - Sink:
UserDao.java中的entityManager.createQuery(sql)。 - 路径: 参数
sort被拼接到ORDER BY子句之后。
2. LLM 分析:
判定:TRUE
理由: 开发者使用了 Hibernate 的 HQL,通常 HQL 是安全的。但是,在ORDER BY子句中,Hibernate 不支持预编译参数(PreparedStatement)。开发者直接使用了字符串拼接... ORDER BY " + sort。
尽管代码前面有一行sort = sort.trim();,但这只去除了空格,没有防御 SQL 注入。
PoC 建议: 尝试基于布尔盲注或报错注入,例如id DESC, (CASE WHEN (SELECT 1)=1 THEN 1 ELSE 1/0 END)。
3. 人工验证:
我立刻搭建环境,发送 Payload:GET /admin/users?sort=id, (SELECT CASE WHEN (SUBSTRING(password,1,1)='a') THEN 1 ELSE 1/0 END)
服务器返回 500 错误!
修改条件为 FALSE,服务器正常返回。
实锤了!一个 HQL 盲注 0-day。
🛡️ 五、 成果汇总
在一周的“挂机”运行中,这套系统扫描了 50 个项目,消耗了约 $30 的 API 额度,成果如下:
- 高危 (Critical): 某 CMS 系统后台 HQL 注入 (已获 CVE)。
- 高危 (High): 某微服务框架的 Zip Slip (文件解压路径遍历) 漏洞。
- 中危 (Medium): 某 IoT 管理平台的硬编码密钥泄露。
误报率对比:
- 仅使用 CodeQL:误报率约 85%(需大量人工排查)。
- CodeQL + LLM:误报率降至 15% 以下。
🎯 总结
“自动化”不是为了偷懒,而是为了把人的精力集中在最关键的“利用”环节。
CodeQL 解决了“找不到”的问题,LLM 解决了“看不准”的问题。
作为红队人员,掌握 AST(抽象语法树)分析 和 LLM Agent 开发,将是你未来最核心的竞争力。
Next Step:
这套系统的瓶颈在于 Building Database(编译项目)太慢。
下一步,尝试使用 CodeQL 的 “Dataflow without build” 特性(虽然精度低一点但不用编译),或者结合 Joern 这种基于图数据库的更轻量级扫描工具,进一步提升扫描速度。
更多推荐

所有评论(0)