Sécurité des applications Go : top 10 des vulnérabilités
Go (Golang) est un langage compilé, typé statiquement, créé chez Google. Grâce à son support intégré de la concurrence, ses hautes performances et son modèle mémoire simple, il est devenu populaire pour créer des microservices, des passerelles API, des applications cloud et des outils DevOps. Cependant, comme tout autre langage, Go n'est pas à l'abri des erreurs de sécurité. Les développeurs se fient souvent à la "sécurité par défaut", ce qui conduit à l'apparition de vulnérabilités graves. Dans cet article, nous allons passer en revue le top 10 des problèmes de sécurité les plus critiques dans les applications Go et montrer comment les éviter.
1. Injections SQL (SQL Injection)
Bien que Go fournisse des pilotes de base de données puissants (database/sql), les développeurs commettent souvent l'erreur classique de concaténer des chaînes lors de la construction de requêtes.
Code vulnérable :
package main
import ( "database/sql" "fmt" _ "github.com/lib/pq")
func getUser(db *sql.DB, username string) { // Ne faites jamais cela ! query := fmt.Sprintf("SELECT * FROM users WHERE username = '%s'", username) rows, _ := db.Query(query) // ...}Solution : Utilisez des requêtes paramétrées (prepared statements).
func getUserSafe(db *sql.DB, username string) { query := "SELECT * FROM users WHERE username = $1" rows, err := db.Query(query, username) if err != nil { // Gestion de l'erreur } // ...}La paramétrisation échappe automatiquement les caractères spéciaux, rendant l'injection impossible.
2. Cross-Site Scripting (XSS)
Go est souvent utilisé pour la partie serveur des applications web. Si vous renvoyez une entrée utilisateur sans l'échapper, vous ouvrez la voie à des attaques XSS.
Code vulnérable (utilisation de fmt.Fprintf) :
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { name := r.URL.Query().Get("name") fmt.Fprintf(w, "<h1>Bonjour, %s!</h1>", name) // XSS !})Solution : Utilisez le package html/template pour un échappement automatique.
import "html/template"
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { tmpl := template.Must(template.New("index").Parse("<h1>Bonjour, {{.}}!</h1>")) name := r.URL.Query().Get("name") tmpl.Execute(w, name) // Sécurisé})Le moteur de templates html/template échappe automatiquement les contextes HTML, JavaScript et CSS.
3. Conditions de course (Race Conditions)
Go est réputé pour sa concurrence légère (goroutines et canaux). Cependant, une mauvaise utilisation des ressources partagées (comme les maps ou les compteurs) sans synchronisation conduit à des data races.
Code vulnérable :
var counter int
func increment() { for i := 0; i < 1000; i++ { counter++ // Pas atomique ! }}
func main() { go increment() go increment()}Solution : Utilisez des mutex (sync.Mutex) ou des opérations atomiques (sync/atomic).
import "sync"
var ( counter int mu sync.Mutex)
func increment() { for i := 0; i < 1000; i++ { mu.Lock() counter++ mu.Unlock() }}Lancez toujours vos tests avec le flag -race : go test -race.
4. Fuites de mémoire et de goroutines (Memory & Goroutine Leaks)
L'une des vulnérabilités les plus insidieuses en Go est celle des goroutines "oubliées". Si une goroutine se bloque indéfiniment (par exemple, en attendant des données d'un canal qui ne sera jamais fermé), cela entraîne une fuite de mémoire et un plantage de l'application.
Exemple de fuite :