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

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

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

Начать курс

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

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

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

Самая распространенная уязвимость веб-приложений. Она возникает, когда пользовательский ввод напрямую конкатенируется в SQL-запрос.

Опасный код:

username := r.URL.Query().Get("username")
query := fmt.Sprintf("SELECT * FROM users WHERE name='%s'", username)
rows, err := db.Query(query)

Безопасный код (используем параметризованные запросы):

username := r.URL.Query().Get("username")
query := "SELECT * FROM users WHERE name=$1"
rows, err := db.Query(query, username)

Совет: Всегда используйте плейсхолдеры ($1, ? или @p1) в зависимости от вашей базы данных. Никогда не стройте запросы через fmt.Sprintf с пользовательскими данными.

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

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

Опасный код:

tmpl := template.Must(template.New("page").Parse("<h1>{{.}}</h1>"))
tmpl.Execute(w, r.URL.Query().Get("name")) // <script>alert('xss')</script>

Встроенный пакет html/template автоматически экранирует вывод, поэтому в стандартном случае вы защищены. Но будьте осторожны с функциями template.HTML — они отключают экранирование.

Безопасный подход: Используйте text/template только для текстовых данных, а для веба — html/template. Избегайте использования template.HTML без крайней необходимости.

3. Race Conditions (Гонка данных)

Go активно использует горутины, и если несколько горутин одновременно читают и пишут в одну переменную без синхронизации, возникает гонка данных. Это может привести к некорректному состоянию приложения и утечке данных.

Опасный код:

var counter int
for i := 0; i < 1000; i++ {
    go func() {
        counter++ // Небезопасно!
    }()
}

Безопасный код (используем мьютекс):

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

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

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

Уязвимость позволяет злоумышленнику прочитать файлы за пределами разрешенной директории, используя символы ../.

Опасный код:

filename := r.URL.Query().Get("file")
data, _ := os.ReadFile("/data/" + filename) // /data/../../etc/passwd

Безопасный код (очистка пути):

filename := r.URL.Query().Get("file")
cleanPath := filepath.Clean("/data/" + filename)
if !strings.HasPrefix(cleanPath, "/data/") {
    http.Error(w, "Invalid path", http.StatusBadRequest)
    return
}
data, _ := os.ReadFile(cleanPath)

Совет: Используйте filepath.Clean для нормализации пути и обязательно проверяйте, что результат начинается с разрешенного префикса.

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

Если вы выводите пользовательский ввод в лог без экранирования, злоумышленник может подделать записи лога, добавив символы перевода строки (\n).

Опасный код:

log.Printf("User input: %s", r.URL.Query().Get("input"))
// В логе появится фальшивая запись: "User input: \n[ERROR] Critical failure"

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

safeInput := strings.ReplaceAll(r.URL.Query().Get("input"), "\n", "\\n")
log.Printf("User input: %s", safeInput)

Совет: Всегда экранируйте или заменяйте управляющие символы перед записью в лог.

6. Command Injection (Инъекция команд)

Передача пользовательского ввода в системные команды без валидации может привести к выполнению произвольных команд.

Опасный код:

cmd := exec.Command("ping", "-c", "1", r.URL.Query().Get("host"))
// ping -c 1 8.8.8.8; rm -rf /

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

host := r.URL.Query().Get("host")
if !isValidHost(host) {
    http.Error(w, "Invalid host", http.StatusBadRequest)
    return
}
cmd := exec.Command("ping", "-c", "1", host)

Совет: Никогда не передавайте пользовательский ввод в exec.Command без строгой валидации. Используйте белые списки разрешенных значений.

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

Игнорирование ошибок (_) может привести к непредсказуемому поведению, утечкам памяти или раскрытию информации.

Блоги

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