Seguridad de aplicaciones Go: top 10 vulnerabilidades
Go (Golang) es un lenguaje compilado, estáticamente tipado, creado en Google. Gracias a su soporte integrado para concurrencia, alto rendimiento y modelo de memoria simple, se ha vuelto popular para crear microservicios, API gateways, aplicaciones en la nube y herramientas DevOps. Sin embargo, como cualquier otro lenguaje, Go no está exento de errores de seguridad. Los desarrolladores a menudo confían en la "seguridad por defecto", lo que lleva a la aparición de vulnerabilidades graves. En este artículo, analizaremos las 10 vulnerabilidades más críticas en aplicaciones Go y mostraremos cómo evitarlas.
1. Inyección SQL (SQL Injection)
A pesar de que Go proporciona potentes drivers de bases de datos (database/sql), los desarrolladores suelen cometer el error clásico de concatenar cadenas al formar consultas.
Código vulnerable:
package main
import ( "database/sql" "fmt" _ "github.com/lib/pq")
func getUser(db *sql.DB, username string) { // ¡Nunca hagas esto! query := fmt.Sprintf("SELECT * FROM users WHERE username = '%s'", username) rows, _ := db.Query(query) // ...}Solución: Usa consultas parametrizadas (prepared statements).
func getUserSafe(db *sql.DB, username string) { query := "SELECT * FROM users WHERE username = $1" rows, err := db.Query(query, username) if err != nil { // Manejo de errores } // ...}La parametrización escapa automáticamente los caracteres especiales, haciendo imposible la inyección.
2. Cross-Site Scripting (XSS)
Go se usa a menudo para el backend de aplicaciones web. Si devuelves la entrada del usuario sin escaparla, abres la puerta a ataques XSS.
Código vulnerable (usando fmt.Fprintf):
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { name := r.URL.Query().Get("name") fmt.Fprintf(w, "<h1>¡Hola, %s!</h1>", name) // ¡XSS!})Solución: Usa el paquete html/template para el escape automático.
import "html/template"
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { tmpl := template.Must(template.New("index").Parse("<h1>¡Hola, {{.}}!</h1>")) name := r.URL.Query().Get("name") tmpl.Execute(w, name) // Seguro})El motor de plantillas html/template escapa automáticamente los contextos HTML, JavaScript y CSS.
3. Condiciones de carrera (Race Conditions)
Go es famoso por su concurrencia ligera (goroutines y canales). Sin embargo, el uso incorrecto de recursos compartidos (como mapas o contadores) sin sincronización provoca data races.
Código vulnerable:
var counter int
func increment() { for i := 0; i < 1000; i++ { counter++ // ¡No es atómico! }}
func main() { go increment() go increment()}Solución: Usa mutex (sync.Mutex) u operaciones atómicas (sync/atomic).
import "sync"
var ( counter int mu sync.Mutex)
func increment() { for i := 0; i < 1000; i++ { mu.Lock() counter++ mu.Unlock() }}Siempre ejecuta las pruebas con la bandera -race: go test -race.
4. Fugas de memoria y goroutines (Memory & Goroutine Leaks)
Una de las vulnerabilidades más engañosas en Go son las goroutines "olvidadas". Si una goroutine se bloquea para siempre (por ejemplo, esperando datos de un canal que nunca se cerrará), esto provoca una fuga de memoria y la caída de la aplicación.
Ejemplo de fuga: