Golang is a new open source programming language that is growing in popularity. Since I am getting bored of Python, I decided to begin studying it. While I'm really enjoying it as a language, I was completely caught off guard when I started reading about Golang's built in HTML templating package. I noticed in their documentation they are doing context based encoding. Not only that, it is all done automatically. No explicit calls to encodeJS or htmlentities or any of that other stuff we as security professionals commonly recommend our customers to use.
Context Aware XSS is something I as a researcher have been heavily involved in for our own Dynamic scanner. While we have been using context awareness in attacking applications for some time now, I never once saw a solid implementation for defense. Needless to say, I was pleasantly surprised to find right in their documentation a section dedicated to encoding input for various HTML contexts.
The built in templating features automatically encode by intelligently parsing the template and encoding dynamic values depending on where with in the template the output is to be rendered. I figured this was interesting enough to write up a quick blog post on because I don't think enough attention has been given to this feature. Also, I feel anyone else writing a template engine should take a serious look at how Golang is doing it.
Example Code
These examples assume you have some understanding of Golang and how it all works, if you don't know what Golang is, you should definitely check out their online Tour. Please keep in mind I myself am quite new to Golang and it is wildly different than most languages I have used. If you spot a bug, please let me know so I can fix it!
If you are not interested in the code itself, feel free to skip down to the results section to see how Golang's templates encode data.
server.go
Below is a very simple .go file that gets our current working directory then calls our handlers module to map various paths to specific HTTP handler functions. Then it simply starts up a server on port 8080.
package main
import (
"fmt"
"github.com/wirepair/web_examples/web_templates/handlers"
"net/http"
"os"
"path/filepath"
)
var templatesPath = "templates"
// our init function to get our templates path depending on where we are.
func init() {
dir, _ := os.Getwd() // gives us the source path if we haven't installed.
templatesPath = filepath.Join(dir, templatesPath)
}
// calls our handlers and starts our server.
func main() {
if err := handlers.ServerFunc(templatesPath); err != nil {
fmt.Println("Error starting server!")
os.Exit(1)
}
http.ListenAndServe(":8080", nil) // listen on all interfaces on port 8080
}
templates/root.html
Below is our static HTML page containing a form with no template substitution or place holders.
<!DOCTYPE html>
<head>
<title>Go Template Examples</title>
<script>
window.addEventListener("load", function () {
var submitter = document.getElementById("submit");
submitter.addEventListener("click", function() {
var form = document.getElementById("boop");
form.submit();
});
});
</script>
<style>
background: #808080
</style>
</head>
<body>
<form id="boop" method="POST" action="//www.veracode.com/display">
<label for="tag">Tag Injection:
<input name="tag" type="text" value=""></label><br>
<label for="script">Script Injection:
<input name="script" type="text" value=""></label><br>
<label for="attr_double">Double Quote Attribute Injection:
<input name="attr_double" type="text" value=""></label><br>
<label for="attr_single">Single Quote Attribute Injection:
<input name="attr_single" type="text" value=""></label><br>
<label for="attr_onevent">OnEvent Attribute Injection:
<input name="attr_onevent" type="text" value=""></label><br>
<label for="attr_src">Source Attribute Injection:
<input name="attr_src" type="text" value=""></label><br>
<label for="style">CSS/Style Injection:
<input name="style" type="text" value=""></label><br>
<button id="submit">Submit</button>
</form>
</body>
</html>
handlers/handlers.go
package handlers
import (
"fmt"
"html/template"
"net/http"
"net/url"
"os"
"path/filepath"
)
// our user context input type
type UserContextInput struct {
Tag string
Script string
Style string
AttributeLink string
AttributeDouble string
AttributeSingle string
AttributeOnEvent string
AttributeSrc string
}
var templates string // package scoped var to hold templates path
// parses the form values into our custom structure.
func getFormValues(userInput *url.Values) (contextInput *UserContextInput, err error) {
// create a new UserContextInput struct to hold user input
contextInput = new(UserContextInput)
// iterate over the form values assigning each one to our struct.
for key, value := range *userInput {
switch key {
case "tag":
contextInput.Tag = value[0]
case "script":
contextInput.Script = value[0]
case "style":
contextInput.Style = value[0]
case "attr_double":
contextInput.AttributeDouble = value[0]
case "attr_single":
contextInput.AttributeSingle = value[0]
case "attr_onevent":
contextInput.AttributeOnEvent = value[0]
case "attr_src":
contextInput.AttributeSrc = value[0]
default:
return nil, fmt.Errorf("Unknown value passed in form!") // no funny business.
}
}
return contextInput, nil // yes, it is safe to return a pointer in Go
}
// !!! 注意 DO NOT DO THIS (Disables XSS protection in IE/Chrome) 注意 !!!
func disableXSSProtection(function func(http.ResponseWriter,
*http.Request)) func(http.ResponseWriter, *http.Request) {
// return a closure wrapping our response with our header added
return func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("X-XSS-Protection", "0")
function(w, r)
}
}
// our function to map handlers to URLs, also sets package var for our template path.
func ServerFunc(dir string) (err error) {
templates = dir
if _, err := os.Stat(templates); err != nil {
return fmt.Errorf("templates path is incorrect!")
}
// map / to our rootHandler
http.HandleFunc("/", disableXSSProtection(rootHandler))
// map /display to our postHandler
http.HandleFunc("/display", disableXSSProtection(postHandler))
return nil
}
// handles everything in / except what is mapped in ServerFunc.
func rootHandler(w http.ResponseWriter, r *http.Request) {
// Parse our root.html template
if t, err := template.ParseFiles(filepath.Join(templates, "root.html")); err != nil {
// Something gnarly happened.
http.Error(w, err.Error(), http.StatusInternalServerError)
} else {
// return to client via t.Execute
t.Execute(w, nil)
}
}
func postHandler(w http.ResponseWriter, r *http.Request) {
// parse our form results page which injects user input.
if t, err := template.ParseFiles(filepath.Join(templates, "form_results.html")); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
} else {
// make sure they parse correctly
if err := r.ParseForm(); err != nil {
http.Error(w, "Error parsing form values!", http.StatusInternalServerError)
return
}
// get the form values
userInput, err := getFormValues(&r.Form)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// inject user input with our implicit context aware encoding thanks to Go.
t.Execute(w, userInput)
}
}
This is where the magic happens. We define a structure that will hold user input that will take in the POST parameters from the initial html form. The ServerFunc function maps all URIs under the root "/" to a single function, rootHandler. The "/display" URI is mapped to a function that takes in the user input from the form and displays the values in a separate template. You may notice the disableXssProtection wrapper, this is used to disable Chrome and Internet Explorer's XSS protections. Obviously, it is NOT recommended you ever do this in a real site!
The postHandler function is of most interest to us since this is where the user interaction actually takes place. First we read in the template we are going to use for displaying the results. Next, we parse out the parameter names (keys) and their values (values) using the getFormValues function. If successful, we pass our structure holding the input (no validation done) to the template's Execute function. This will simply replace the template place holders with the values of our structure.
templates/form_results.html
This is the template that will display the user's input. Notice that the .[Fieldname] will be dynamically replaced with safe encoding of the values we passed to it using the template.Execute() function.
<!DOCTYPE html>
<head>
<title>Go Template Examples</title>
<script>
window.addEventListener("load", function () {
var test_dq = "{{.Script}}";
var test_sq = '{{.Script}}';
if {{.Script}} != null {
var test = {{.Script}};
}
document.{{.Script}};
});
function horray(input) {
if (input != '') {
alert("horray:" + input);
}
}
</script>
<style>
background: #808080 url('{{.Style}}')
</style>
</head>
<body>
<span>
<div>Tag: </div>
<div>{{.Tag}}</div>
</span>
<span>
<div>Link Attribute:</div>
<a href="//www.veracode.com/%7B%7B.AttributeLink%7D%7D">stuff</a>
</span>
<span>
<div>Double Quote Attribute:</div>
<div id="{{.AttributeDouble}}">stuff</div>
</span>
<span>
<div>Single Quote Attribute:</div>
<div id='{{.AttributeSingle}}'>stuff</div>
</span>
<span>
<div>OnEvent Attribute (onmouseover):</div>
<a href="https://veracode.com/" onmouseover="horray('{{.AttributeOnEvent}}');">stuff</a>
</span>
<span>
<div>Src Attribute:</div>
<iframe src="//www.veracode.com/%7B%7B.AttributeSrc%7D%7D"></iframe>
</span>
</body>
</html>
You probably noticed at no time did we do any sort of encoding on the values prior to passing them to the template function. In most templating languages this would ultimately lead to disaster. So what does the output look like?
Results
Obviously to test we are going to attempt to inject various Cross-Site Scripting attack strings and attempt to execute our own code. But first let's add some safe values to see what "normal" output should look like.
Nothing special here just non malicious input values.
As you can see our values are put into their proper positions within the HTML document. No special characters were inserted so no encoding needs to take place.
Here we are attempting:
- Tag injection: Using the favorite alert box wrapped inside a script tag element.
- Script Injection: Attempting both single quote and double quote break outs to escape our encased string characters, followed by alert.
- Double Quote Attribute Injection: Attempting to break out of the attribute wrapped in double quotes.
- Single Quote Attribute Injection: Attempting to break out of the attribute wrapped in single quotes.
- OnEvent Attribute Injection: Here we attempt to break out of horray function which wraps input in single quotes. We attempt both a single quote and the encoded html entity of a single quote to demonstrate it can handle both. If you weren't aware most browsers will helpfully decode encoded entities that exist in onevent attributes potentially allowing XSS!
- Source Attribute Injection: Attempting to inject a JavaScript protocol handler with an alert function.
- CSS/Style Injection: Attempting to escape out of the URL field and inject additional CSS directives.
As you can see in each case Golang properly encodes every one of our attack attempts into safe values. You may notice in the source attribute injection that the value was replaced with #ZgotmplZ, this is inserted when an unsafe value is encountered. For all of this encoding to occur, all that was required of us was to use the Execute function from html/template. No further processing or encoding was necessary on our part.
Conclusion
So that's it! XSS is dead! Well no, not exactly. While I honestly have not spent too much time seeing if I could break out of any of these encodings, it is on my TODO list. I suspect as more and more people become aware of this feature, it will be added to their TODO list as well. It should also be noted it is possible to make yourself vulnerable, especially if you wrap user input in the HTML() function prior to passing it to a template. As always, see their documentation for more details.