← 返回 Day 164 正文

Day 164 · 练习与清单

Day 164 完成清单 · 安全审计工具箱(三):代理中间人 + 流量分析

用法:先自己跑,再对照打勾。打不上的勾就是今天没掌握的部分。 所有命令都在 days/day-164-安全审计工具箱-part3/code/ 下执行。


✅ 一、完成清单(照着打勾)

概念与原理(读 README.md)

代码与实测(在 code/ 下跑)

工程习惯


📝 二、基础练习题(3 道)

练习 1:读档案,找出"看得见"与"看不见"的边界(观察题)

bash
cd days/day-164-安全审计工具箱-part3/code
python3 01-flow-capture.py > /dev/null
python3 - <<'EOF'
import json
rows = [json.loads(l) for l in open("out/day164-basic-flows.jsonl", encoding="utf-8")]
for r in rows:
    print(f"{r['fid']}  {r['method']:<7} inspected={str(r['inspected']):<5} "
          f"status={r['response_status']:<4} url={r['url']}")
EOF

问题:

  1. 哪一条 flow 的 inspected 是 False?它的 method 是什么?
  2. 它的 response_body 和 request_body 分别是什么?为什么?
  3. coverage_percent 算出多少?如果这条 flow 不存在,覆盖率会变成多少?

验收标准:能用一句话说清"inspected=False 意味着没有结论, 而不是没有问题",并说出它会把退出码推到哪个值。


练习 2:只加一条规则,并解释它的代价

给 FlowAnalyzer 加一条 R16 http-method-over-plaintext-header: 当 scheme == "http" 且请求头里出现 X-API-Key 时报告一条 high 发现。

python
# 在 FlowAnalyzer._analyze_one 里找位置插入(参考 R3 的写法)

问题:

  1. 为什么这条规则要单独加,而不是直接放进 SENSITIVE_KEYS? 提示:SENSITIVE_KEYS 管的是"值是否脱敏",规则管的是"这件事是不是问题"。
  2. 加完之后 --self-test 会不会失败?为什么? 提示:靶场里有没有端点在明文连接上发 X-API-Key?
  3. 如果要让自检通过,你会改靶场还是改断言?两个选择各自的含义是什么?

验收标准:能说清"新增检测规则"与"新增脱敏字段"是两件不同的事, 且能说出为什么改动规则时必须同步考虑靶场/断言。


练习 3:手算一次优先级(算术题)

现有三条发现:

规则 严重度 置信度
A cleartext-credential high 0.95
B tls-tunnel-uninspected medium 1.00
C suspected-time-based-sqli(流量层推测) high 0.30

问题:

  1. 分别算出三条的 priority 与 level
  2. 按优先级排序,并说明 C 为什么排在 A 之后——尽管它俩严重度相同
  3. 如果 C 的置信度被提到 0.80,排序会怎么变?这次变化说明了什么?

验收标准:能说清"严重但很可能假阳性的发现必须排在 不严重但确定的发现之后",并说出理由(否则运维会不再读报告)。


🔍 三、自检命令(一键跑完)

bash
cd days/day-164-安全审计工具箱-part3/code

# 1) 引擎自检(7 组断言,含"档案不得含明文"这条硬线)
python3 mitm_core.py --self-test                        # 期望 SELF-TEST OK

# 2) 基础闭环
python3 01-flow-capture.py | tail -15

# 3) 规则引擎 + 四个踩坑复现
python3 02-flow-analyze.py --no-pitfalls | tail -15

# 4) CLI 三种模式的退出码
python3 03-traffic-audit-cli.py --tag verify           > /dev/null; echo "auto      exit=$?"   # 4
python3 03-traffic-audit-cli.py --capture flows.example.jsonl --quiet >/dev/null; echo "fixture   exit=$?"  # 4

# 5) 防泄密全文扫描(应全部 ✅)
python3 - <<'EOF'
import json, pathlib
BAD = ["Sup3rSecret!", "tk_live_164_fake", "sk_live_164_FAKE_NOT_REAL", "13800138000"]
for p in sorted(pathlib.Path("out").glob("*.jsonl")):
    blob = p.read_text(encoding="utf-8")
    hits = [b for b in BAD if b in blob]
    print(f"{p.name}: {'❌ ' + str(hits) if hits else '✅ 无明文'}")
EOF

📌 四、今日关键结论(背下来)

  1. 流量面给的是事实,前两面给的是推断。 事实不需要争论,所以它在报告里最站得住脚。

  2. CONNECT 之后代理是哑管子。 能看见 host:port、字节数、时间;看不见路径、头、体。 不伪造证书、不降级 TLS——"看不见"必须如实记账。

  3. "没看" ≠ "没问题"。 这是整份报告诚实性的基准线,也是退出码 4 存在的唯一理由。

  4. 逐跳头必须剔除。 不剔除会污染两段连接的状态, 严重时构成请求走私(request smuggling)的土壤。

  5. 先脱敏,再检测。 检测靠"字段名 + 指纹标记"两个结构信号工作, 全程不需要看到明文。脱敏是唯一入口,不是事后补救。

  6. PII 擦除与 PII 检测必须分开。 擦掉就再也扫不到——所以必须在擦除的同一时刻记下类别 (flow.pii_labels)。

  7. 覆盖率是两层的。 流量层(内容看得见多少)+ 业务层(清单端点出现在流量里的比例)。 只看第一层会得到"100% 覆盖"的假象。

  8. 优先级 = 严重度 × 置信度。 报告的价值在"被读",不在"条目多"。降噪是为了让人继续读。

  9. 退出码的优先级顺序 = 谁该被最先通知。 2 找流程、4 找运维、3 找开发、0 才能自动通过。

  10. 五个真实 bug 的共性:假设当成了事实。 ① 框架默认行为 ② 编码后字面量 ③ 同源数据漏脱敏 ④ 破坏性操作与检测的顺序 ⑤ 端口漂移造成的假盲区。 对策:把假设变成断言。


🚀 五、进阶挑战题(3 道)

挑战 1:把"业务层覆盖"做成真正的门禁

背景:现在 reconcile_targets() 只是算出"哪些清单端点从未出现", 并在退出码为 0 时把它降级成 4。但它有两个明显的漏洞:

  1. 如果退出码已经是 3(有发现),业务层缺口就被吞掉了;
  2. 输出的"未见端点"只是一行行打印,没有结构化产物,没法交给下一环节。

任务:

  1. 让 facts["targets"] 的结构里新增 gate_passed: bool, 并让业务层覆盖率低于阈值(新增 --min-target-coverage 80)时 独立地把退出码推到 4(而不是"只在原本是 0 的时候");
  2. 产出一份 out/day164-uncovered-endpoints.md, 含表头 端点 | 建议复核方式 | 优先级,并把清单里的端点分成两类: - 静态端点(/、/health)→ "低优先,属预期未被访问" - 带参端点(/api/user?id=、/search?)→ "高优先:带参端点最可能藏漏洞, 却从未被真实流量覆盖,必须用其它手段(Day 163 的检测器)补上"

验收标准:


挑战 2:给代理加"转发但不落档"的旁路模式,并论证它什么时候是错的

背景:有时你只想让代理转发,不想留下档案(比如处理的是明文隐私数据, 连脱敏后的结构都不想留)。

任务:

  1. 给 AuditProxy 加参数 record: bool = True;record=False 时 正常转发、不写 JSONL,但仍在内存计数(这样报告能说"本轮转了 N 条,未落档");
  2. 让报告在这种情况下显式写一行: ⚠️ 本轮为旁路模式:流量未被记录,本报告无法复核任何结论;
  3. 让 exit_code() 在旁路模式下永远返回 4(因为没有可复核的证据链)。

问题:

  1. 为什么"不落档"仍然必须让退出码变成 4,而不是"干净通过"?
  2. 如果业务方说"我就是想要一个不留痕的代理",你该怎么回应? 提示:把"需求"翻译成"他要解决什么问题",再判断留痕是不是必要条件。
  3. 这个参数会不会让本工具变得危险?为什么它必须默认 True?

验收标准:能说清"证据链是审计工具的产品本身"—— 没有证据链的报告只是"某个程序的输出",不具备任何证明力。


挑战 3:用 mitmproxy 重写同一个代理,并映射流模型

背景:本课的代理是纯 socket 实现的,好处是"60 行核心逻辑能读懂", 代价是不支持 keep-alive、不支持 HTTP/2、不支持 WebSocket。 生产环境的标准答案是 mitmproxy(Day 155 已接触过它的 addon 机制)。

任务:用 mitmproxy addon 实现同一套流模型与同一套规则:

python
# 参考骨架(本机未安装 mitmproxy,属于"设计题",按文档写出来即可)
class AuditAddon:
    def __init__(self, store: FlowStore):
        self.store = store

    def request(self, flow):
        # 就在这里做脱敏 + 请求侧规则
        ...

    def response(self, flow):
        # 响应侧脱敏 + 规则 + 落档
        self.store.append(to_my_flow(flow))

问题:

  1. mitmproxy 的 flow 对象与本课的 Flow 数据结构如何一一映射? 哪些字段是它能免费给你的(本课要手写)?哪些需要你补?
  2. CONNECT 在 mitmproxy 里对应哪个钩子?它默认会尝试解密—— 这正是本课刻意不做的事。请说明"mitmproxy 默认解密"这件事 在自己设备与别人设备上的性质差异,以及你应当怎么配置它。
  3. 最重要的一题:本课的五条规则里,哪几条在 mitmproxy 下 "本来是免费的"(框架已经算好),哪几条仍然必须自己写? 提示:Set-Cookie 解析、URL 拆分、chunked 解码都有现成的; 而"明文传输了凭据"这个判断,任何框架都不会替你做。

验收标准:


挑战 4(附加):用"重复指纹"发现凭据复用

背景:value_fingerprint() 保留下来的指纹支持一类只能在流量层发现的结论:

同一个指纹(如 a1b2c3d4)出现在 3 个不同端点 / 不同主机的请求里
        ↓
结论:这个口令/令牌被重复使用 → 横向移动风险

任务:

  1. 在 FlowAnalyzer 里新增规则 credential-reuse: 统计所有 flow 中出现过的"敏感字段值指纹", 当同一个指纹跨 ≥ 2 个不同端点出现时,报一条 medium 发现, 证据里写"指纹 xxx 出现在 N 个端点"(不写值);
  2. 让靶场在 /login 之外再加一个端点(比如 /api/token), 让它也用同一个口令,这样演示能真的触发这条规则;
  3. 想清楚一个陷阱:如果指纹统计把"每个请求都不一样的会话令牌" 算了进去(每次登录指纹都不同),这条规则就会失效。 你要怎么区分"长期凭据(指纹稳定)"与"一次性令牌(指纹易变)"?

验收标准: