初探CodeQL之Python篇-使用AST(一)
字数 2114 2025-08-22 12:22:24
CodeQL Python 静态分析基础教程:AST 应用与实践
1. 静态分析基础概念
1.1 AST(抽象语法树)
AST 是将源代码以树形式进行表示的数据结构,在 CodeQL 中可以将其视为比正则表达式更精确的代码匹配工具。
特点:
- 能够精确匹配特定代码结构(表达式、语句块、函数定义等)
- 比正则表达式定位更准确和细腻
- 主要用于关键代码、函数或表达式的查找和定位
- 不具备上下文分析能力(如参数来源追踪)
应用场景:
- 查找特定函数(如
eval()) - 匹配特定代码模式
- 替代简单的
grep搜索
1.2 控制流(Control Flow)
控制流表示程序的执行逻辑,用于分析代码的执行路径和分支。
特点:
- 表示程序的执行顺序和执行逻辑
- 用于分析
if、while、try...catch等控制结构 - 帮助理解敏感函数的调用过程
应用场景:
- 函数回溯分析
- 执行路径分析
- 条件分支分析
1.3 数据流(Data Flow)
数据流分析用于追踪数据在程序中的流动路径。
关键概念:
- Source(源点):程序的可控输入点(如
$_GET、$_POST、request.GET) - Sink(汇点):漏洞触发点(如
system()、eval()、os.system())
应用场景:
- 判断数据是否能从 source 成功到达 sink
- 分析参数传递路径
1.4 污点分析(Taint Tracking)
污点分析是数据流分析的扩展,用于处理数据在传递过程中的变化。
特点:
- 标记特定输入为"污点"(不安全、用户可控)
- 跟踪污点数据的传播过程
- 解决数据流分析中数据变化导致跟踪中断的问题
示例场景:
username = request.GET.get("username") # source
sql = f"SELECT * FROM users WHERE username={username}" # 污点传播
cursor.execute(sql) # sink
2. CodeQL Python 基础应用
2.1 基本查询结构
import python
from Function f
select f, "Found function:" + f.toString()
2.2 函数装饰器查询
查找所有带有装饰器的函数:
from Function f
where f.getADecorator().toString() != "" and
f.getLocation().getFile().getRelativePath().regexpMatch(".*")
select f, "Path is:" + f.getLocation().getFile().getRelativePath() +
" Decorator string:" + f.getADecorator().toString()
2.3 特定装饰器查询
查找使用 FastAPI APIRouter 装饰的函数:
import python
from Function f, Call c, Name router
where f.getADecorator().toString() != "" and
f.getADecorator() instanceof Call and
f.getADecorator().getAChildNode() instanceof Attribute and
router = f.getADecorator().getAChildNode().getAChildNode() and
router.getId() = "router" and
f.getLocation().getFile().getRelativePath().regexpMatch(".*")
select f, "Path is:" + f.getLocation().getFile().getRelativePath() +
" Decorator string:" + f.getADecorator().toString()
2.4 参数检查查询
查找不包含 session_user 参数的 FastAPI 路由函数:
import python
from Function f, Name router, Call decorator
where decorator = f.getADecorator() and
decorator.getAChildNode() instanceof Attribute and
router = decorator.getAChildNode().getAChildNode() and
router.getId() = "router" and
not exists(
Parameter session_user |
session_user = f.getAnArg() and
session_user.getName() = "session_user"
) and
f.getLocation().getFile().getRelativePath().regexpMatch(".*")
select f, "Path is:" + f.getLocation().getFile().getRelativePath()
2.5 改进版参数检查
查找不包含 session_user 或 user 参数的 FastAPI 路由函数:
import python
from Function f, Name router, Call decorator
where decorator = f.getADecorator() and
decorator.getAChildNode() instanceof Attribute and
router = decorator.getAChildNode().getAChildNode() and
router.getId() = "router" and
not exists(
Parameter session_user |
session_user = f.getAnArg() and
(session_user.getName() = "session_user" or
session_user.getName() = "user")
) and
f.getLocation().getFile().getRelativePath().regexpMatch(".*")
select f, "Path is:" + f.getLocation().getFile().getRelativePath()
3. 调试技巧与最佳实践
3.1 调试技巧
- 使用
toString()输出:在不确定查询结果时,使用toString()输出中间结果 - AST Viewer 工具:利用 VSCode CodeQL 扩展的 AST Viewer 可视化代码结构
- 类型判断:使用
instanceof判断节点类型 - 类信息查询:使用
getAQlClass()获取节点的 CodeQL 类型
3.2 查询优化技巧
- 路径限制:使用
getLocation().getFile().getRelativePath().regexpMatch(".*")限制查询范围 - 子节点遍历:使用
getAChildNode()遍历 AST 节点 - 存在性判断:使用
not exists()进行否定条件判断 - 参数获取:使用
getAnArg()获取函数参数
3.3 实用谓词
Function.getADecorator():获取函数装饰器Call.getAChildNode():获取调用表达式的子节点Name.getId():获取名称标识符Parameter.getName():获取参数名称Function.getAnArg():获取函数参数
4. 实际案例分析
4.1 案例背景
分析目标:open-webui 项目中可能存在未授权访问漏洞的路由函数
4.2 分析步骤
-
识别 FastAPI 路由函数特征:
- 使用
APIRouter装饰 - 装饰器形式如
@router.get("/path")
- 使用
-
构建查询:
- 查找所有使用
router装饰的函数 - 排除包含
session_user或user参数的函数
- 查找所有使用
-
验证结果:
- 检查查询结果的函数是否确实不需要认证
- 人工验证潜在的安全问题
4.3 完整查询
import python
from Function f, Name router, Call decorator
where decorator = f.getADecorator() and
decorator.getAChildNode() instanceof Attribute and
router = decorator.getAChildNode().getAChildNode() and
router.getId() = "router" and
not exists(
Parameter session_user |
session_user = f.getAnArg() and
(session_user.getName() = "session_user" or
session_user.getName() = "user")
) and
f.getLocation().getFile().getRelativePath().regexpMatch(".*")
select f, "Path is:" + f.getLocation().getFile().getRelativePath()
5. 总结与进阶
5.1 学习要点
- AST 是基础:掌握 AST 结构是编写有效查询的前提
- 逐步构建查询:从简单查询开始,逐步添加条件
- 善用调试工具:AST Viewer 和
toString()是重要调试手段 - 理解静态分析概念:数据流、控制流和污点分析的关系
5.2 进阶方向
- 结合数据流分析:从 AST 查询扩展到数据流分析
- 自定义污点跟踪:定义特定的 source 和 sink
- 复杂模式识别:识别更复杂的漏洞模式
- 性能优化:优化大型项目的查询性能