Server Side Template Injection in Gorilla Mux with Jwt Tokens
Server Side Template Injection in Gorilla Mux with Jwt Tokens — how this specific combination creates or exposes the vulnerability
Server Side Template Injection (SSTI) occurs when an attacker can inject template directives that are later evaluated by a server-side templating engine. In a Gorilla Mux application that uses JWT tokens for authentication, the combination of dynamic route variables, middleware-based token parsing, and improperly sanitized user input can expose template injection points.
Gorilla Mux is a URL router and dispatcher for Go. When routes include parameters (e.g., /profile/{username}), developers often extract these values and pass them into a template execution context. If the template engine is configured to parse user-controlled data (such as query parameters, headers, or request bodies) and that data reaches the template execution layer, an attacker can supply payloads that cause unintended behavior. JWT tokens are commonly parsed in middleware, and claims within the token (such as username, email, or roles) are often forwarded into template rendering without strict validation or escaping.
Consider a handler that decodes a JWT token, extracts a claim, and passes it to a Go html/template instance without proper context-aware escaping. A crafted JWT claim like {{define "attack"}}{{end}}{{template "attack"}} could lead to template redefinition or unexpected execution depending on the template engine behavior. While Go’s html/template package is designed to be safe by default, it can be bypassed when developers use custom delimiters or invoke functions that evaluate strings as templates (e.g., template.New("t").Parse(userInput)). When JWT data is used to construct dynamic template names, loop over user-controlled slices, or inject into JavaScript blocks, the attack surface expands.
The risk is compounded when developers rely on JWT scopes or roles to conditionally render parts of a template. If an attacker modifies a low-privilege JWT to include administrative claims and the template uses those claims to decide whether to include sensitive sections, SSTI can be leveraged to alter control flow or inject executable logic. Because Gorilla Mux does not inherently sanitize route parameters or claims, the developer must ensure that any user-influenced data reaching the template layer is validated, contextualized, and escaped according to the templating engine’s safety rules.
In practice, an automated scan from middleBrick can detect patterns where JWT claims are passed into template execution contexts without adequate sanitization. The scanner checks for risky function calls, dynamic template construction, and missing output encoding, highlighting specific code paths where SSTI may be feasible even when JWT parsing appears isolated.
Jwt Tokens-Specific Remediation in Gorilla Mux — concrete code fixes
To mitigate SSTI in a Gorilla Mux application that uses JWT tokens, apply strict input validation, context-aware escaping, and separation between authentication data and template logic. Below are concrete code examples demonstrating secure patterns.
1. Avoid passing JWT claims directly into templates
Instead of injecting raw claims, map only the necessary, pre-validated values to the template data structure. Use strong types and avoid interface{} where possible.
type TemplateData struct {
Username string
Role string
}
func profileHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
tokenString := r.Header.Get("Authorization")
if tokenString == "" {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
return []byte("your-secret-key"), nil
})
if err != nil || !token.Valid {
http.Error(w, "Invalid token", http.StatusUnauthorized)
return
}
claims, ok := token.Claims.(jwt.MapClaims)
if !ok {
http.Error(w, "Invalid claims", http.StatusUnauthorized)
return
}
data := TemplateData{
Username: claims["username"].(string),
Role: claims["role"].(string),
}
tmpl, _ := template.New("profile").Parse(profileTemplate)
tmpl.Execute(w, data)
}
2. Use context-aware escaping and restrict delimiters
Ensure templates are parsed with the correct delimiters and that any dynamic content is escaped for the target context (HTML, JS, URLs). Do not change delimiters to user-controlled values.
const profileTemplate = `
<h1>Welcome, {{.Username}}</h1>
<p>Role: {{.Role}}</p>
`
// The template package automatically escapes HTML when using {{.field}}.
3. Validate and sanitize claims before use
Apply allowlists to claims used in templates. For example, validate role values against a known set.
validRoles := map[string]bool{"user": true, "admin": true}
role, ok := claims["role"].(string)
if !ok || !validRoles[role] {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
// Only then include role in template data
4. Do not construct templates from user input
Never call template.New().Parse() with data derived from JWT claims or any user input. Pre-define templates at initialization.
var staticTmpl = template.Must(template.New("static").Parse(`
<div>{{.Content}}</div>
`))
func contentHandler(w http.ResponseWriter, r *http.Request) {
staticTmpl.Execute(w, struct{ Content string }{Content: "Safe static content"})
}
5. Use middleware to enforce claim integrity
Validate and normalize JWT claims before they reach handlers, and store only safe, transformed values in request context.
func jwtMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
tokenString := r.Header.Get("Authorization")
token, err := validateToken(tokenString)
if err != nil {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
claims := token.Claims.(jwt.MapClaims)
ctx := context.WithValue(r.Context(), "user", map[string]string{
"username": claims["username"].(string),
"role": sanitizeRole(claims["role"]),
})
next.ServeHTTP(w, r.WithContext(ctx))
})
}
func sanitizeRole(raw interface{}) string {
role, _ := raw.(string)
if role == "admin" || role == "user" {
return role
}
return "user"
}