Understanding Server-Side Template Injection in Golang
A not-so-common security vulnerability among developers is Server-Side Template Injection (SSTI). At the same time, it can open the door to escalating security risks such as file inclusion, Cross-Site Scripting (XSS), or even Code Injection Attacks.
When developers use Golang for full-stack web development, handling both the backend and frontend, they are more susceptible to making mistakes with insecure code that may lead to SSTI.
In this article, you’ll learn a simple web application in Golang with several security vulnerabilities and insecure code patterns that I hope you’ll catch. We will then deep-dive into the specific case of SSTI to understand how attackers can exploit this vulnerability and how Golang’s built-in html/template
library is used in that context.
Building a web application in Golang
Let’s start by building part-by-part the building blocks of a small web application in Golang. We will be making use of the following components:
The Gin web framework for Golang uses the open source Go library
github.com/gin-gonic/gin
.The SQLite 3 database uses the open source Go library
database/sql
and the SQLite database driver "github.com/mattn/go-sqlite3".The built-in
html/template
Go library to compose HTML responses using a declared template.
Declaring Golang project dependencies
To begin with, our web application, denoted via app.go
, starts by declaring the imported dependencies to our project:
package main
import (
"database/sql"
"fmt"
"html/template"
"log"
"net/http"
"github.com/gin-gonic/gin"
_ "github.com/mattn/go-sqlite3"
)
If you’re unfamiliar with Golang syntax, these are names or URLs to import dependencies. Specifically, the SQLite3 driver uses the underscore _ syntax to specify that the imported package isn’t explicitly used in the code but is needed by indirect code that relies on this being present in our application’s code.
Golang web router and function handlers
Next, let’s draw a high-level picture of how we will organize the code for this simple web application. Following are the functions in use from a bird’s eye view:
package main
import (...)
func search(c *gin.Context) {...}
func main() {
r := gin.Default()
r.GET("/search", search)
r.Run(":8080")
}
In the above code snippet, we declare the main()
entry point function for the Golang program. It defines a new router using the Gin web framework, followed by an HTTP GET endpoint at /search
, which calls the search function we defined. Finally, the web application is run to handle requests at port 8080.
Displaying database results
This is the part where we extract the query string from the URL to parse the user’s search query, use it to query the database records, extract them, format them into an HTML template, and send that as a response.
Note:
The following code snippet is riddled with security vulnerabilities and is only intended for educational purposes. You should not copy-paste or use it beyond a learning exercise.
The complete code for our search
function handler is as follows:
func search(c *gin.Context) {
query := c.Query("q")
db, err := sql.Open("sqlite3", "posts.db")
if err != nil {
log.Fatal(err)
}
defer db.Close()
rows, err := db.Query(fmt.Sprintf("SELECT * FROM posts WHERE title LIKE '%%%s%%'", query))
if err != nil {
c.String(http.StatusInternalServerError, "Error executing query")
return
}
defer rows.Close()
tmpl, err := template.New("search").Parse(fmt.Sprintf(`
<h2>Results for query "%s":</h2>
<ul>
{{range .}}
<li>{{.}}</li>
{{end}}
</ul>
`, query))
if err != nil {
c.String(http.StatusInternalServerError, "Error creating template")
return
}
var results []struct {
ID int
Title string
Content string
}
for rows.Next() {
var id int
var title string
var content string
err := rows.Scan(&id, &title, &content)
if err != nil {
log.Fatal(err)
}
results = append(results, struct {
ID int
Title string
Content string
}{id, title, content})
}
err = tmpl.Execute(c.Writer, results)
if err != nil {
c.String(http.StatusInternalServerError, "Error executing template")
return
}
}
Security vulnerabilities in a Golang web application
In the above code implementation for a database query search, we have some insecure code that shouldn’t be in the production application.
Which security vulnerabilities did you take note of in particular?
If you had the Snyk IDE extension installed (it’s free!), you’d catch those pesky security vulnerabilities in a matter of seconds after you hit the save-file shortcut:
The SQL Injection in the search handler
Briefly looking at the relevant code lines in the func search()
function handler for the search handler, we can observe that the query
variable is read from user input that comes from the query string in the URL:
query := c.Query("q")
Then, it flows into the database query in an unsanitized way and in an insecure way which doesn’t employ parameterized query binding:
func search(c *gin.Context) {
query := c.Query("q")
db, err := sql.Open("sqlite3", "posts.db")
if err != nil {
log.Fatal(err)
}
defer db.Close()
rows, err := db.Query(fmt.Sprintf("SELECT * FROM posts WHERE title LIKE '%%%s%%'", query))
if err != nil {
c.String(http.StatusInternalServerError, "Error executing query")
return
}
defer rows.Close()
// ... rest of the program code ...
}
That vulnerable code invites attackers to perform SQL injection attacks.
The SSTI in the search handler
In continuation from where we left off in the above func search()
function handler, the next part of the code creates an HTML template as follows:
tmpl, err := template.New("search").Parse(fmt.Sprintf(`
<h2>Results for query "%s":</h2>
<ul>
{{range .}}
<li>{{.}}</li>
{{end}}
</ul>
`, query))
The use of the query
variable in the way that it is passed to the html/template
library via the %s
placeholder enables at least two apparent security risks:
Cross-Site Scripting (XSS) - Allowing attackers to specify plain HTML elements that will be inserted into the HTML template and not encoded safely.
Server-Side Template Injection (SSTI) - Allowing attackers to provide literal template syntax, such as
{{ .Title | unsafeHTML }}
which is supported inhtml/template
and will be evaluated with it.
About code injection in the Golang template library
The case with Golang’s built-in html/template
library is that the package has built-in security measures that restrict access to potentially hazardous user-land functions such as os.system()
and others.
However, the package does allow you to define custom functions that can take user input and pipe out a result; in this way, developers are free to define their logic and use sensitive APIs and user-land functions from within the Golang standard library or other third-party packages in the Golang ecosystem.
Consider the following hypothetical custom template function:
func dangerousFunction(input string) string {
output, err := exec.Command("bash", "-c", input).Output()
if err != nil {
return "Error executing command"
}
return string(output)
}
tmpl, err := template.New("search").Funcs(template.FuncMap{"dangerous": dangerousFunction}).Parse( /* ... */ )
Note:
The above custom function is hazardous and should not be used beyond a learning exercise.
Back to our example about user input from the URL q query string flowing into the query
variable and then to the template, it can now be interpolated as a valid html/template
special syntax if the value of the query parameter is as follows: {{ dangerous "rm -rf /tmp" }}
.
Strengthening Golang application security
Let’s summarize the learnings and conclusions from this tour on Golang security vulnerabilities. While Go provides powerful tools like the html/template
package for building web applications, security must remain on our minds.
By understanding and mitigating potential vulnerabilities like SQL Injection, SSTI, and XSS, we can further increase the security of our code and take a step towards secure Golang applications.
Even more so, using Snyk’s IDE extension in VS Code, IntelliJ, or other developer tooling helps create a more secure application code by detecting and helping us fix security vulnerabilities as fast as we write code.
Quick tips for building safely with Golang
SQL Injection: Always use parameterized queries to prevent SQL injection vulnerabilities.
SSTI: Be mindful of potential SSTI risks, especially when using custom template functions or integrating third-party libraries. Make sure you use safe interpolation of variables.
Data Sanitization: Sanitize all user input before using it in templates, SQL queries, or any other part of the application, and ensure you are not concatenating user input as-is or in an insecure way.
The html/template Security: Use the safety features of the html/template library, such as the template.HTML type, with caution and only when necessary. Otherwise, you risk XSS vulnerabilities.
Regular Security Audits: Use Snyk to perform routine security audits of your application to identify and address potential security vulnerabilities in your Golang code, Go modules, and third-party and open-source dependencies.
Secure your Gen AI development with Snyk
Create security guardrails for any AI-assisted development.