Go SSTI Method Confusion

BY Gus Ralph / ON May 20, 2021

Method Confusion in Go SSTIs lead to file read and RCE.

Although SSTI’s have been thoroughly researched in common web languages such as PHP and Python, I noticed a lack in research towards Go’s builtin html/template module (apart from this writeup, which explains how to achieve XSS).

The documentation for both the html/template module can be found here, and the documentation for the text/template module can be found here, and yes, they do vary, a lot. For example, in text/template, you can directly call any public function with the “call” value, this however, is not the case with html/template.

Testing arena

package main

import (
	"html/template"
	"os/exec"
	"bufio"
	"log"
	"os"
)

type Person string

func (p Person) Secret (test string) string {
	out, _ := exec.Command(test).CombinedOutput()
	return string(out)
}

func (p Person) Label (test string) string {
	return "This is " + string(test)
}

func main(){
	reader := bufio.NewReader(os.Stdin)
	text, _ := reader.ReadString('\n')
	tmpl, err := template.New("").Parse(text)
	if err != nil {
		log.Fatalf("Parse: %v", err)
	}
	tmpl.Execute(os.Stdin,Person("Gus"))
}

Research

While looking into how the template engine works in Go, I noticed you can call any of the properties of the object that you render into the template, for example the below:

type User struct {
	ID       int
	Email    string
	Password string
}

func main() {
	var user1 = &User{1, "test@example.com", "test123"}
	var tmpl = fmt.Sprintf(`Hi {{ .Email }}`)
	t, err := template.New("page").Parse(tmpl)
	if err != nil {
    	fmt.Println(err)
	}
	t.Execute(w, &user1)
}

Since we execute the template with &user1, the {{.Email}} template uses the email attribute of user1, which is defined as test@example.com, this chunk of code will essentially return Hi test@example.com. If we control the content of the template string, we could even add a {{.Password}} to leak the users password, and take over the account.

Upon experimenting with the previously stated functionality, I was wondering if you could equally call a method through a template injection, as long as the method is an attribute of the value passed to the template. This lead me to finding out that you can in fact call methods, and even specify explicit parameters, similar to how you would in a deserialization attack.

This can be demonstrated as seen below:

This works, as the template {{.Secret "id"}} calls the .Secret method using the Person struct, and passes “id” as a parameter. The output to said template is then the output of the command.

Something to take into account with the previously defined source code is that the capital letter in the secret function’s name is not part of the naming convention, it is part of the Go syntax. Exported names (that is, identifiers that can be used from a package other than the one where they are defined) begin with a capital letter. We need this function to be an exported name, due to the fact that upon rendering the template, it is the template package that will be calling the function.

Using this theory, it should be possible to find “gadgets” that allow us to perform arbitrary activies depending on what module is imported, especially if interfaces are used. This has never been researched before from an SSTI perspective, and will allow to take Go SSTI’s much further than an XSS, if succesful.

To find some basic gadgets, I decided to start by looking into a popular web application module, called echo. Essentially what I am looking for, is any method called within the module, that could potentially perform malicious activities.

An example I acquired can be seen below:

func (c *context) File(file string) (err error) {
	f, err := os.Open(file)
	if err != nil {
		return NotFoundHandler(c)
	}
	defer f.Close()

	fi, _ := f.Stat()
	if fi.IsDir() {
		file = filepath.Join(file, indexPage)
		f, err = os.Open(file)
		if err != nil {
			return NotFoundHandler(c)
		}
		defer f.Close()
		if fi, err = f.Stat(); err != nil {
			return
		}
	}
	http.ServeContent(c.Response(), c.Request(), fi.Name(), fi.ModTime(), f)
	return
}

This function essentially takes in a string parameter called “file”, which is the opened and read. This can be easily verified, although a slight modification is required to be made to the script, specifically, execute the template by taking in the Context data type, as File is a method of Context. In my testing environment, c is already type Context, so we can just pass the variable c to the execute function.

This means we can succesfully abuse the gadget to read a local file. This similar tactic can be abused in many situations, as long as the data parameter is the same type as the one that the method uses, you can invoke said method with custom parameters.

Share: