Segurança: prevenção de header injection e CORS

1. Introdução à Segurança de Headers HTTP em Go

Headers HTTP são metadados que acompanham requisições e respostas, controlando caching, autenticação, tipo de conteúdo e muito mais. Por serem processados por servidores e navegadores, representam um vetor de ataque significativo quando manipulados indevidamente.

Três conceitos fundamentais precisam ser compreendidos:

  • Header Injection: inserção de caracteres de controle (\r\n) para adicionar headers maliciosos
  • Response Splitting: técnica que divide a resposta HTTP em duas, envenenando caches
  • Cache Poisoning: contaminação de proxies para servir conteúdo malicioso a outros usuários

Em Go, o pacote net/http oferece controle direto sobre headers, mas exige cuidado. Diferentemente de linguagens como PHP, Go não sanitiza automaticamente valores de headers — a responsabilidade é do desenvolvedor.

2. Header Injection: Mecanismos e Riscos

A injeção ocorre quando o servidor reflete dados do usuário em headers HTTP sem validação. O atacante insere sequências como \r\n (CRLF) para encerrar o header atual e iniciar novos.

Consequências graves incluem:
- Sequestro de sessão via cookies injetados
- Redirecionamento forçado para sites maliciosos
- Contaminação de cache em proxies intermediários

Exemplo vulnerável em Go:

package main

import (
    "fmt"
    "net/http"
)

func vulneravelHandler(w http.ResponseWriter, r *http.Request) {
    // PERIGO: reflete input do usuário diretamente no header
    nome := r.URL.Query().Get("nome")
    w.Header().Set("X-Custom-Header", "Olá, "+nome)
    fmt.Fprintf(w, "Header definido!")
}

func main() {
    http.HandleFunc("/", vulneravelHandler)
    http.ListenAndServe(":8080", nil)
}

Uma requisição como /?nome=João%0d%0aSet-Cookie:%20session=hacked injetaria um cookie malicioso.

3. Prevenção de Header Injection em Go

Validação rigorosa de entrada é a primeira linha de defesa:

import (
    "net/http"
    "strings"
)

func sanitizarHeader(valor string) string {
    // Remove caracteres CR (\r) e LF (\n)
    valor = strings.ReplaceAll(valor, "\r", "")
    valor = strings.ReplaceAll(valor, "\n", "")
    return valor
}

func handlerSeguro(w http.ResponseWriter, r *http.Request) {
    nome := sanitizarHeader(r.URL.Query().Get("nome"))
    w.Header().Set("X-Custom-Header", nome)
    w.WriteHeader(http.StatusOK)
}

Diferença crucial: http.Header.Set sobrescreve valores existentes, enquanto http.Header.Add pode acumular múltiplos valores — use Set para evitar duplicação maliciosa.

Middleware de proteção global:

func SecurityHeadersMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // Remove headers potencialmente perigosos da requisição
        r.Header.Del("X-Forwarded-For")
        r.Header.Del("X-Real-IP")
        next.ServeHTTP(w, r)
    })
}

Nunca confie em headers do cliente para decisões críticas como autenticação — sempre valide no backend.

4. CORS (Cross-Origin Resource Sharing) – Fundamentos

CORS é um mecanismo de segurança do navegador que controla requisições entre diferentes origens (domínio, protocolo, porta). Sem CORS, um site malicioso não poderia ler respostas de APIs de terceiros.

Headers principais:
- Access-Control-Allow-Origin: origens permitidas
- Access-Control-Allow-Methods: métodos HTTP autorizados
- Access-Control-Allow-Headers: headers personalizados
- Access-Control-Allow-Credentials: suporte a cookies/autenticação

Requisições preflight (OPTIONS) ocorrem quando:
- Método diferente de GET/POST/HEAD
- Headers não considerados "simples"
- Cookies ou credenciais envolvidas

5. Implementando CORS em Go: Abordagens Seguras

Configuração manual:

func corsMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        origem := r.Header.Get("Origin")

        // Validação dinâmica de origem
        if strings.HasSuffix(origem, ".meudominio.com") {
            w.Header().Set("Access-Control-Allow-Origin", origem)
        } else {
            w.Header().Set("Access-Control-Allow-Origin", "https://app.meudominio.com")
        }

        w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE")
        w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
        w.Header().Set("Access-Control-Allow-Credentials", "true")

        if r.Method == "OPTIONS" {
            w.WriteHeader(http.StatusNoContent)
            return
        }

        next.ServeHTTP(w, r)
    })
}

Usando biblioteca rs/cors:

import "github.com/rs/cors"

func main() {
    c := cors.New(cors.Options{
        AllowedOrigins:   []string{"https://app.meudominio.com"},
        AllowedMethods:   []string{"GET", "POST", "PUT"},
        AllowedHeaders:   []string{"Content-Type", "Authorization"},
        AllowCredentials: true,
        MaxAge:           86400,
    })

    handler := c.Handler(http.HandlerFunc(meuHandler))
    http.ListenAndServe(":8080", handler)
}

Armadilhas comuns:
- Usar * em produção — permite qualquer origem
- Expor headers sensíveis como Authorization sem necessidade
- Configurar AllowCredentials: true com * — inválido por especificação

6. Headers de Segurança Adicionais (Defesa em Profundidade)

Além de CORS, headers de segurança fortalecem a proteção:

func securityHeaders(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("X-Content-Type-Options", "nosniff")
        w.Header().Set("X-Frame-Options", "DENY")
        w.Header().Set("Content-Security-Policy", "default-src 'self'")
        w.Header().Set("Strict-Transport-Security", "max-age=63072000; includeSubDomains")
        w.Header().Set("X-XSS-Protection", "1; mode=block")
        next.ServeHTTP(w, r)
    })
}
  • X-Content-Type-Options: impede MIME sniffing
  • X-Frame-Options: bloqueia clickjacking
  • Content-Security-Policy: controla fontes de conteúdo
  • Strict-Transport-Security: força HTTPS
  • X-XSS-Protection: ativa filtro XSS do navegador

7. Testando e Auditando a Segurança de Headers

Teste manual com curl:

curl -I https://api.meudominio.com

Testes automatizados em Go:

func TestSecurityHeaders(t *testing.T) {
    handler := securityHeaders(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.WriteHeader(http.StatusOK)
    }))

    server := httptest.NewServer(handler)
    defer server.Close()

    resp, _ := http.Get(server.URL)

    assert.Equal(t, "nosniff", resp.Header.Get("X-Content-Type-Options"))
    assert.Equal(t, "DENY", resp.Header.Get("X-Frame-Options"))
}

Ferramentas como OWASP ZAP e SecLists ajudam a validar configurações em produção.

8. Conclusão e Checklist de Segurança

Headers HTTP são uma superfície de ataque crítica. Em Go, a prevenção de header injection e a configuração correta de CORS exigem atenção constante.

Checklist para deploy seguro:
- [ ] Validar e sanitizar todo input refletido em headers
- [ ] Usar http.Header.Set em vez de Add
- [ ] Configurar CORS com origens específicas, nunca *
- [ ] Adicionar headers de segurança (CSP, HSTS, X-Frame-Options)
- [ ] Testar configurações com httptest e scanners OWASP
- [ ] Revisar periodicamente as políticas de CORS

A segurança é um processo contínuo — mantenha-se atualizado com as melhores práticas e vulnerabilidades emergentes.

Referências