Commit eb9df086 by Archer Committed by GitHub

perf: SSRF check (#6805)

* perf: SSRF check

* doc
parent a5cd8229
......@@ -15,4 +15,5 @@ description: 'FastGPT V4.14.14 更新说明'
## ⚙️ 优化
1. 个人微信发布渠道,优化轮询策略(拉取与回复解耦),避免数据量超大时出现阻塞。
2. 新增环境变量 `WECHAT_CHANNEL_CONCURRENCY`(默认 1000)用于控制微信渠道 poll worker 并发数,建议 ≥ online channel 峰值。
\ No newline at end of file
2. 新增环境变量 `WECHAT_CHANNEL_CONCURRENCY`(默认 1000)用于控制微信渠道 poll worker 并发数,建议 ≥ online channel 峰值。
3. 完善内网地址检测。
\ No newline at end of file
......@@ -230,7 +230,7 @@
"document/content/docs/self-host/upgrading/4-14/41412.mdx": "2026-04-21T23:04:26+08:00",
"document/content/docs/self-host/upgrading/4-14/41413.en.mdx": "2026-04-21T23:04:26+08:00",
"document/content/docs/self-host/upgrading/4-14/41413.mdx": "2026-04-21T23:04:26+08:00",
"document/content/docs/self-host/upgrading/4-14/41414.mdx": "2026-04-22T16:09:29+08:00",
"document/content/docs/self-host/upgrading/4-14/41414.mdx": "2026-04-22T16:29:40+08:00",
"document/content/docs/self-host/upgrading/4-14/4142.en.mdx": "2026-03-03T17:39:47+08:00",
"document/content/docs/self-host/upgrading/4-14/4142.mdx": "2026-03-03T17:39:47+08:00",
"document/content/docs/self-host/upgrading/4-14/4143.en.mdx": "2026-03-03T17:39:47+08:00",
......@@ -393,7 +393,7 @@
"document/content/docs/self-host/upgrading/upgrade-intruction.en.mdx": "2026-03-03T17:39:47+08:00",
"document/content/docs/self-host/upgrading/upgrade-intruction.mdx": "2026-04-20T13:51:34+08:00",
"document/content/docs/toc.en.mdx": "2026-04-21T23:04:26+08:00",
"document/content/docs/toc.mdx": "2026-04-22T16:09:29+08:00",
"document/content/docs/toc.mdx": "2026-04-22T16:29:40+08:00",
"document/content/docs/use-cases/app-cases/dalle3.en.mdx": "2026-02-26T22:14:30+08:00",
"document/content/docs/use-cases/app-cases/dalle3.mdx": "2025-07-23T21:35:03+08:00",
"document/content/docs/use-cases/app-cases/english_essay_correction_bot.en.mdx": "2026-02-26T22:14:30+08:00",
......
......@@ -8,12 +8,12 @@
},
"dependencies": {
"@apidevtools/json-schema-ref-parser": "^11.7.2",
"@mariozechner/pi-agent-core": "^0.67.3",
"@mariozechner/pi-ai": "^0.67.3",
"@fastgpt-sdk/sandbox-adapter": "^0.0.36",
"@fastgpt-sdk/otel": "catalog:",
"@fastgpt-sdk/sandbox-adapter": "^0.0.36",
"@fastgpt-sdk/storage": "catalog:",
"@fastgpt/global": "workspace:*",
"@mariozechner/pi-agent-core": "^0.67.3",
"@mariozechner/pi-ai": "^0.67.3",
"@maxmind/geoip2-node": "^6.3.4",
"@modelcontextprotocol/sdk": "catalog:",
"@node-rs/jieba": "2.0.1",
......@@ -37,12 +37,13 @@
"https-proxy-agent": "^7.0.6",
"iconv-lite": "^0.6.3",
"ioredis": "^5.6.0",
"ipaddr.js": "^2.3.0",
"joplin-turndown-plugin-gfm": "^1.0.12",
"json5": "catalog:",
"jsonrepair": "^3.0.0",
"jszip": "^3.10.1",
"jsonpath-plus": "^10.3.0",
"jsonrepair": "^3.0.0",
"jsonwebtoken": "^9.0.2",
"jszip": "^3.10.1",
"lodash": "catalog:",
"mammoth": "^1.11.0",
"mime": "catalog:",
......
......@@ -345,6 +345,9 @@ importers:
ioredis:
specifier: ^5.6.0
version: 5.6.0
ipaddr.js:
specifier: ^2.3.0
version: 2.3.0
joplin-turndown-plugin-gfm:
specifier: ^1.0.12
version: 1.0.12
......@@ -8141,8 +8144,8 @@ packages:
resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==}
engines: {node: '>= 0.10'}
ipaddr.js@2.2.0:
resolution: {integrity: sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==}
ipaddr.js@2.3.0:
resolution: {integrity: sha512-Zv/pA+ciVFbCSBBjGfaKUya/CcGmUHzTydLMaTwrUUEM2DIEO3iZvueGxmacvmN50fGpGVKeTXpb2LcYQxeVdg==}
engines: {node: '>= 10'}
is-absolute-url@4.0.1:
......@@ -21079,7 +21082,7 @@ snapshots:
ipaddr.js@1.9.1: {}
ipaddr.js@2.2.0: {}
ipaddr.js@2.3.0: {}
is-absolute-url@4.0.1: {}
......@@ -22450,7 +22453,7 @@ snapshots:
buffer-crc32: 1.0.0
eventemitter3: 5.0.1
fast-xml-parser: 5.4.2
ipaddr.js: 2.2.0
ipaddr.js: 2.3.0
lodash: 4.17.23
mime-types: 2.1.35
query-string: 7.1.3
......
......@@ -450,4 +450,83 @@ describe('SSRF Protection - isInternalAddress', () => {
expect(await isInternalAddress('http://dual-stack-public.example.com/')).toBe(false);
});
});
// GHSA-jhqw-944x-xh94: 云元数据端点 SSRF 保护绕过
describe('GHSA-jhqw-944x-xh94 元数据端点绕过防护', () => {
beforeEach(() => {
delete process.env.CHECK_INTERNAL_IP;
});
test('应该阻止显式端口绕过 http://169.254.169.254:80/', async () => {
expect(await isInternalAddress('http://169.254.169.254:80/latest/meta-data/')).toBe(true);
expect(await isInternalAddress('http://169.254.169.254:80/')).toBe(true);
});
test('应该阻止 IPv6-mapped IPv4 绕过 http://[::ffff:a9fe:a9fe]/', async () => {
expect(await isInternalAddress('http://[::ffff:a9fe:a9fe]/latest/meta-data/')).toBe(true);
expect(await isInternalAddress('http://[::ffff:169.254.169.254]/')).toBe(true);
});
test('应该阻止十六进制 IP 绕过 http://0xa9fea9fe/', async () => {
expect(await isInternalAddress('http://0xa9fea9fe/latest/meta-data/')).toBe(true);
expect(await isInternalAddress('http://0xA9FEA9FE/')).toBe(true);
});
test('应该阻止十进制 IP 绕过 http://2852039166/', async () => {
expect(await isInternalAddress('http://2852039166/latest/meta-data/')).toBe(true);
expect(await isInternalAddress('http://2852039166/')).toBe(true);
});
test('应该阻止尾部点绕过 http://169.254.169.254./', async () => {
expect(await isInternalAddress('http://169.254.169.254./latest/meta-data/')).toBe(true);
expect(await isInternalAddress('http://169.254.169.254./')).toBe(true);
});
test('应该阻止 nip.io 通配 DNS 绕过', async () => {
vi.mocked(dns.resolve4).mockResolvedValue(['169.254.169.254']);
vi.mocked(dns.resolve6).mockRejectedValue(new Error('No AAAA records'));
expect(await isInternalAddress('http://169.254.169.254.nip.io/latest/meta-data/')).toBe(true);
});
test('应该阻止链路本地 /16 段内其他 IP', async () => {
expect(await isInternalAddress('http://169.254.1.1/')).toBe(true);
expect(await isInternalAddress('http://169.254.254.254:8080/')).toBe(true);
});
test('应该阻止 Alibaba Cloud 元数据 IP', async () => {
expect(await isInternalAddress('http://100.100.100.200:80/')).toBe(true);
});
test('应该阻止 AWS IPv6 元数据端点', async () => {
expect(await isInternalAddress('http://[fd00:ec2::254]/')).toBe(true);
});
test('应该阻止十进制/十六进制绕过阿里云元数据', async () => {
// 100.100.100.200 -> 0x64646464 -> 1684300900... 实际: 100*256^3+100*256^2+100*256+200
const decimal = 100 * 256 ** 3 + 100 * 256 ** 2 + 100 * 256 + 200;
expect(await isInternalAddress(`http://${decimal}/`)).toBe(true);
expect(await isInternalAddress(`http://0x${decimal.toString(16)}/`)).toBe(true);
});
test('应该阻止元数据主机名的尾部点/大小写变体', async () => {
expect(await isInternalAddress('http://METADATA.google.internal/')).toBe(true);
expect(await isInternalAddress('http://metadata.google.internal./')).toBe(true);
expect(await isInternalAddress('http://kubernetes.default.svc./')).toBe(true);
});
test('域名解析到阿里云元数据 IP 时始终阻止', async () => {
vi.mocked(dns.resolve4).mockResolvedValue(['100.100.100.200']);
vi.mocked(dns.resolve6).mockRejectedValue(new Error('No AAAA records'));
expect(await isInternalAddress('http://rebind.example.com/')).toBe(true);
});
test('域名解析到 AWS 元数据 IP 时始终阻止(无需 CHECK_INTERNAL_IP)', async () => {
vi.mocked(dns.resolve4).mockResolvedValue(['169.254.169.254']);
vi.mocked(dns.resolve6).mockRejectedValue(new Error('No AAAA records'));
expect(await isInternalAddress('http://metadata-proxy.example.com/')).toBe(true);
});
});
});
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment