DeepSeek Harness(DSH)的插件开发中,经常涉及从外部拉取资源(图片、网页、音频等)。如果这些 URL 源于用户输入或模型输出,就构成了 SSRF(服务器端请求伪造)的攻击面。攻击者可能利用此漏洞访问内网服务(如 http://127.0.0.1:5099/api)或云厂商元数据接口(如 http://169.254.169.254/latest/meta-data/iam/security-credentials/)。
dsh-netguard 是一个 DSH 插件,旨在作为“出网护栏”。它通过一个 netguard 服务和一个可选的 fetch_url 工具,统一管理所有出网请求的地址合法性、大小与超时,避免因判定逻辑写错而导致的内网暴露。
核心功能¶
该插件提供以下能力:
- SSRF 防护:只允许访问公网地址,拦截私网、内网及特殊用途地址(如
127.0.0.1、169.254.169.254等)。 - DNS 解析检查:对 DNS 解析出的每一条地址都进行合法性判定,防止域名解析到内网。
- 重定向逐跳复查:对 HTTP 重定向的每一跳地址重新进行体检,防止通过
302位置头指向内网。 - 流式大小限制:边读边计数,超过
maxBytes立即断开连接,防止内存耗尽攻击。 - 超时控制:整条请求链路共用一个超时预算,防止长时间卡顿或跳转拖慢整体性能。
- 端口黑名单:拦截常见内网服务端口(如 22, 23, 25, 6379, 27017)。
- IPv4/IPv6 地址分类:支持 IPv4 的各种变形写法(如
0x7f.1),IPv6 仅允许2000::/3。 - 工具支持:提供
fetch_url工具(默认关闭),供模型调用获取公网内容。
安装与配置¶
安装¶
在 DSH 项目目录下执行:
dsh plugin --profile web add github:JackZo400/dsh-netguard
配置¶
在插件配置文件中添加 netguard 服务实例。以下为常用参数示例:
- insert:
- id: netguard
name: dsh-netguard
config:
allowedProtocols: ['http:', 'https:'] # 仅允许 HTTP/HTTPS
maxBytes: 8388608 # 一次最多下载 8 MiB
onOverflow: error # 超限报错;若需截断请设为 'truncate'
timeoutMs: 20000 # 整条链路共用超时时间
allowRedirects: true # 允许重定向,每跳都会重新体检
maxRedirects: 3
blockedPorts: [22, 23, 25, 6379, 11211, 27017] # 内网常见端口
allowUrlCredentials: false # 禁止 URL 中带 user:pass
allowSingleLabel: false # 禁止单段主机名(如 http://intranet)
userAgent: dsh-netguard/0.1.0
enableTool: false # 是否开启 fetch_url 工具
典型用法¶
给其他插件使用¶
获取公网字节(适合下载图片或音频):
const netguard = ctx.get('netguard')
const bytes = await netguard.fetchPublicBytes(imageUrl, {
maxBytes: 8 * 1024 * 1024,
timeoutMs: 20_000
})
获取完整响应信息(含状态码、最终地址、跳转次数等):
const r = await netguard.fetchPublic(url, {
maxBytes: 64 * 1024,
onOverflow: 'truncate'
})
// 返回值包含 finalUrl, status, bytes, truncated, redirects 等字段
仅体检 URL 合法性,不下载:
const u = await netguard.assertPublicUrl(url) // 不安全会抛出 NetguardError
主机名与地址判定(可单独使用):
// 同步检查,不查 DNS,只挡明显写法
netguard.isPublicHost('127.0.0.1') // false
// 异步检查,查 DNS
await netguard.checkHost('demo.example.com') // 返回 { ok, addresses, offender? }
// IP 分类
netguard.classifyIp('::ffff:10.0.0.1') // 返回 { isIp:true, public:false, reason:'PRIVATE' }
给模型使用(默认关闭)¶
若需允许模型调用外部接口获取内容,需在配置中开启工具:
config:
enableTool: true
开启后,模型可使用 fetch_url 工具:
抓一下 https://example.com/demo.html 里写了什么
纯函数逻辑¶
如果不需要 DSH 环境,可直接引入插件提供的纯函数逻辑:
import { isPublicIp, classifyIp } from 'dsh-netguard/src/ip.js'
import { assertPublicUrl } from 'dsh-netguard/src/guard.js'
注意事项¶
插件虽然提供全面的地址检查,但在以下场景下存在局限:
- DNS 重绑定(TOCTOU)攻击:插件在解析阶段和连接阶段可能使用不同的 DNS 结果(如 TTL=0 的域名),攻击者可能利用此时间差绕过检测。
- 代理判定时机:插件的地址判定发生在代理配置之前,无法感知通过代理访问的实际地址。
- 内容安全:插件只验证“能否去”和“拿多大”,不验证返回内容本身的安全性(如 HTML 中的恶意指令)。
- 特殊跳转:不支持
<meta http-equiv="refresh">或 JavaScript 跳转,仅处理 HTTP 重定向。 - 协议支持:不支持上传、POST 请求及流式长连接。
- DNSSEC:不执行 DNSSEC 验证,依赖系统解析器。
- 公网反代:攻击者若在公网服务器上搭建反向代理指向内网,地址层面无法检测。
- 端口黑名单:黑名单为近似值,非完整列表(如
8081,9090等也可能有内网服务)。 - IPv6 限制:仅允许
2000::/3,极少数合法的特殊 IPv6 写法可能会被误拦截。 - 无速率限制:插件不限制请求的频率或并发数,需在外部自行添加配额控制。
总结¶
dsh-netguard 通过严格的地址分类、逐跳重定向检查和流式限流,为 DSH 插件提供了一个坚实的出网安全层。它解决了传统正则匹配无法覆盖的变形 IP、DNS 解析到内网以及重定向劫持等常见 SSRF 漏洞。开发者应根据实际业务需求调整 maxBytes、timeoutMs 和 blockedPorts 参数。
插件目录:SkillHub 插件目录
GitHub 仓库:JackZo400/dsh-netguard