Saltearse al contenido

Mejores prácticas de seguridad

Las aplicaciones web son un objetivo principal para los atacantes. Una aplicación Gin que maneja entrada del usuario, almacena datos o se ejecuta detrás de un proxy inverso necesita una configuración de seguridad deliberada antes de ir a producción. Esta guía cubre las defensas más importantes y muestra cómo aplicar cada una con middleware de Gin y bibliotecas estándar de Go.

Configuración CORS

El Intercambio de Recursos de Origen Cruzado (CORS) controla qué dominios externos pueden realizar solicitudes a tu API. Un CORS mal configurado puede permitir que sitios web maliciosos lean respuestas de tu servidor en nombre de usuarios autenticados.

Usa el paquete gin-contrib/cors para una solución bien probada.

package main
import (
"time"
"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
r.Use(cors.New(cors.Config{
AllowOrigins: []string{"https://example.com"},
AllowMethods: []string{"GET", "POST", "PUT", "DELETE"},
AllowHeaders: []string{"Origin", "Content-Type", "Authorization"},
ExposeHeaders: []string{"Content-Length"},
AllowCredentials: true,
MaxAge: 12 * time.Hour,
}))
r.GET("/api/data", func(c *gin.Context) {
c.JSON(200, gin.H{"message": "ok"})
})
r.Run()
}

Protección CSRF

La Falsificación de Solicitud entre Sitios engaña al navegador de un usuario autenticado para enviar solicitudes no deseadas a tu aplicación. Cualquier endpoint que cambie estado (POST, PUT, DELETE) que dependa de cookies para autenticación necesita protección CSRF.

Usa el middleware gin-contrib/csrf para agregar protección basada en tokens.

package main
import (
"github.com/gin-contrib/csrf"
"github.com/gin-contrib/sessions"
"github.com/gin-contrib/sessions/cookie"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
store := cookie.NewStore([]byte("session-secret"))
r.Use(sessions.Sessions("mysession", store))
r.Use(csrf.Middleware(csrf.Options{
Secret: "csrf-token-secret",
ErrorFunc: func(c *gin.Context) {
c.String(403, "CSRF token mismatch")
c.Abort()
},
}))
r.GET("/form", func(c *gin.Context) {
token := csrf.GetToken(c)
c.JSON(200, gin.H{"csrf_token": token})
})
r.POST("/form", func(c *gin.Context) {
c.JSON(200, gin.H{"message": "submitted"})
})
r.Run()
}

Limitación de tasa

La limitación de tasa previene abuso, ataques de fuerza bruta y agotamiento de recursos. Puedes usar el paquete de la biblioteca estándar golang.org/x/time/rate para construir un limitador de tasa simple por cliente como middleware.

package main
import (
"net/http"
"sync"
"github.com/gin-gonic/gin"
"golang.org/x/time/rate"
)
func RateLimiter() gin.HandlerFunc {
type client struct {
limiter *rate.Limiter
}
var (
mu sync.Mutex
clients = make(map[string]*client)
)
return func(c *gin.Context) {
ip := c.ClientIP()
mu.Lock()
if _, exists := clients[ip]; !exists {
// Allow 10 requests per second with a burst of 20
clients[ip] = &client{limiter: rate.NewLimiter(10, 20)}
}
cl := clients[ip]
mu.Unlock()
if !cl.limiter.Allow() {
c.AbortWithStatusJSON(http.StatusTooManyRequests, gin.H{
"error": "rate limit exceeded",
})
return
}
c.Next()
}
}
func main() {
r := gin.Default()
r.Use(RateLimiter())
r.GET("/api/resource", func(c *gin.Context) {
c.JSON(200, gin.H{"message": "ok"})
})
r.Run()
}

Validación de entrada y prevención de inyección SQL

Siempre valida y enlaza la entrada usando el enlace de modelos de Gin con etiquetas de struct. Nunca construyas consultas SQL concatenando entrada del usuario.

Validar entrada con etiquetas de struct

type CreateUser struct {
Username string `json:"username" binding:"required,alphanum,min=3,max=30"`
Email string `json:"email" binding:"required,email"`
Age int `json:"age" binding:"required,gte=1,lte=130"`
}
func createUserHandler(c *gin.Context) {
var req CreateUser
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
// req is now validated; proceed safely
}

Usar consultas parametrizadas

// DANGEROUS -- SQL injection vulnerability
row := db.QueryRow("SELECT id FROM users WHERE username = '" + username + "'")
// SAFE -- parameterized query
row := db.QueryRow("SELECT id FROM users WHERE username = $1", username)

Esto aplica a cada biblioteca de base de datos. Ya sea que uses database/sql, GORM, sqlx u otro ORM, siempre usa marcadores de posición de parámetros y nunca concatenación de cadenas.

Prevención de XSS

Cross-Site Scripting (XSS) ocurre cuando un atacante inyecta scripts maliciosos que se ejecutan en los navegadores de otros usuarios. Defiéndete contra esto en múltiples capas.

Escapar la salida HTML

Al renderizar plantillas HTML, el paquete html/template de Go escapa la salida por defecto. Si devuelves datos proporcionados por el usuario como JSON, asegúrate de que el encabezado Content-Type esté configurado correctamente para que los navegadores no interpreten el JSON como HTML.

// Gin sets Content-Type automatically for JSON responses.
// Use c.JSON, not c.String, when returning structured data.
c.JSON(200, gin.H{"input": userInput})

Usar SecureJSON para protección JSONP

Gin proporciona c.SecureJSON que antepone while(1); para prevenir el secuestro de JSON.

c.SecureJSON(200, gin.H{"data": userInput})

Establecer Content-Type explícitamente cuando sea necesario

// For API endpoints, always return JSON
c.Header("Content-Type", "application/json; charset=utf-8")
c.Header("X-Content-Type-Options", "nosniff")

El encabezado X-Content-Type-Options: nosniff previene que los navegadores detecten el tipo MIME, lo que impide que interpreten una respuesta como HTML cuando el servidor la declara como algo diferente.

Middleware de encabezados de seguridad

Agregar encabezados de seguridad es uno de los pasos de endurecimiento más simples y efectivos. Consulta la página completa de Encabezados de seguridad para un ejemplo detallado. A continuación hay un resumen rápido de los encabezados esenciales.

func SecurityHeaders() gin.HandlerFunc {
return func(c *gin.Context) {
c.Header("X-Frame-Options", "DENY")
c.Header("X-Content-Type-Options", "nosniff")
c.Header("Strict-Transport-Security", "max-age=31536000; includeSubDomains")
c.Header("Content-Security-Policy", "default-src 'self'")
c.Header("Referrer-Policy", "strict-origin")
c.Header("Permissions-Policy", "geolocation=(), camera=(), microphone=()")
c.Next()
}
}
EncabezadoQué previene
X-Frame-Options: DENYClickjacking a través de iframes
X-Content-Type-Options: nosniffAtaques de detección de tipo MIME
Strict-Transport-SecurityDegradación de protocolo y secuestro de cookies
Content-Security-PolicyXSS e inyección de datos
Referrer-PolicyFiltración de parámetros URL sensibles a terceros
Permissions-PolicyUso no autorizado de APIs del navegador (cámara, micrófono, etc.)

Proxies de confianza

Cuando tu aplicación se ejecuta detrás de un proxy inverso o balanceador de carga, debes decirle a Gin en qué proxies confiar. Sin esta configuración, los atacantes pueden falsificar el encabezado X-Forwarded-For para evadir controles de acceso basados en IP y limitación de tasa.

// Trust only your known proxy addresses
router.SetTrustedProxies([]string{"10.0.0.1", "192.168.1.0/24"})

Consulta la página de Proxies de confianza para una explicación completa y opciones de configuración.

HTTPS y TLS

Todas las aplicaciones Gin en producción deberían servir tráfico sobre HTTPS. Gin soporta certificados TLS automáticos a través de Let’s Encrypt.

import "github.com/gin-gonic/autotls"
func main() {
r := gin.Default()
// ... routes ...
log.Fatal(autotls.Run(r, "example.com"))
}

Consulta la página de Soporte para Let’s Encrypt para instrucciones de configuración completas incluyendo gestores de certificados personalizados.

Ver también