AgentDepthAgent 工程学习指南
输入概念或问题,检索正文和待编写目录。
第一次学习?从 L0 总览开始 →

SSRF、密钥与 PII 保护

我的学习 →

正在读取学习记录…

资料 Agent 接受一个文档地址,服务端随后准备读取文档。正常业务是访问已批准的资料站点。SSRF 是 Server-Side Request Forgery 的缩写,意思是输入使服务端代替用户向不该访问的位置发请求。Agent 的网页读取工具天然处在这个入口。

密钥是服务用于认证的秘密值;PII 表示可识别个人的信息,例如邮箱、电话号码及与其他数据组合后能识别个人的字段。即使资料读取地址合规,服务也要避免把密钥和无关个人信息送进模型、输出或日志。

先观察一次正常策略判断:https://docs.example.test/guide 是批准的资料域名,固定地址表返回一个公网地址,程序判定地址策略通过。该地址表是本地夹具,后面的操作不会打开网页。

地址检查放在请求之前,数据裁剪放在输出之前

URL 解析将协议、主机、端口和用户凭据拆开;用字符串“包含域名”来判断,容易把子串误认为可信主机。程序只允许 HTTPS、已批准的完整主机名和默认 HTTPS 端口,并拒绝 URL 自带凭据。

DNS 是把主机名转换成地址的系统。一个主机可能同时返回 IPv4、IPv6 或多个地址,所以 check_url 检查固定表中的全部地址;只要存在非公网地址就拒绝。IPv4 的回环地址 127.0.0.1 和 IPv6 的 ::1 都指向本机,无法作为公开资料地址通过。

真正网络客户端还要把检查过的解析结果与实际连接绑定,防止检查之后重新解析到另一地址;HTTPS 的证书和主机校验也要继续执行。每次重定向产生新地址时重新验证或禁用重定向,并通过网络出口策略限制到内网、元数据服务等位置的连接。

边界本地夹具怎样判断接入真实读取工具时的位置
协议与端口HTTPS,443或默认端口URL解析后
完整域名可信集合成员构造请求前
全部解析地址都是公网地址DNS结果与连接绑定处
新跳转地址重新跑相同策略跟随每次重定向前
输出字段保留 title送模型或记录日志前
SSRF、密钥与 PII 保护中输入、检查和结果分支的关系
图1:先检查协议、主机和端口,再检查全部解析地址;公网地址通过,夹带本机地址的解析结果被拒绝。

在空目录保存并运行两个文件

在 PowerShell 输入以下命令创建练习目录,并进入目录。目录创建好后,在编辑器中新建下面两份文件并保存到这个目录。

PowerShell
New-Item -ItemType Directory -Force l8-ssrf
Set-Location l8-ssrf

macOS 终端:

Bash
mkdir -p l8-ssrf
cd l8-ssrf

先保存 ssrf_demo.py。它包含前面解释的全部地址以及正常和边界输入。

Python
"""用固定地址表做 URL 策略判断,不发起网络请求。"""
import ipaddress
from urllib.parse import urlsplit

# 地址表模拟解析结果,包含同一域名返回多个地址的情况。
DNS = {"docs.example.test": ["93.184.216.34"],
       "mixed.example.test": ["93.184.216.34", "127.0.0.1"]}
HOSTS = {"docs.example.test", "mixed.example.test"}
TARGET = "https://docs.example.test/guide"

def check_url(url, dns):
    try:
        parsed = urlsplit(url)
        port = parsed.port
    except ValueError:
        return "拒绝:URL格式"
    if parsed.scheme != "https":
        return "拒绝:协议"
    if parsed.username is not None or parsed.password is not None:
        return "拒绝:URL含凭据"
    if parsed.hostname not in HOSTS or port not in (None, 443):
        return "拒绝:域名或端口"
    addresses = dns.get(parsed.hostname, [])
    if not addresses:
        return "拒绝:无解析地址"
    try:
        # 一个解析地址违规就拒绝,不能只挑第一个公网地址。
        if any(not ipaddress.ip_address(ip).is_global for ip in addresses):
            return "拒绝:非公网地址"
    except ValueError:
        return "拒绝:地址格式"
    return "允许:地址策略通过"

def safe_fields(record):
    # 按字段白名单输出,测试邮箱与测试令牌都留在输入侧。
    return {key: record[key] for key in ("title",) if key in record}

def main():
    print("资料地址 ->", check_url(TARGET, DNS))
    print("混合解析 ->", check_url("https://mixed.example.test/guide", DNS))
    print("最少字段 ->", safe_fields(
        {"title": "接口文档", "email": "reader@example.test", "token": "FAKE_TOKEN"}))

if __name__ == "__main__":
    main()

再保存 test_ssrf.py。测试通过程序的公开函数检查正常路径与拒绝路径;测试文件和主程序放在同一目录,才能按文件名导入。

Python
import unittest
# 固定良性输入检查正常、拒绝和边界路径,不连接外部服务。
from ssrf_demo import DNS, check_url, safe_fields

class AddressTests(unittest.TestCase):
    def test_normal(self):
        self.assertTrue(check_url("https://docs.example.test/guide", DNS).startswith("允许"))

    def test_mixed_addresses(self):
        self.assertEqual(check_url("https://mixed.example.test/", DNS), "拒绝:非公网地址")

    def test_scheme(self):
        self.assertEqual(check_url("file:///guide", DNS), "拒绝:协议")

    def test_credentials(self):
        self.assertEqual(check_url("https://u:p@docs.example.test/", DNS), "拒绝:URL含凭据")

    def test_host_and_port(self):
        for url in ("https://other.example.test/", "https://docs.example.test:8443/"):
            self.assertEqual(check_url(url, DNS), "拒绝:域名或端口")

    def test_dns_boundary(self):
        for addresses in ([], ["::1"], ["bad-ip"]):
            self.assertTrue(check_url("https://docs.example.test/",
                                     {"docs.example.test": addresses}).startswith("拒绝"))

    def test_field_projection(self):
        self.assertEqual(safe_fields({"title": "指南", "token": "FAKE", "email": "a@test"}),
                         {"title": "指南"})

if __name__ == "__main__":
    unittest.main()

返回同一个终端,先列出当前目录内容,确认两个文件名都以 .py 结尾,然后运行主程序。

Bash
ls
python -X utf8 ssrf_demo.py

程序输出:

TXT
资料地址 -> 允许:地址策略通过
混合解析 -> 拒绝:非公网地址
最少字段 -> {'title': '接口文档'}

资料地址通过策略,混合解析由于包含本机地址被拒绝。最后一行只留下标题;测试邮箱和 FAKE_TOKEN 没有进入输出。这是按字段结构选择数据,适合已知记录格式;自由文本中的个人信息还需要相应识别、脱敏策略和样本验证。

运行测试:

Bash
python -X utf8 -m unittest -v test_ssrf.py

末尾应显示 Ran 7 tests 和 OK。若提示找不到模块,先用 dir 核对目录与文件名。

改一处,观察决定怎样变化

把 TARGET = "https://docs.example.test/guide" 改成 TARGET = "https://docs.example.test:8443/guide",运行主程序。第一行变为 资料地址 -> 拒绝:域名或端口,其余两行不变。将端口删除恢复初始地址。

Bash
python -X utf8 ssrf_demo.py

恢复原值后,重新运行测试。

接到生产客户端时容易漏在哪里

现象重点检查修正动作
检查通过,实际连接进入内网是否二次DNS解析将连接绑定到受检地址,核对客户端与代理行为
初始地址正常,跳转后不正常重定向配置禁用自动跳转或逐跳校验
域名看着一样却被拒绝大小写、端口和完整主机名统一解析,按完整主机名维护名单
日志带测试令牌是否记录整包输入白名单选字段,单独管理秘密和调试日志

业务必须访问公司内网时,应给该工具独立的目标服务名单和网络身份,避免与“读取任意公开网页”的工具混用权限。密钥从受控秘密服务注入到真正需要的调用端,模型通常只需工具名称和参数格式。错误堆栈、URL查询参数和代理日志也要检查是否泄漏秘密。

OWASP SSRF 防护指南区分固定目标与任意外部目标,并讨论应用层与网络层检查。该离线程序展示策略分支,真实DNS、TLS、重定向和网络出口行为需在接入的客户端上验证。

课后练习展开习题收起习题

4 道练习 · 客观题规则评分 · 其余题目待批改

正在读取本地记录…

不调用模型,不执行代码。简答、代码和场景题仅保存答案并展示自查标准,不生成评分、徽章或认证。输入内容不会自动保存,请点击提交;不要填写个人信息或密钥。

练习 1 / 4 · v1.0.1

选择题 · 先判断边界

关于“SSRF、密钥与 PII 保护”,哪种做法更稳妥?

对应知识点:L8-ssrf

选择一个答案
练习 2 / 4 · v1.0.0

简答题 · 解释失败

写出一个失败输入并说明记录字段。

对应知识点:L8-ssrf

练习 3 / 4 · v1.0.0

代码题 · 边界测试

补充正常、边界、失败三组输入和预期结果。

对应知识点:L8-ssrf

练习 4 / 4 · v1.0.0

场景题 · 结果未知

证据不足时如何设计状态和人工接管?

对应知识点:L8-ssrf

图片查看器
100%

文章配图 · 按 Esc 关闭