Cross-site scripting (XSS)

Cross-site scripting (XSS)

Description

Concatenating user input into JavaScript code can let an attacker run scripts in the page. template.JS marks a value as trusted JavaScript; it does not validate or escape the input.

Potential impact

  • An attacker may read sensitive data accessible to the page or send requests with the user's privileges.
  • They may alter the page or collect information through a fake form.

Remediation

  • Do not convert user input to template.JS or concatenate it into JavaScript code.
  • Pass ordinary strings to html/template for context-aware escaping.
  • As in the example below, pass values through data attributes and read them in JavaScript after creating the DOM element.

Examples

Before

go
package main

import (
    "html/template"
    "net/http"
)

// This example demonstrates the vulnerable pattern.

func UnsafeJS(w http.ResponseWriter, r *http.Request) {
    // User input from the id query parameter
    userID := r.URL.Query().Get("id")

    // Vulnerable: concatenate user input directly into HTML/JS
    // Converting to template.JS does not validate or escape the input
    js := "var msg = '<h1>" + userID + "</h1>'; document.write(msg);"

    // ruleid: unescaped-data-in-js
    safeLike := template.JS(js) // The value is still unsafe

    w.Header().Set("Content-Type", "text/html; charset=utf-8")
    _, _ = w.Write([]byte("<script>" + string(safeLike) + "</script>"))
}

After

go
package main

import (
    "html/template"
    "net/http"
)

// The template escapes data inserted at {{.UserID}}
var pageTmpl = template.Must(template.New("page").Parse(`
<!DOCTYPE html>
<html>
<head><meta charset="utf-8"><title>Profile</title></head>
<body>
  <h1>User: {{.UserID}}</h1>
  <div id="user" data-userid="{{.UserID}}"></div>
  <script>
    // Pass data through data-* attributes or appropriate JSON encoding
    const userId = document.querySelector('#user').dataset.userid;
    console.log('User:', userId);
  </script>
</body>
</html>
`))

type PageData struct {
    UserID string
}

func SafeJS(w http.ResponseWriter, r *http.Request) {
    userID := r.URL.Query().Get("id")

    // Validate length and allowed characters on the server as needed
    // if !validUserID(userID) { ... }

    data := PageData{UserID: userID}

    w.Header().Set("Content-Type", "text/html; charset=utf-8")
    // html/template escapes values for their output context
    _ = pageTmpl.Execute(w, data)
}

Explanation: Before, the response directly contains a script built from user input. After, UserID is rendered as template text and an attribute value, and JavaScript reads it from an existing element. Do not then interpret the value as HTML or code.

References