Overview
CVE-2026-33715 describes a broken-authentication and access-control flaw in Chamilo LMS where an unauthenticated AJAX endpoint (public/main/inc/ajax/install.ajax.php) could be reached on a fully installed instance. This endpoint accepts an arbitrary SMTP DSN from POST data and connects to an attacker-specified SMTP server, enabling Server-Side Request Forgery (SSRF) into internal networks via the SMTP protocol. The issue could also weaponize the Chamilo server as an open email relay for phishing and spam campaigns, with emails potentially appearing to originate from the server’s IP. Error responses from failed SMTP connections could reveal internal topology or service details. The root problem is poor authentication checks and improper segregation of sensitive administrative paths. It was fixed in version 2.0.0-RC.3. This pattern maps to CWE-306 (Missing Authentication for Critical Function) and CWE-918 (Server-Side CSRF/SSRF-like exposure via improperly secured endpoints). In Go with Gin, analogous vulnerabilities arise when endpoints performing sensitive actions are left unauthenticated or when clients can supply critical configuration (like an SMTP DSN) that the server uses to contact internal resources. The remediation is to enforce strong authentication on sensitive routes, avoid using client-provided DSNs for outbound connections, and validate all inputs with strict allowlists and server-side configuration.
Code Fix Example
Go (Gin) API Security Remediation
package main
import (
"net/http"
"net/smtp"
"os"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
// Vulnerable pattern: unauthenticated endpoint that uses a client-provided DSN to dial SMTP
r.POST("/install/ajax/test_mailer", func(c *gin.Context) {
dsn := c.PostForm("dsn")
if dsn == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "dsn is required"})
return
}
// Untrusted input: DSN used directly to connect to an SMTP server
client, err := smtp.Dial(dsn)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
// Minimal operation to demonstrate usage; not a production mail flow
_ = client.Close()
c.JSON(http.StatusOK, gin.H{"status": "connected to SMTP", "dsn": dsn})
})
// Fixed pattern: require authentication and do not use client-provided DSN
// All outbound SMTP connections must use a server-configured DSN
authorize := func(c *gin.Context) {
if c.GetHeader("X-API-Key") != "secret-key" {
c.AbortWithStatus(http.StatusUnauthorized)
return
}
c.Next()
}
r.POST("/install/ajax/test_mailer_fix", authorize, func(c *gin.Context) {
smtpDSN := os.Getenv("SMTP_DSN") // configured on the server, not from the client
if smtpDSN == "" {
c.JSON(http.StatusInternalServerError, gin.H{"error": "SMTP DSN not configured on server"})
return
}
client, err := smtp.Dial(smtpDSN)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
_ = client.Close()
c.JSON(http.StatusOK, gin.H{"status": "connected to server SMTP"})
})
r.Run(":8080")
}