In this article
Entender la inyección de plantillas del lado del servidor en Golang
Una vulnerabilidad de seguridad no tan común entre desarrolladores es la inyección de plantillas del lado del servidor (SSTI). Aun así, puede abrir la puerta a riesgos de seguridad cada vez mayores, como la inclusión de archivos, el cross-site scripting (XSS) e incluso los ataques de inyección de código.
Cuando los desarrolladores usan Golang para crear aplicaciones web full stack y se encargan tanto del backend como del frontend, es más probable que cometan errores al escribir código inseguro que puede provocar SSTI.
En este artículo, aprenderás a crear una aplicación web sencilla en Golang con varias vulnerabilidades de seguridad y patrones de código inseguro que espero que puedas detectar. Después, profundizaremos en el caso específico de la SSTI para entender cómo los atacantes pueden explotar esta vulnerabilidad y cómo se usa la biblioteca integrada html/template de Golang en ese contexto.
Crear una aplicación web en Golang
Empecemos por crear, paso a paso, los componentes de una pequeña aplicación web en Golang. Usaremos los siguientes elementos:
El framework web Gin para Golang usa la biblioteca de código abierto de Go
github.com/gin-gonic/gin.La base de datos SQLite 3 usa la biblioteca de código abierto de Go
database/sqly el controlador de SQLite "github.com/mattn/go-sqlite3".La biblioteca integrada de Go
html/templatepara crear respuestas HTML mediante una plantilla declarada.
Declarar las dependencias del proyecto en Golang
Para empezar, nuestra aplicación web, representada por app.go, comienza por declarar las dependencias importadas del proyecto:
package main
import (
"database/sql"
"fmt"
"html/template"
"log"
"net/http"
"github.com/gin-gonic/gin"
_ "github.com/mattn/go-sqlite3"
)Si no conoces la sintaxis de Golang, estos son nombres o URL para importar dependencias. En concreto, el controlador SQLite3 usa la sintaxis de guion bajo _ para indicar que el paquete importado no se usa explícitamente en el código, pero que el código indirecto que depende de él necesita que esté presente en el código de nuestra aplicación.
Enrutador web y controladores de funciones de Golang
A continuación, veamos a grandes rasgos cómo organizaremos el código de esta sencilla aplicación web. Estas son las funciones que usaremos:
package main
import (...)
func search(c *gin.Context) {...}
func main() {
r := gin.Default()
r.GET("/search", search)
r.Run(":8080")
}En el fragmento de código anterior, declaramos la función de entrada main() del programa de Golang. Esta define un nuevo enrutador con el framework web Gin y, luego, un endpoint HTTP GET en /search que llama a la función de búsqueda que definimos. Por último, la aplicación web se ejecuta para atender solicitudes en el puerto 8080.
Mostrar los resultados de la base de datos
Aquí extraemos la cadena de consulta de la URL para analizar la búsqueda del usuario, la usamos para consultar los registros de la base de datos, los extraemos, les damos formato en una plantilla HTML y enviamos esa plantilla como respuesta.
Nota:
El siguiente fragmento de código contiene numerosas vulnerabilidades de seguridad y está pensado únicamente con fines educativos. No lo copies ni lo uses fuera de un ejercicio de aprendizaje.
El código completo del controlador de la función search es el siguiente:
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
}
}Vulnerabilidades de seguridad en una aplicación web de Golang
En la implementación del código anterior para buscar en la base de datos, hay código inseguro que no debería estar en una aplicación de producción.
¿Qué vulnerabilidades de seguridad detectaste en particular?
Si tuvieras instalada la extensión de Snyk para IDE (¡es gratis!), detectarías esas molestas vulnerabilidades de seguridad en cuestión de segundos, después de usar el atajo para guardar el archivo:

La inyección SQL en el controlador de búsqueda
Al revisar brevemente las líneas de código pertinentes en el controlador de la función func search(), podemos observar que la variable query lee datos ingresados por el usuario, que provienen de la cadena de consulta de la URL:
query := c.Query("q")Luego, esos datos pasan a la consulta de la base de datos sin sanitizar y de forma insegura, sin usar parámetros vinculados:
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 ...
}Ese código vulnerable permite que los atacantes realicen ataques de inyección SQL.
La SSTI en el controlador de búsqueda
A continuación de lo que vimos en el controlador de la función func search(), la siguiente parte del código crea una plantilla HTML de la siguiente manera:
tmpl, err := template.New("search").Parse(fmt.Sprintf(`
<h2>Results for query "%s":</h2>
<ul>
{{range .}}
<li>{{.}}</li>
{{end}}
</ul>
`, query))El uso de la variable query tal como se pasa a la biblioteca html/template mediante el marcador de posición %s presenta al menos dos riesgos de seguridad evidentes:
Cross-site scripting (XSS): permite que los atacantes especifiquen elementos HTML sin procesar que se insertarán en la plantilla HTML sin codificarse de forma segura.
Inyección de plantillas del lado del servidor (SSTI): permite que los atacantes proporcionen sintaxis literal de plantillas, como
{{ .Title | unsafeHTML }}, compatible conhtml/templatey que se evaluará con ella.
Acerca de la inyección de código en la biblioteca de plantillas de Golang
La biblioteca integrada html/template de Golang incluye medidas de seguridad que restringen el acceso a funciones potencialmente peligrosas del espacio de usuario, como os.system() y otras.
Sin embargo, la biblioteca permite definir funciones personalizadas que reciben datos del usuario y devuelven un resultado. De esta manera, los desarrolladores tienen libertad para definir su lógica y usar API sensibles y funciones del espacio de usuario de la biblioteca estándar de Golang o de otros paquetes de terceros del ecosistema de Golang.
Considera la siguiente función personalizada de plantilla hipotética:
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( /* ... */ )Nota:
La función personalizada anterior es peligrosa y no debe usarse fuera de un ejercicio de aprendizaje.
Volvamos al ejemplo en el que los datos del usuario provenientes de la cadena de consulta q de la URL pasan a la variable query y luego a la plantilla. Ahora pueden interpolarse como sintaxis especial válida de html/template si el valor del parámetro de consulta es el siguiente: {{ dangerous "rm -rf /tmp" }}.
Reforzar la seguridad de las aplicaciones en Golang
Resumamos lo aprendido y las conclusiones de este recorrido por las vulnerabilidades de seguridad en Golang. Aunque Go ofrece herramientas potentes como el paquete html/template para crear aplicaciones web, siempre debemos tener presente la seguridad.
Al entender y mitigar posibles vulnerabilidades como la inyección SQL, la SSTI y el XSS, podemos mejorar aún más la seguridad de nuestro código y dar un paso hacia aplicaciones seguras en Golang.
Además, usar la extensión de Snyk para IDE en VS Code, IntelliJ u otras herramientas para desarrolladores ayuda a crear código de aplicaciones más seguro, ya que detecta vulnerabilidades de seguridad y nos ayuda a corregirlas mientras escribimos.
Consejos rápidos para desarrollar de forma segura con Golang
Inyección SQL: usa siempre consultas parametrizadas para evitar vulnerabilidades de inyección SQL.
SSTI: presta atención a los posibles riesgos de SSTI, especialmente cuando uses funciones de plantillas personalizadas o integres bibliotecas de terceros. Asegúrate de interpolar las variables de forma segura.
Sanitización de datos: sanitiza todos los datos ingresados por los usuarios antes de usarlos en plantillas, consultas SQL o cualquier otra parte de la aplicación, y asegúrate de no concatenarlos tal cual ni de forma insegura.
Seguridad de html/template: usa con cautela las funciones de seguridad de la biblioteca html/template, como el tipo template.HTML, y solo cuando sea necesario. De lo contrario, corres el riesgo de sufrir vulnerabilidades XSS.
Auditorías de seguridad periódicas: usa Snyk para realizar auditorías de seguridad periódicas de tu aplicación e identificar y abordar posibles vulnerabilidades de seguridad en tu código de Golang, módulos de Go y dependencias de terceros y de código abierto.
Protege el desarrollo de IA generativa con Snyk
Crea barreras de seguridad para cualquier desarrollo asistido por IA.