Server Side Template Injection in Gin with Basic Auth
Server Side Template Injection in Gin with Basic Auth — how this specific combination creates or exposes the vulnerability
Server Side Template Injection (SSTI) in the Gin web framework occurs when user-controlled data is passed into a template and executed as code. Gin uses the html/template package (or third-party engines like gin-contrib/html) which provide automatic escaping for HTML contexts by default. However, if a developer uses the .SafeHTML method, disables escaping, or calls functions like template.HTML on attacker-influenced input, the template engine can evaluate malicious content. When Basic Auth is used without additional authorization checks, unauthenticated or low-privilege users can still reach the vulnerable endpoint, which broadens the attack surface.
In a typical Gin route, a developer might pass query parameters or JSON fields into a template rendering function. For example, a debug or status endpoint that includes a user-supplied name parameter in the template can become exploitable:
// Vulnerable example: user input is directly injected into the template
type PageData struct {
Name string
}
g.GET("/profile", func(c *gin.Context) {
name := c.Query("name")
c.HTML(http.StatusOK, "profile", gin.H{
"Name": name,
})
})
If the template profile.tmpl uses {{ .Name | safe }} or {{ .Name | printf ", an attacker can inject template directives. In combination with Basic Auth, where credentials are only checked at the handler level and do not restrict template rendering, an authenticated session can be abused to execute arbitrary template logic. This may lead to reading local files via {{ (index . "$nil").Files "/" }} or invoking functions mapped into the template’s function map, enabling further exploitation. The risk is elevated when Basic Auth is used for coarse-grained access control while template-level controls are missing.
Gin does not automatically protect against SSTI simply because Basic Auth validates credentials. Attack patterns like CVE-2021-26121 (Atlassian Confluence SSTI) illustrate how template injection can lead to remote code execution or sensitive data exposure. middleBrick checks for these issues by correlating OpenAPI/Swagger spec definitions with runtime behavior, identifying unsafe template usage and overly permissive auth configurations.
Basic Auth-Specific Remediation in Gin — concrete code fixes
Remediation focuses on preventing untrusted data from reaching the template layer and tightening access controls. Avoid marking user input as safe, and use context-aware escaping. Prefer structured data output (e.g., JSON) for APIs, or ensure templates escape by default.
1. Use strict escaping and avoid SafeHTML
Never apply template.HTML or custom safe functions to user input. Instead, let the template engine handle escaping:
// Safe approach: rely on default escaping
g.GET("/profile-safe", func(c *gin.Context) {
name := c.Query("name")
c.HTML(http.StatusOK, "profile", gin.H{
"Name": name, // auto-escaped in html/template
})
})
2. Validate and sanitize input before use
Allowlist acceptable values and reject unexpected patterns. If you must include input in templates, sanitize it to a safe subset:
import "regexp"
var safeName = regexp.MustCompile(`^[a-zA-Z0-9_\-\s]+$`)
g.GET("/profile-validated", func(c *gin.Context) {
name := c.Query("name")
if !safeName.MatchString(name) {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "invalid name"})
return
}
c.HTML(http.StatusOK, "profile", gin.H{
"Name": name,
})
})
3. Combine Basic Auth with role-based authorization at the route or handler level
Basic Auth provides authentication but not fine-grained authorization. Use middleware to enforce scopes or roles before rendering sensitive templates:
func BasicAuth() gin.HandlerFunc {
return func(c *gin.Context) {
user, pass, ok := c.Request.BasicAuth()
if !ok || !checkCredentials(user, pass) {
c.Header("WWW-Authenticate", `Basic realm="restricted"`)
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "auth required"})
return
}
// Attach user identity for downstream handlers
c.Set("user", user)
c.Next()
}
}
func RequireRole(role string) gin.HandlerFunc {
return func(c *gin.Context) {
user, _ := c.Get("user")
if !hasRole(user.(string), role) {
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"error": "insufficient permissions"})
return
}
c.Next()
}
}
// Usage
g.GET("/admin", BasicAuth(), RequireRole("admin"), func(c *gin.Context) {
// Only authenticated users with role "admin" reach here
c.JSON(http.StatusOK, gin.H{"status": "admin area"})
})
4. Use JSON APIs instead of server-side templates for public endpoints
For endpoints consumed by web or mobile clients, prefer JSON responses. This removes template injection risk entirely and aligns with modern API practices:
g.GET("/api/status", BasicAuth(), func(c *gin.Context) {
name := c.Query("name")
// Validate input as needed
c.JSON(http.StatusOK, gin.H{
"message": "Hello, " + name,
})
})
By default, encoding/json escapes HTML characters, preventing injection into downstream consumers. Combine this with middleware that enforces Basic Auth and scope checks to maintain a secure, minimal attack surface.