Sicherheit von Go-Anwendungen: Die Top-10 Schwachstellen in der Praxis
Go (Golang) ist eine Sprache, die für ihre Leistung und integrierten Sicherheitsmechanismen bekannt ist. Wie jedes Werkzeug schützt sie Entwickler jedoch nicht vollständig vor Fehlern. Viele Schwachstellen entstehen nicht durch die Sprache selbst, sondern durch die falsche Nutzung ihrer Möglichkeiten: der Arbeit mit Goroutinen, Kanälen, Zeigern oder Drittanbieter-Bibliotheken.
In diesem Artikel analysieren wir die 10 häufigsten Schwachstellen in Go-Anwendungen. Jeder Punkt enthält ein reales Codebeispiel, eine Erklärung des Problems und einen praktischen Tipp zur Behebung. Das Material ist sowohl für Anfänger als auch für erfahrene Go-Entwickler nützlich, die ihr Wissen auffrischen möchten.
1. SQL-Injection durch String-Konkatenation
Der klassischste Fehler ist die Erstellung einer SQL-Abfrage durch Verkettung von Zeichenketten mit Benutzerdaten. Go bietet hervorragende Treiber (z. B. database/sql), schützt aber nicht vor dummen Fehlern.
Verwundbarer Code:
func getUser(db *sql.DB, username string) (*User, error) { query := "SELECT id, name FROM users WHERE username = '" + username + "'" rows, err := db.Query(query) // ...}Ein Angreifer kann admin' OR '1'='1 übergeben und alle Benutzer abrufen.
Lösung: Verwenden Sie immer parametrisierte Abfragen.
func getUserSafe(db *sql.DB, username string) (*User, error) { query := "SELECT id, name FROM users WHERE username = $1" row := db.QueryRow(query, username) // ...}2. Race Condition (Datenrennen) in Goroutinen
Go ist eine Sprache mit starker Unterstützung für Nebenläufigkeit, aber ohne Synchronisation führt der Zugriff auf gemeinsame Daten aus verschiedenen Goroutinen zu undefiniertem Verhalten.
Verwundbarer Code:
var counter int
func increment() { for i := 0; i < 1000; i++ { counter++ // nicht atomar }}
func main() { go increment() go increment() time.Sleep(time.Second) fmt.Println(counter) // Ergebnis unvorhersehbar}Lösung: Verwenden Sie Mutexe (sync.Mutex) oder atomare Operationen (sync/atomic).
var mu sync.Mutexvar counter int
func incrementSafe() { for i := 0; i < 1000; i++ { mu.Lock() counter++ mu.Unlock() }}3. Goroutine-Leak
Wenn eine gestartete Goroutine für immer blockiert (z. B. auf Daten in einem Kanal wartet, der nie geschlossen wird), „leckt“ sie und verbraucht Speicher.
Verwundbarer Code:
func process(jobs <-chan int) { for j := range jobs { go func(job int) { time.Sleep(time.Hour) // lange Arbeit fmt.Println(job) }(j) }}Wenn der Kanal jobs nicht geschlossen wird, hängen die Goroutinen für immer.
Lösung: Verwenden Sie Kontext (context.Context) zum Abbrechen von Operationen und kontrollieren Sie immer die Lebensdauer von Goroutinen.
func processSafe(ctx context.Context, jobs <-chan int) { for j := range jobs { select { case <-ctx.Done(): return default: go func(job int) { select { case <-time.After(time.Hour): fmt.Println(job) case <-ctx.Done(): return } }(j) } }}4. Falsche Fehlerbehandlung (panic/recover)
Die Verwendung von panic() zur Behandlung gewöhnlicher Fehler ist eine schlechte Praxis. Wenn ein panic nicht abgefangen wird, stürzt die Anwendung ab. Aber auch der übermäßige Gebrauch von recover() kann kritische Probleme verbergen.
Verwundbarer Code: