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 相关流程

  1. 调用链

    • Session 调用 Request 的 get_mutations 函数
    • Request 使用 stack 变量存储所有 Block 和 Primitive
    • get_mutations 调用 FuzzableBlock 中的 mutations 函数
    • 遍历 stack 中的元素并调用它们的 get_mutations 函数
  2. 关键发现

    • 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_namemutation_context.mutations 中,则执行变异策略
    • 否则使用原语的默认值

1.4 结论

  1. 通常情况下,一次 Request 生成只执行一个 Primitive 的一次变异策略
  2. 使用 group 属性可以让 Block 和原语同时变异
  3. 不指定 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 源码分析

  1. 遍历方式:深度优先遍历所有可能的 Path

    • root→req1
    • root→req1→req2
    • root→req1→req2→req3
    • root→req1→req2→req4
  2. 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 的 Request
  • transmit_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

  1. 直接修改方式

    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"
    
  2. 优雅方式(推荐)

    • 使用 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 结论

  1. 通过 session 参数获取上一个 Request 的响应信息
  2. 通过 test_case_context 获取当前 Request
  3. 通过 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. 高级技巧

  1. 自定义变异策略

    • 继承 Primitive 类实现自定义变异逻辑
  2. 条件路径

    • 在 callback 中根据响应决定后续路径
  3. 性能优化

    • 对 non-fuzznode 设置 receive_data_after_each_request=False
    • 合理设置 max_depth 控制组合爆炸
  4. 调试技巧

    • 使用 fuzz_data_logger 记录测试信息
    • 在 callback 中保存关键请求/响应数据
Boofuzz 中的 Request 和 Path 源码分析与使用指南 1. Request 生成策略 1.1 基本概念 在 boofuzz 中,测试用例被称为 Request(数据包),每个 Request 包含多个字段,称为 Primitive(原语)。每个 Primitive 都有自己的生成策略(fuzz 策略)。 1.2 生成策略工作机制 问题 :当一个 Request 包含多个不同的 Primitive 时,它们的生成策略如何协同工作? 示例代码 : 1.3 源码分析 Mutation 相关流程 调用链 : Session 调用 Request 的 get_mutations 函数 Request 使用 stack 变量存储所有 Block 和 Primitive get_mutations 调用 FuzzableBlock 中的 mutations 函数 遍历 stack 中的元素并调用它们的 get_mutations 函数 关键发现 : 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 示例代码 形成的树结构: 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 基本概念 回调函数调用顺序: 应用场景 : 前一个 Request 获取 cookie 后一个 Request 使用 cookie 访问需要登录的页面 3.2 源码分析 数据传输函数 transmit_normal :发送 non-fuzznode 的 Request transmit_fuzz :发送 fuzznode 的 Request callback 函数在前一个 Node 发包之后、后一个 Node 发包之前调用 获取响应数据 session.last_send :存储发送的数据 session.last_recv :存储接收的数据(默认 non-fuzznode 会自动 recv) fuzznode 默认不 recv(因为容易超时且是最后一个节点) 示例代码 修改后续 Request 直接修改方式 : 优雅方式(推荐) : 使用 ProtocolSessionReference 通过 test_case_context.session_variables 修改值 原理 : 对于使用 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 定义 4.2 Session 配置 4.3 Callback 使用 4.4 变异控制 5. 高级技巧 自定义变异策略 : 继承 Primitive 类实现自定义变异逻辑 条件路径 : 在 callback 中根据响应决定后续路径 性能优化 : 对 non-fuzznode 设置 receive_data_after_each_request=False 合理设置 max_depth 控制组合爆炸 调试技巧 : 使用 fuzz_data_logger 记录测试信息 在 callback 中保存关键请求/响应数据