Безопасность 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-моду