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

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

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

Начать курс

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

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

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

Несмотря на то, что Go предоставляет мощные драйверы баз данных (database/sql), разработчики часто допускают классическую ошибку — конкатенацию строк при формировании запросов.

Уязвимый код:

package main

import (
    "database/sql"
    "fmt"
    _ "github.com/lib/pq"
)

func getUser(db *sql.DB, username string) {
    // Никогда так не делайте!
    query := fmt.Sprintf("SELECT * FROM users WHERE username = '%s'", username)
    rows, _ := db.Query(query)
    // ...
}

Решение: Используйте параметризованные запросы (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 {
        // Обработка ошибки
    }
    // ...
}

Параметризация автоматически экранирует специальные символы, делая инъекцию невозможной.

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

Go часто используется для серверной части веб-приложений. Если вы возвращаете пользовательский ввод без экранирования, вы открываете путь для XSS-атак.

Уязвимый код (использование fmt.Fprintf):

http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
    name := r.URL.Query().Get("name")
    fmt.Fprintf(w, "<h1>Привет, %s!</h1>", name) // XSS!
})

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

import "html/template"

http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
    tmpl := template.Must(template.New("index").Parse("<h1>Привет, {{.}}!</h1>"))
    name := r.URL.Query().Get("name")
    tmpl.Execute(w, name) // Безопасно
})

Шаблонизатор html/template автоматически экранирует HTML, JavaScript и CSS контексты.

3. Состояния гонки (Race Conditions)

Go славится своей легковесной конкурентностью (горутины и каналы). Однако неправильное использование общих ресурсов (например, карт или счетчиков) без синхронизации приводит к data races.

Уязвимый код:

var counter int

func increment() {
    for i := 0; i < 1000; i++ {
        counter++ // Не атомарно!
    }
}

func main() {
    go increment()
    go increment()
}

Решение: Используйте мьютексы (sync.Mutex) или атомарные операции (sync/atomic).

import "sync"

var (
    counter int
    mu      sync.Mutex
)

func increment() {
    for i := 0; i < 1000; i++ {
        mu.Lock()
        counter++
        mu.Unlock()
    }
}

Всегда запускайте тесты с флагом -race: go test -race.

4. Утечки памяти и горутин (Memory & Goroutine Leaks)

Одна из самых коварных уязвимостей в Go — "забытые" горутины. Если горутина блокируется навсегда (например, ожидает данные из канала, который никогда не закроется), это приводит к утечке памяти и отказу приложения.

Пример утечки:

func worker(ch chan int) {
    for {
        data := <-ch // Блокируется навсегда, если канал пуст и не закрыт
        // Обработка data
    }
}

func main() {
    ch := make(chan int)
    go worker(ch)
    // ch никогда не закрывается и не получает данные
}

Решение: Используйте контексты (context.Context) для отмены горутин и всегда закрывайте каналы.

func worker(ctx context.Context, ch chan int) {
    for {
        select {
        case <-ctx.Done():
            return // Корректное завершение
        case data, ok := <-ch:
            if !ok {
                return // Канал закрыт
            }
            // Обработка data
        }
    }
}

func main() {
    ctx, cancel := context.WithCancel(context.Background())
    defer cancel()
    ch := make(chan int)
    go worker(ctx, ch)
    close(ch)
}

5. Неправильная обработка ошибок

В Go ошибки — это значения. Игнорирование ошибок (_) или их неправильная обработка может привести к раскрытию информации или нарушению логики безопасности.

Уязвимый код:

data, _ := ioutil.ReadFile("config.json") // Игнорируем ошибку
// Если файла нет, data будет nil, и приложение упадет или будет вести себя непредсказуемо

Решение: Всегда обрабатывайте ошибки. Используйте errors.Is и errors.As для проверки типов ошибок.

data, err := os.ReadFile("config.json")
if err != nil {
    log.Fatalf("Не удалось прочитать файл: %v", err)
}

6. Небезопасное логирование (Log Injection)

Если вы логируете пользовательский ввод без фильтрации, злоумышленник может внедрить управляющие последовательности в лог-файлы (CRLF-инъекция) или подделать записи.

Уязвимый код:

log.Printf("                

Блоги

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