Безопасность Go приложений: топ-10 уязвимостей для начинающих

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Безопасность Go приложений: топ-10 уязвимостей для начинающих

Go (Golang) — один из самых популярных языков для создания высоконагруженных веб-сервисов, микросервисов и сетевых приложений. Однако, как и любой другой язык, Go требует внимательного подхода к безопасности. Если вы только начинаете осваивать Go, важно знать основные уязвимости, которые могут подстерегать ваше приложение. В этой статье мы разберём 10 самых распространённых угроз и покажем, как их избежать.

1. SQL-инъекции (SQL Injection)

SQL-инъекция — одна из старейших и опаснейших уязвимостей. Она возникает, когда пользовательский ввод напрямую вставляется в SQL-запрос.

Пример уязвимого кода:

func getUser(db *sql.DB, id string) (*User, error) {
    row := db.QueryRow("SELECT * FROM users WHERE id = " + id)
    // ...
}

Как исправить:

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

func getUser(db *sql.DB, id string) (*User, error) {
    row := db.QueryRow("SELECT * FROM users WHERE id = $1", id)
    // ...
}

2. Межсайтовый скриптинг (XSS)

XSS возникает, когда неэкранированный пользовательский ввод отображается на веб-странице. В Go это часто происходит при использовании html/template вместо text/template.

Пример уязвимого кода:

fmt.Fprintf(w, "<p>Привет, %s</p>", r.URL.Query().Get("name"))

Как исправить:

Используйте html/template для автоматического экранирования:

tmpl := template.Must(template.New("").Parse("<p>Привет, {{.}}</p>"))
tmpl.Execute(w, r.URL.Query().Get("name"))

3. Подделка межсайтовых запросов (CSRF)

CSRF-атаки заставляют пользователя выполнить нежелательное действие на сайте, где он авторизован. Go-приложения часто забывают о защите от CSRF.

Решение:

Используйте библиотеку gorilla/csrf или реализуйте свой middleware:

import "github.com/gorilla/csrf"

func main() {
    r := mux.NewRouter()
    r.Use(csrf.Protect([]byte("32-байтовый-ключ")))
    // ...
}

4. Небезопасная обработка ошибок

Распространённая ошибка — возврат пользователю системных сообщений об ошибках, которые могут раскрыть внутреннюю структуру приложения.

Пример уязвимого кода:

if err != nil {
    http.Error(w, err.Error(), http.StatusInternalServerError)
}

Как исправить:

if err != nil {
    log.Printf("Ошибка: %v", err)
    http.Error(w, "Внутренняя ошибка сервера", http.StatusInternalServerError)
}

5. Утечка информации через заголовки

По умолчанию Go-сервер может раскрывать версию Go, тип сервера и другую служебную информацию.

Решение:

type secureHandler struct {
    next http.Handler
}

func (h *secureHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("X-Content-Type-Options", "nosniff")
    w.Header().Set("X-Frame-Options", "DENY")
    w.Header().Set("X-XSS-Protection", "1; mode=block")
    h.next.ServeHTTP(w, r)
}

6. Неправильная работа с горутинами

Горутины — мощный инструмент Go, но их неправильное использование может привести к гонкам данных (race conditions) и утечкам памяти.

Пример уязвимого кода:

var counter int
for i := 0; i < 1000; i++ {
    go func() {
        counter++ // race condition!
    }()
}

Как исправить:

var mu sync.Mutex
var counter int
for i := 0; i < 1000; i++ {
    go func() {
        mu.Lock()
        counter++
        mu.Unlock()
    }()
}

7. Небезопасное хранение паролей

Хранение паролей в открытом виде или с использованием слабых хешей (MD5, SHA1) — грубая ошибка.

Решение:

Используйте bcrypt или argon2:

import "golang.org/x/crypto/bcrypt"

func hashPassword(password string) (string, error) {
    bytes, err := bcrypt.GenerateFromPassword([]byte(password), 14)
    return string(bytes), err
}

func checkPasswordHash(password, hash string) bool {
    err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
    return err == nil
}

8. Отсутствие лимитов на запросы

Без ограничения количества запросов ваше приложение уязвимо для DoS-атак.

Решение:

import "golang.org/x/time/rate"

var limiter = rate.NewLimiter(1, 5) // 1 запрос в секунду, burst 5

func limitMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        if !limiter.Allow() {
            http.Error(w, "Слишком много запросов", http.StatusTooManyRequests)
            return
        }
        next.ServeHTTP(w, r)
    })
}

9. Небезопасное использование JSON

При десериализации JSON из ненадёжных источников злоумышленник может передать неожиданные типы данных.

Пример уязвимого кода:

var data interface{}
json.Unmarshal(body, &data)

Как исправить:

type User struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
}

var user User
decoder := json.NewDecoder(strings.NewReader(body))
decoder.DisallowUnknownFields() // запрещаем неизвестные поля
if err := decoder.Decode(&user); err != nil {
    // обрабатываем ошибку
}

10. Использование устаревших зависимостей

Go-моду

Блоги

Рекомендаций по книгам