Segurança de aplicações Go: top 10 vulnerabilidades na prática
Go (Golang) — uma linguagem conhecida por seu desempenho e mecanismos de segurança integrados. No entanto, como qualquer ferramenta, ela não protege completamente o desenvolvedor de erros. Muitas vulnerabilidades surgem não por causa da linguagem em si, mas pelo uso incorreto de seus recursos: trabalho com goroutines, canais, ponteiros ou bibliotecas de terceiros.
Neste artigo, analisaremos as 10 vulnerabilidades mais comuns em aplicações Go. Cada item contém um exemplo real de código, explicação do problema e uma dica prática para resolvê-lo. O material será útil tanto para desenvolvedores Go iniciantes quanto para engenheiros experientes que desejam atualizar seus conhecimentos.
1. SQL injection através de concatenação de strings
O erro mais clássico — formar uma consulta SQL concatenando strings com dados do usuário. O Go oferece excelentes drivers (por exemplo, database/sql), mas não protege contra erros bobos.
Código vulnerável:
func getUser(db *sql.DB, username string) (*User, error) { query := "SELECT id, name FROM users WHERE username = '" + username + "'" rows, err := db.Query(query) // ...}Um invasor pode passar admin' OR '1'='1 e obter todos os usuários.
Solução: Sempre use consultas parametrizadas.
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 (condição de corrida) em goroutines
Go é uma linguagem com forte suporte a concorrência, mas sem sincronização, o acesso a dados compartilhados de diferentes goroutines leva a um comportamento indefinido.
Código vulnerável:
var counter int
func increment() { for i := 0; i < 1000; i++ { counter++ // não atômico }}
func main() { go increment() go increment() time.Sleep(time.Second) fmt.Println(counter) // resultado imprevisível}Solução: Use mutexes (sync.Mutex) ou operações atômicas (sync/atomic).
var mu sync.Mutexvar counter int
func incrementSafe() { for i := 0; i < 1000; i++ { mu.Lock() counter++ mu.Unlock() }}3. Vazamento de goroutines (goroutine leak)
Se uma goroutine iniciada fica bloqueada para sempre (por exemplo, aguardando dados em um canal que nunca será fechado), ela "vaza", consumindo memória.
Código vulnerável:
func process(jobs <-chan int) { for j := range jobs { go func(job int) { time.Sleep(time.Hour) // trabalho longo fmt.Println(job) }(j) }}Se o canal jobs não for fechado, as goroutines ficarão pendentes para sempre.
Solução: Use contexto (context.Context) para cancelar operações e sempre controle o tempo de vida das goroutines.
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. Tratamento incorreto de erros (panic/recover)
Usar panic() para tratar erros comuns é uma má prática. Se o panic não for capturado, a aplicação irá cair. Mas o uso excessivo de recover() também pode ocultar problemas críticos.
Código vulnerável: