Por que até desenvolvedores Go caem nas armadilhas dos hackers?
\n\nGo é famoso por sua velocidade, simplicidade e ferramentas de segurança integradas. Mas se consolar com a frase "eu programo em Go, então estou seguro" é uma ilusão perigosa. SQL Injection e ataques XSS não são relíquias do passado. Eles ainda derrubam sites e vazam bancos de dados diariamente, e Go não é exceção.
\n\nO problema é que Go oferece ferramentas poderosas ao desenvolvedor, mas não as utiliza por você. Você pode acidentalmente concatenar uma consulta SQL usando fmt.Sprintf ou exibir um comentário de usuário sem escape — e um invasor terá acesso ao seu banco de dados ou roubará os cookies dos visitantes.
Neste artigo, vamos abordar os dois tipos de ataque mais perigosos — SQL Injection e Cross-Site Scripting (XSS) — e mostrar como preveni-los usando Go nativo e bibliotecas populares. Você não receberá apenas teoria, mas sim modelos de código prontos que pode inserir no seu projeto ainda hoje.
\n\nSQL Injection em Go: como ocorrem e como eliminá-los pela raiz
\n\nErro típico: concatenação de strings
\n\nImagine que você tem um endpoint para obter um usuário pelo ID. A forma mais ingênua (e perigosa) de escrever a consulta é assim:
\n\npackage main\n\nimport (\n "database/sql"\n "fmt"\n "log"\n _ "github.com/lib/pq"\n)\n\nfunc getUserByID(db *sql.DB, userID string) {\n // PERIGOSO! Concatenação de strings\n query := fmt.Sprintf("SELECT id, name, email FROM users WHERE id = '%s'", userID)\n rows, err := db.Query(query)\n if err != nil {\n log.Fatal(err)\n }\n defer rows.Close()\n // ... processamento\n}\n\nSe um invasor passar para userID a string 1' OR '1'='1, a consulta se torna:
SELECT id, name, email FROM users WHERE id = '1' OR '1'='1'\n\nPronto — ele obterá todos os usuários do banco de dados. E se ele digitar 1'; DROP TABLE users; --, você perderá a tabela.
Solução: consultas parametrizadas (prepared statements)
\n\nO driver Go para PostgreSQL, MySQL e SQLite suporta prepared statements. Eles separam o código SQL dos dados, e o driver escapa automaticamente os caracteres perigosos.
\n\nfunc getUserByIDSafe(db *sql.DB, userID string) {\n // SEGURO: consulta parametrizada\n query := "SELECT id, name, email FROM users WHERE id = $1"\n rows, err := db.Query(query, userID)\n if err != nil {\n log.Fatal(err)\n }\n defer rows.Close()\n // ... processamento\n}\n\nObserve o $1 — é um placeholder para o primeiro argumento. No MySQL, usa-se o símbolo ?. Agora, mesmo que userID contenha 1' OR '1'='1, o driver interpretará isso como um valor string, e não como parte do código SQL.
Trabalhando com o pacote sqlx
\n\nA biblioteca github.com/jmoiron/sqlx torna o código ainda mais limpo. Ela suporta parâmetros nomeados e escaneamento automático para structs:
import "github.com/jmoiron/sqlx"\n\ntype User struct {\n ID int `db:"id"`\n Name string `db:"name"`\n Email string `db:"email"`\n}\n\nfunc getUserByIDSqlx(db *sqlx.DB, userID string) (*User, error) {\n user := &User{}\n // Parâmetro nomeado :id\n err := db.Get(user, "SELECT * FROM users WHERE id = :id", sqlx.Named("id", userID))\n if err != nil {\n return nil, err\n }\n return user, nil\n}\n\nImportante: mesmo se você usar uma ORM como GORM ou Ent, certifique-se de que ela esteja configurada para usar prepared statements. Algumas versões antigas de ORMs permitiam consultas "cruas".
\n\nXSS em Go: quando seu site atira no próprio pé
\n\nComo ocorre um ataque XSS
\n\nXSS (Cross-Site Scripting) ocorre quando você insere conteúdo do usuário na página sem fazer o escape. Um at