Перейти к содержимому

Лучшие практики безопасности

Веб-приложения являются основной целью для злоумышленников. Приложение на Gin, которое обрабатывает пользовательский ввод, хранит данные или работает за обратным прокси, нуждается в целенаправленной настройке безопасности перед выходом в продакшн. Это руководство охватывает наиболее важные защиты и показывает, как применить каждую из них с помощью middleware Gin и стандартных библиотек Go.

Конфигурация CORS

Cross-Origin Resource Sharing (CORS) контролирует, какие внешние домены могут делать запросы к вашему API. Некорректно настроенный CORS может позволить вредоносным сайтам читать ответы с вашего сервера от имени аутентифицированных пользователей.

Используйте пакет gin-contrib/cors для проверенного решения.

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()
}

Защита от CSRF

Cross-Site Request Forgery обманывает браузер аутентифицированного пользователя, заставляя его отправлять нежелательные запросы к вашему приложению. Любой эндпоинт, изменяющий состояние (POST, PUT, DELETE), который полагается на cookies для аутентификации, нуждается в защите от CSRF.

Используйте middleware gin-contrib/csrf для добавления защиты на основе токенов.

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()
}

Ограничение частоты запросов

Ограничение частоты запросов предотвращает злоупотребления, атаки перебором и исчерпание ресурсов. Вы можете использовать пакет стандартной библиотеки golang.org/x/time/rate для создания простого 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()
}

Валидация ввода и предотвращение SQL-инъекций

Всегда валидируйте и привязывайте ввод, используя привязку модели Gin с тегами структур. Никогда не конструируйте SQL-запросы путём конкатенации пользовательского ввода.

Валидация ввода с тегами структур

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
}

Используйте параметризованные запросы

// 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)

Это относится к каждой библиотеке баз данных. Независимо от того, используете ли вы database/sql, GORM, sqlx или другую ORM, всегда используйте параметры-заполнители и никогда конкатенацию строк.

Предотвращение XSS

Межсайтовый скриптинг (XSS) происходит, когда злоумышленник внедряет вредоносные скрипты, которые выполняются в браузерах других пользователей. Защищайтесь от этого на нескольких уровнях.

Экранирование HTML-вывода

При рендеринге HTML-шаблонов пакет Go html/template экранирует вывод по умолчанию. Если вы возвращаете данные, предоставленные пользователем, в виде JSON, убедитесь, что заголовок Content-Type установлен правильно, чтобы браузеры не интерпретировали JSON как 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})

Используйте SecureJSON для защиты от JSONP

Gin предоставляет c.SecureJSON, который добавляет while(1); для предотвращения перехвата JSON.

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

Явно устанавливайте Content-Type при необходимости

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

Заголовок X-Content-Type-Options: nosniff предотвращает определение MIME-типа браузерами, что не позволяет им интерпретировать ответ как HTML, когда сервер объявляет его как что-то другое.

Middleware заголовков безопасности

Добавление заголовков безопасности — один из самых простых и эффективных шагов усиления защиты. Подробный пример см. на странице Заголовки безопасности. Ниже краткое описание основных заголовков.

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()
}
}
ЗаголовокЧто предотвращает
X-Frame-Options: DENYКликджекинг через iframe
X-Content-Type-Options: nosniffАтаки с подменой MIME-типа
Strict-Transport-SecurityПонижение протокола и перехват cookies
Content-Security-PolicyXSS и инъекция данных
Referrer-PolicyУтечка конфиденциальных параметров URL третьим сторонам
Permissions-PolicyНесанкционированное использование API браузера (камера, микрофон и т.д.)

Доверенные прокси

Когда ваше приложение работает за обратным прокси или балансировщиком нагрузки, вы должны указать Gin, каким прокси доверять. Без этой конфигурации злоумышленники могут подделать заголовок X-Forwarded-For для обхода контроля доступа на основе IP и ограничения частоты запросов.

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

Подробное объяснение и параметры конфигурации см. на странице Доверенные прокси.

HTTPS и TLS

Все продакшн-приложения Gin должны обслуживать трафик через HTTPS. Gin поддерживает автоматические TLS-сертификаты через Let’s Encrypt.

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

Полные инструкции по настройке, включая пользовательские менеджеры сертификатов, см. на странице Поддержка Let’s Encrypt.

Смотрите также