Безопасность 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. Неправильная обработка ошибок
Игнорирование ошибок (_) может привести к непредсказуемому поведению, утечкам памяти или раскрытию информации.