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 更新说明' ...@@ -15,4 +15,5 @@ description: 'FastGPT V4.14.14 更新说明'
## ⚙️ 优化 ## ⚙️ 优化
1. 个人微信发布渠道,优化轮询策略(拉取与回复解耦),避免数据量超大时出现阻塞。 1. 个人微信发布渠道,优化轮询策略(拉取与回复解耦),避免数据量超大时出现阻塞。
2. 新增环境变量 `WECHAT_CHANNEL_CONCURRENCY`(默认 1000)用于控制微信渠道 poll worker 并发数,建议 ≥ online channel 峰值。 2. 新增环境变量 `WECHAT_CHANNEL_CONCURRENCY`(默认 1000)用于控制微信渠道 poll worker 并发数,建议 ≥ online channel 峰值。
\ No newline at end of file 3. 完善内网地址检测。
\ No newline at end of file
...@@ -230,7 +230,7 @@ ...@@ -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/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.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/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.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/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", "document/content/docs/self-host/upgrading/4-14/4143.en.mdx": "2026-03-03T17:39:47+08:00",
...@@ -393,7 +393,7 @@ ...@@ -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.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/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.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.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/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", "document/content/docs/use-cases/app-cases/english_essay_correction_bot.en.mdx": "2026-02-26T22:14:30+08:00",
......
...@@ -8,12 +8,12 @@ ...@@ -8,12 +8,12 @@
}, },
"dependencies": { "dependencies": {
"@apidevtools/json-schema-ref-parser": "^11.7.2", "@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/otel": "catalog:",
"@fastgpt-sdk/sandbox-adapter": "^0.0.36",
"@fastgpt-sdk/storage": "catalog:", "@fastgpt-sdk/storage": "catalog:",
"@fastgpt/global": "workspace:*", "@fastgpt/global": "workspace:*",
"@mariozechner/pi-agent-core": "^0.67.3",
"@mariozechner/pi-ai": "^0.67.3",
"@maxmind/geoip2-node": "^6.3.4", "@maxmind/geoip2-node": "^6.3.4",
"@modelcontextprotocol/sdk": "catalog:", "@modelcontextprotocol/sdk": "catalog:",
"@node-rs/jieba": "2.0.1", "@node-rs/jieba": "2.0.1",
...@@ -37,12 +37,13 @@ ...@@ -37,12 +37,13 @@
"https-proxy-agent": "^7.0.6", "https-proxy-agent": "^7.0.6",
"iconv-lite": "^0.6.3", "iconv-lite": "^0.6.3",
"ioredis": "^5.6.0", "ioredis": "^5.6.0",
"ipaddr.js": "^2.3.0",
"joplin-turndown-plugin-gfm": "^1.0.12", "joplin-turndown-plugin-gfm": "^1.0.12",
"json5": "catalog:", "json5": "catalog:",
"jsonrepair": "^3.0.0",
"jszip": "^3.10.1",
"jsonpath-plus": "^10.3.0", "jsonpath-plus": "^10.3.0",
"jsonrepair": "^3.0.0",
"jsonwebtoken": "^9.0.2", "jsonwebtoken": "^9.0.2",
"jszip": "^3.10.1",
"lodash": "catalog:", "lodash": "catalog:",
"mammoth": "^1.11.0", "mammoth": "^1.11.0",
"mime": "catalog:", "mime": "catalog:",
......
...@@ -345,6 +345,9 @@ importers: ...@@ -345,6 +345,9 @@ importers:
ioredis: ioredis:
specifier: ^5.6.0 specifier: ^5.6.0
version: 5.6.0 version: 5.6.0
ipaddr.js:
specifier: ^2.3.0
version: 2.3.0
joplin-turndown-plugin-gfm: joplin-turndown-plugin-gfm:
specifier: ^1.0.12 specifier: ^1.0.12
version: 1.0.12 version: 1.0.12
...@@ -8141,8 +8144,8 @@ packages: ...@@ -8141,8 +8144,8 @@ packages:
resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==}
engines: {node: '>= 0.10'} engines: {node: '>= 0.10'}
ipaddr.js@2.2.0: ipaddr.js@2.3.0:
resolution: {integrity: sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==} resolution: {integrity: sha512-Zv/pA+ciVFbCSBBjGfaKUya/CcGmUHzTydLMaTwrUUEM2DIEO3iZvueGxmacvmN50fGpGVKeTXpb2LcYQxeVdg==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
is-absolute-url@4.0.1: is-absolute-url@4.0.1:
...@@ -21079,7 +21082,7 @@ snapshots: ...@@ -21079,7 +21082,7 @@ snapshots:
ipaddr.js@1.9.1: {} ipaddr.js@1.9.1: {}
ipaddr.js@2.2.0: {} ipaddr.js@2.3.0: {}
is-absolute-url@4.0.1: {} is-absolute-url@4.0.1: {}
...@@ -22450,7 +22453,7 @@ snapshots: ...@@ -22450,7 +22453,7 @@ snapshots:
buffer-crc32: 1.0.0 buffer-crc32: 1.0.0
eventemitter3: 5.0.1 eventemitter3: 5.0.1
fast-xml-parser: 5.4.2 fast-xml-parser: 5.4.2
ipaddr.js: 2.2.0 ipaddr.js: 2.3.0
lodash: 4.17.23 lodash: 4.17.23
mime-types: 2.1.35 mime-types: 2.1.35
query-string: 7.1.3 query-string: 7.1.3
......
...@@ -450,4 +450,83 @@ describe('SSRF Protection - isInternalAddress', () => { ...@@ -450,4 +450,83 @@ describe('SSRF Protection - isInternalAddress', () => {
expect(await isInternalAddress('http://dual-stack-public.example.com/')).toBe(false); 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