In this article
Server-Side Template Injection in Golang verstehen
Server-Side Template Injection (SSTI) ist eine unter Entwicklern eher seltene Sicherheitslücke. Gleichzeitig kann sie den Weg für zunehmende Sicherheitsrisiken wie File-Inclusion, Cross-Site-Scripting (XSS) oder sogar Code-Injection-Angriffe ebnen.
Wenn Entwickler Golang für die Full-Stack-Webentwicklung einsetzen und sowohl das Backend als auch das Frontend betreuen, sind sie anfälliger für Fehler und unsicheren Code, der zu SSTI führen kann.
In diesem Artikel lernen Sie eine einfache Webanwendung in Golang kennen, die mehrere Sicherheitslücken und unsichere Code-Muster enthält, die Ihnen hoffentlich auffallen. Anschließend befassen wir uns eingehend mit SSTI, um zu verstehen, wie Angreifer diese Schwachstelle ausnutzen können und wie Golangs integrierte Bibliothek html/template dabei eingesetzt wird.
Eine Webanwendung in Golang erstellen
Beginnen wir damit, die Bausteine einer kleinen Webanwendung in Golang Schritt für Schritt zusammenzufügen. Dafür verwenden wir die folgenden Komponenten:
Das Gin-Webframework für Golang verwendet die Open-Source-Go-Bibliothek
github.com/gin-gonic/gin.Die SQLite-3-Datenbank verwendet die Open-Source-Go-Bibliothek
database/sqlund den SQLite-Datenbanktreiber „github.com/mattn/go-sqlite3“.Die integrierte Go-Bibliothek
html/template, um HTML-Antworten anhand einer festgelegten Vorlage zusammenzustellen.
Golang-Projektabhängigkeiten deklarieren
Zu Beginn deklariert unsere Webanwendung, gekennzeichnet durch app.go, die importierten Abhängigkeiten des Projekts:
package main
import (
"database/sql"
"fmt"
"html/template"
"log"
"net/http"
"github.com/gin-gonic/gin"
_ "github.com/mattn/go-sqlite3"
)Falls Sie mit der Golang-Syntax nicht vertraut sind: Dabei handelt es sich um Namen oder URLs zum Importieren von Abhängigkeiten. Der SQLite3-Treiber verwendet insbesondere die Syntax mit Unterstrich _, um anzugeben, dass das importierte Paket im Code nicht explizit verwendet wird, aber von indirektem Code benötigt wird, der voraussetzt, dass es im Code unserer Anwendung vorhanden ist.
Golang-Web-Router und Funktions-Handler
Sehen wir uns als Nächstes grob an, wie wir den Code für diese einfache Webanwendung organisieren. Im Folgenden sehen Sie die verwendeten Funktionen im Überblick:
package main
import (...)
func search(c *gin.Context) {...}
func main() {
r := gin.Default()
r.GET("/search", search)
r.Run(":8080")
}Im obigen Codeausschnitt deklarieren wir die Einstiegspunktfunktion main() für das Golang-Programm. Sie definiert einen neuen Router mit dem Gin-Webframework und anschließend einen HTTP-GET-Endpunkt unter /search, der die von uns definierte Suchfunktion aufruft. Schließlich wird die Webanwendung gestartet, um Anfragen an Port 8080 zu verarbeiten.
Datenbankergebnisse anzeigen
In diesem Schritt extrahieren wir den Query-String aus der URL, um die Suchanfrage des Nutzers zu analysieren. Damit fragen wir die Datenbankeinträge ab, extrahieren die Ergebnisse, formatieren sie als HTML-Vorlage und senden diese als Antwort.
Hinweis:
Das folgende Code-Snippet enthält zahlreiche Sicherheitslücken und dient ausschließlich Lernzwecken. Sie sollten es nicht kopieren oder außerhalb einer Lernübung verwenden.
Der vollständige Code für unseren Funktions-Handler search lautet:
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
}
}Sicherheitslücken in einer Golang-Webanwendung
In der obigen Implementierung für die Suche in einer Datenbankabfrage finden sich einige unsichere Code-Stellen, die in einer Produktionsanwendung nichts zu suchen haben.
Welche Sicherheitslücken sind Ihnen besonders aufgefallen?
Wenn Sie die Snyk-IDE-Erweiterung installiert hätten (sie ist kostenlos!), würden Sie diese lästigen Sicherheitslücken nur wenige Sekunden nach dem Speichern entdecken:

Die SQL-Injection im Such-Handler
Ein kurzer Blick auf die relevanten Codezeilen im Funktions-Handler func search() für die Suche zeigt: Die Variable query wird aus Nutzereingaben gelesen, die aus dem Query-String der URL stammen:
query := c.Query("q")Anschließend fließt sie ungeprüft und auf unsichere Weise in die Datenbankabfrage ein, ohne parametrisierte Abfragebindungen zu verwenden:
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 ...
}Dieser anfällige Code lädt Angreifer geradezu zu SQL-Injection-Angriffen ein.
Die SSTI im Such-Handler
Anknüpfend an den vorherigen Abschnitt der Funktion func search() erstellt der nächste Teil des Codes eine HTML-Vorlage:
tmpl, err := template.New("search").Parse(fmt.Sprintf(`
<h2>Results for query "%s":</h2>
<ul>
{{range .}}
<li>{{.}}</li>
{{end}}
</ul>
`, query))Die Verwendung der Variable query, die über den Platzhalter %s an die Bibliothek html/template übergeben wird, birgt mindestens zwei offensichtliche Sicherheitsrisiken:
Cross-Site-Scripting (XSS) – Angreifer können einfache HTML-Elemente angeben, die in die HTML-Vorlage eingefügt und nicht sicher kodiert werden.
Server-Side Template Injection (SSTI) – Angreifer können wörtliche Vorlagensyntax wie
{{ .Title | unsafeHTML }}einschleusen, die vonhtml/templateunterstützt und ausgewertet wird.
Code-Injection in der Golang-Vorlagenbibliothek
Die integrierte Golang-Bibliothek html/template verfügt über Sicherheitsmaßnahmen, die den Zugriff auf potenziell gefährliche Funktionen aus dem Userland wie os.system() und andere einschränken.
Die Bibliothek ermöglicht es Ihnen jedoch, benutzerdefinierte Funktionen zu definieren, die Nutzereingaben entgegennehmen und ein Ergebnis ausgeben. So können Entwickler ihre eigene Logik festlegen und innerhalb der Golang-Standardbibliothek oder anderer Drittanbieterpakete des Golang-Ökosystems auf sensible APIs und Userland-Funktionen zugreifen.
Sehen wir uns die folgende hypothetische benutzerdefinierte Vorlagenfunktion an:
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( /* ... */ )Hinweis:
Die obige benutzerdefinierte Funktion ist gefährlich und sollte ausschließlich zu Lernzwecken verwendet werden.
Kehren wir zu unserem Beispiel zurück: Nutzereingaben aus dem URL-Query-String q fließen in die Variable query und dann in die Vorlage. Nun können sie als gültige spezielle html/template-Syntax interpoliert werden, wenn der Wert des Query-Parameters wie folgt lautet: {{ dangerous "rm -rf /tmp" }}.
Die Sicherheit von Golang-Anwendungen stärken
Fassen wir die Erkenntnisse und Schlussfolgerungen aus diesem Überblick über Sicherheitslücken in Golang zusammen. Go bietet leistungsstarke Tools wie das Paket html/template für die Entwicklung von Webanwendungen. Dennoch müssen wir die Sicherheit stets im Blick behalten.
Wenn wir potenzielle Schwachstellen wie SQL-Injection, SSTI und XSS verstehen und beheben, können wir die Sicherheit unseres Codes weiter erhöhen und einen Schritt hin zu sicheren Golang-Anwendungen machen.
Noch sicherer wird der Anwendungscode, wenn Sie die Snyk-IDE-Erweiterung in VS Code, IntelliJ oder anderen Entwickler-Tools nutzen. Sie erkennt Sicherheitslücken und hilft uns, sie so schnell zu beheben, wie wir Code schreiben.
Schnelle Tipps für sicheres Entwickeln mit Golang
SQL-Injection: Verwenden Sie stets parametrisierte Abfragen, um SQL-Injection-Schwachstellen zu verhindern.
SSTI: Achten Sie auf potenzielle SSTI-Risiken, insbesondere bei der Verwendung benutzerdefinierter Vorlagenfunktionen oder der Integration von Drittanbieterbibliotheken. Stellen Sie sicher, dass Sie Variablen sicher interpolieren.
Datensanitization: Bereinigen Sie alle Nutzereingaben, bevor Sie sie in Vorlagen, SQL-Abfragen oder anderen Teilen der Anwendung verwenden. Achten Sie darauf, Nutzereingaben nicht unverändert oder auf unsichere Weise zu verketten.
Sicherheit von html/template: Nutzen Sie die Sicherheitsfunktionen der Bibliothek html/template, etwa den Typ template.HTML, mit Vorsicht und nur, wenn es nötig ist. Andernfalls riskieren Sie XSS-Schwachstellen.
Regelmäßige Sicherheitsaudits: Nutzen Sie Snyk, um Ihre Anwendung regelmäßig auf potenzielle Sicherheitslücken in Ihrem Golang-Code, Go-Modulen sowie Drittanbieter- und Open-Source-Abhängigkeiten zu prüfen und diese zu beheben.
Sichern Sie Ihre GenAI-Entwicklung mit Snyk
Schaffen Sie Sicherheitsleitplanken für jede KI-gestützte Entwicklung.