boofuzz 中的 Request 和 Path 源码分析
字数 2661 2025-08-22 12:23:18
Boofuzz 中的 Request 和 Path 源码分析与使用指南
1. Request 生成策略
1.1 基本概念
在 boofuzz 中,测试用例被称为 Request(数据包),每个 Request 包含多个字段,称为 Primitive(原语)。每个 Primitive 都有自己的生成策略(fuzz 策略)。
1.2 生成策略工作机制
问题:当一个 Request 包含多个不同的 Primitive 时,它们的生成策略如何协同工作?
示例代码:
s_initialize("req1")
s_simple(value="0", fuzz_values=["1", "2", "3"], name="simple_field1")
s_simple(value="5", fuzz_values=["6", "7"], name="simple_field2")
1.3 源码分析
Mutation 相关流程
-
调用链:
- Session 调用 Request 的
get_mutations函数 - Request 使用
stack变量存储所有 Block 和 Primitive get_mutations调用FuzzableBlock中的mutations函数- 遍历
stack中的元素并调用它们的get_mutations函数
- Session 调用 Request 的
-
关键发现:
Fuzzable.get_mutations是一个迭代器,每次迭代返回一个包含单个Mutation类的列表- 这表明在一次 Request 生成过程中通常只执行一个 Primitive 的一次生成策略
分组变异(Group Mutation)
- Block 类有
group属性,可以让 Block 和 group 绑定的原语同时进行变异 - 这种情况下
mutations迭代器返回的是group_mutations + mutations(两个 Mutation 列表相加)
组合模糊测试
- 当调用
session.fuzz()不指定max_depth参数时,会启用组合模糊测试 depth值决定 Request 中进行变异的原语数量depth=1:每次只变异一个原语depth=2:每次变异两个原语的组合- 以此类推
示例:
- 3 个 Primitive:primitive1, primitive2, primitive3
depth=1:顺序变异每个原语depth=2:变异 primitive1 + primitive2, primitive1 + primitive3 等组合
Render 相关流程
- 核心函数:
Fuzzable.get_value - 判断逻辑:
- 如果原语的
qualified_name在mutation_context.mutations中,则执行变异策略 - 否则使用原语的默认值
- 如果原语的
1.4 结论
- 通常情况下,一次 Request 生成只执行一个 Primitive 的一次变异策略
- 使用
group属性可以让 Block 和原语同时变异 - 不指定
max_depth时,boofuzz 会进行组合变异,同时变异的原语数量随测试轮数增加
2. Path 管理策略
2.1 基本概念
- Path:由多个 Request 构成的调用链(如登录→操作)
- Node:Path 中的每个 Request 称为一个 Node
- 树结构:多个 Request 可以形成树状调用关系
2.2 示例代码
session.connect(s_get("req1"))
session.connect(s_get("req1"), s_get("req2"))
session.connect(s_get("req2"), s_get("req3"))
session.connect(s_get("req2"), s_get("req4"))
形成的树结构:
root
└── req1
└── req2
├── req3
└── req4
2.3 源码分析
-
遍历方式:深度优先遍历所有可能的 Path
- root→req1
- root→req1→req2
- root→req1→req2→req3
- root→req1→req2→req4
-
Node 分类:
- fuzz_node:Path 的最后一个 Node,需要进行变异
- non-fuzznode:其他 Node,直接使用默认值
2.4 结论
- boofuzz 以深度优先方式遍历所有可能的 Path
- 只对 Path 中最后一个 Request(fuzz_node)进行变异
- 前面 Request(non-fuzznode)使用默认值
3. Request 之间的 Callback
3.1 基本概念
回调函数调用顺序:
pre_send() - req - callback ... req - callback - post-test-case-callback
应用场景:
- 前一个 Request 获取 cookie
- 后一个 Request 使用 cookie 访问需要登录的页面
3.2 源码分析
数据传输函数
transmit_normal:发送 non-fuzznode 的 Requesttransmit_fuzz:发送 fuzznode 的 Request- callback 函数在前一个 Node 发包之后、后一个 Node 发包之前调用
获取响应数据
session.last_send:存储发送的数据session.last_recv:存储接收的数据(默认 non-fuzznode 会自动 recv)- fuzznode 默认不 recv(因为容易超时且是最后一个节点)
示例代码
def test_case_callback(target, fuzz_data_logger, session, test_case_context, *args, **kwargs):
with open("test.txt", "wb") as f:
f.write(session.last_send)
f.write(session.last_recv)
修改后续 Request
-
直接修改方式:
def test_case_callback(target, fuzz_data_logger, session, test_case_context, *args, **kwargs): test_case_context.current_message.names["req2.Host-Line-Value"]._default_value = "128.1.1.1" -
优雅方式(推荐):
- 使用
ProtocolSessionReference - 通过
test_case_context.session_variables修改值
- 使用
def test_case_callback(target, fuzz_data_logger, session, test_case_context, *args, **kwargs):
test_case_context.session_variables["ref_data"] = "128.1.1.1"
s_static(ProtocolSessionReference(name="ref_data", default_value="127.0.0.1"), name="Host-Line-Value")
原理:
- 对于使用
default_value的原语,render 时会检查是否为ProtocolSessionReference类型 - 如果是,则从
test_case_context.session_variables[name]中取值
3.3 结论
- 通过
session参数获取上一个 Request 的响应信息 - 通过
test_case_context获取当前 Request - 通过
test_case_context.session_variables影响使用default_value的原语
4. 最佳实践指南
4.1 Request 定义
s_initialize("login")
s_string("admin", name="username")
s_string("password123", name="password")
s_initialize("action")
s_static("GET /admin/")
s_static(ProtocolSessionReference(name="auth_token", default_value=""), name="token")
4.2 Session 配置
session = Session(
target=Target(
connection=TCPSocketConnection("127.0.0.1", 8080),
),
receive_data_after_each_request=True
)
4.3 Callback 使用
def extract_token(target, fuzz_data_logger, session, test_case_context, *args, **kwargs):
# 从响应中提取 token
token = parse_token(session.last_recv)
test_case_context.session_variables["auth_token"] = token
session.connect(s_get("login"))
session.connect(s_get("login"), s_get("action"), callback=extract_token)
4.4 变异控制
# 只变异单个字段
session.fuzz(max_depth=1)
# 启用组合变异
session.fuzz()
5. 高级技巧
-
自定义变异策略:
- 继承
Primitive类实现自定义变异逻辑
- 继承
-
条件路径:
- 在 callback 中根据响应决定后续路径
-
性能优化:
- 对 non-fuzznode 设置
receive_data_after_each_request=False - 合理设置
max_depth控制组合爆炸
- 对 non-fuzznode 设置
-
调试技巧:
- 使用
fuzz_data_logger记录测试信息 - 在 callback 中保存关键请求/响应数据
- 使用