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

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

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

Начать курс

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

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

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

Несмотря на наличие пакета database/sql, разработчики часто допускают конкатенацию строк при построении SQL-запросов. Это классическая уязвимость, которая позволяет злоумышленнику выполнить произвольный SQL-код.

Небезопасный код:

// ОПАСНО: конкатенация строк
func getUser(w http.ResponseWriter, r *http.Request) {
    username := r.URL.Query().Get("username")
    query := "SELECT * FROM users WHERE username = '" + username + "'"
    rows, err := db.Query(query)
    // ...
}

Безопасный код с параметризацией:

func getUser(w http.ResponseWriter, r *http.Request) {
    username := r.URL.Query().Get("username")
    // Используем плейсхолдеры
    query := "SELECT * FROM users WHERE username = $1"
    rows, err := db.Query(query, username)
    // ...
}

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

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

Если ваше Go-приложение рендерит HTML на сервере (например, с помощью html/template), необходимо экранировать вывод. Встроенный пакет text/template этого не делает.

Небезопасный код:

// Использование text/template опасно
tmpl, _ := template.New("test").Parse("<h1>{{.Name}}</h1>")
tmpl.Execute(w, map[string]string{"Name": "<script>alert('XSS')</script>"})

Безопасный код:

// Всегда используйте html/template для HTML-шаблонов
import "html/template"
tmpl, _ := template.New("test").Parse("<h1>{{.Name}}</h1>")
tmpl.Execute(w, map[string]string{"Name": "<script>alert('XSS')</script>"})
// Вывод: &lt;script&gt;alert('XSS')&lt;/script&gt;

Рекомендация: Для работы с HTML используйте только html/template. Всегда валидируйте и экранируйте пользовательский ввод, особенно если он выводится в атрибуты тегов или JavaScript.

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

Go — язык с конкурентностью, и race condition — одна из самых частых проблем. Она может привести к повреждению данных, утечкам памяти и неожиданным сбоям.

Небезопасный код:

var counter int

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

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

Безопасный код с мьютексом:

var (
    counter int
    mu      sync.Mutex
)

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

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

Рекомендация: Используйте sync.Mutex, sync.RWMutex или атомарные операции (sync/atomic). Запускайте тесты с флагом -race для обнаружения гонок.

4. Path Traversal (Обход пути)

Уязвимость возникает, когда приложение использует пользовательский ввод для формирования пути к файлу без должной проверки.

Небезопасный код:

func serveFile(w http.ResponseWriter, r *http.Request) {
    filename := r.URL.Query().Get("file")
    http.ServeFile(w, r, "/var/www/"+filename) // Можно запросить ../../etc/passwd
}

Безопасный код:

import "path/filepath"

func serveFile(w http.ResponseWriter, r *http.Request) {
    filename := r.URL.Query().Get("file")
    // Очищаем путь и проверяем, что он внутри разрешенной директории
    safePath := filepath.Clean("/var/www/" + filename)
    if !strings.HasPrefix(safePath, "/var/www/") {
        http.Error(w, "Invalid path", http.StatusForbidden)
        return
    }
    http.ServeFile(w, r, safePath)
}

Рекомендация: Используйте filepath.Clean и проверяйте префикс пути. Не доверяйте пользовательскому вводу при работе с файловой системой.

5. Неправильная обработка ошибок и утечка информации

Go возвращает ошибки как значения, и разработчики часто возвращают их напрямую в HTTP-ответ, раскрывая внутреннюю структуру приложения.

Небезопасный код:

func connectDB() error {
    // ...
    return fmt.Errorf("failed to connect to database at 192.168.1.1: %w", err)
}

func handler(w http.ResponseWriter, r *http.Request) {
    err := connectDB()
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError) // Утечка IP и деталей
    }
}

Безопасный код:

func connectDB() error {
    // ...
    return fmt.Errorf("database connection error: %w", err)
}

func handler(w http.ResponseWriter, r *http.Request) {
    err := connectDB()
    if err != nil {
        log.Printf("Internal error: %v", err) // Логируем детали
        http.Error(w, "Internal Server Error", http.StatusInternalServerError) // Пользователю — общее сообщение
    }
}

Рекомендация: Никогда не возвращайте пользователю сырые ошибки. Логируйте их для себя, а клиенту отдавайте общие сообщения.

6.

Блоги

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