Pular para o conteúdo
>_ITDITDPlataforma de Segurança Web

Guias de Segurança

O que é a falsificação de X-Forwarded-For (XFF) — a armadilha da configuração de proxy confiável

X-Forwarded-For (XFF) é um cabeçalho que os clientes podem forjar — scanners escondem sondagens de SQLi/XSS nele, e uma configuração de 'confiar em todos os proxies' deixa o valor falso chegar ao seu app. Um estudo de caso defensivo: por que a defesa em profundidade resistiu e a correção real de proxy confiável.

Publicado 2026-06-08 12 min de leitura

X-Forwarded-For (XFF) é o cabeçalho que diz ao seu app "o IP real do visitante é este" quando o tráfego passa por um proxy. O detalhe: o cliente pode forjar esse valor livremente. Eis um caso comum de dev independente — "fomos atingidos por um scan de falsificação de XFF" — transformado em lição, anonimizado, sem passos de ataque reproduzíveis.

Arquivo do caso
Tipo
Falsificação de X-Forwarded-For + sondagem de injeção (scan automatizado)
Impacto
Nenhum (deter pela defesa em profundidade)
Como surgiu
Requisições de ataque deram 500; uma enxurrada de e-mails de exceção
Escudos que resistiram
① placeholders (valores vinculados) ② validação de charset do BD
A lacuna deixada
"confiar em todos os proxies" — sem proxy de fato na frente
Resposta
Higienizar cabeçalhos de IP na fronteira (patch) → corrigir a config de proxy confiável (raiz)
0
Impacto real (vazamento/adulteração)
2
Escudos que resistiram
todos
Proxies confiados (curinga)
fronteira
A correção real

Primeiro, separe: "ataque" ou "erro meu"?

A regra é: não pule para "ataque!" ao ver erros. Se você acabou de mudar a configuração, pondere "talvez minha mudança fez isso" com igual seriedade.

1

Suspeite da sua própria mudança

Hipótese: uma mudança recente de configuração fez os usuários relogarem repetidamente. Mas isso não explica as sequências de bytes malformados no valor armazenado → rejeitada.
2

Deixe o valor ser a evidência

O que apareceu foram aspas codificadas em URL múltiplas vezes e sequências de bytes longas demais (redundantes) — não valores que usuários reais ou proxies produzem → carga maliciosa confirmada.
3

Direcionado ou um scan?

Menos um ataque direcionado, mais provavelmente um scanner automatizado varrendo a internet inteira indiscriminadamente.

A visão deste site: ao raciocinar de trás para frente a partir dos sintomas, mate hipóteses com dados

"A coisa que acabei de mexer parece suspeita" é um bom ponto de partida. Mas se a evidência (aqui, o próprio valor) a refuta, abandone-a. Forme várias hipóteses e derrube-as uma a uma com dados, não com palpites — o erro de diagnóstico cai acentuadamente.

O que era o ataque: sondagens de injeção pegando carona em um XFF falsificado

O bot empacota uma string de sondagem no XFF "em vez de um IP", com só um IP de aparência normal no fim. O objetivo é uma coisa só —

"Este app confia no valor do XFF e o mistura cru em uma consulta de BD, ou o imprime direto no HTML?"

Se o app é desleixado, a injeção de SQL ou o XSS chega via XFF. Ele abusa do "lugar que carrega um IP" como um ponto de injeção de strings arbitrárias.

Por que o impacto foi zero: dois escudos

Escudo ① valores vinculados (placeholders)

O valor do IP foi passado via um placeholder como dado, não concatenado em string. A entrada não consegue cruzar para "comando", então o SQLi não pode se formar por design — um dos maiores benefícios de usar um framework.

Escudo ② validação de charset do BD

Os bytes malformados (UTF-8 longo demais etc.) são sequências inválidas que o charset da coluna não consegue armazenar. O BD rejeitou a gravação e lançou erro — o ataque falhou e foi detectado.

Tudo o que aconteceu: as próprias requisições do atacante deram 500 e uma enxurrada de e-mails de exceção saiu. Nenhum vazamento, nenhuma adulteração, nenhum login não autorizado. Um caso de manual de defesa em profundidade funcionando como pretendido.

O problema real: confiar em todos os proxies

Se parássemos aqui seria uma história bonitinha — mas uma coisa não pode ser ignorada. Por que o XFF falsificado chegou ao tratamento de IP afinal?

✗ Confiar em todos os proxies (curinga)

XFF falso do cliente → adotado como o "IP real" → logs, sessões, verificações de IP envenenados

✓ Confiar em nada (padrão)

XFF falso ignorado → IP real do socket é usado → a falsificação não tem efeito

Com os proxies confiáveis definidos como curinga, o XFF falsificado de qualquer um passa como o 'IP real'. Sem proxy na frente, 'confiar em nada' é o correto.

Esta configuração não tinha nenhum proxy reverso ou CDN — o servidor web estava diretamente exposto. Então o XFF nunca precisou ser confiado, mas estava definido como "confiar em todos". "Só permita tudo" é o atalho clássico de tempo de desenvolvimento que sobrevive até a produção.

Ponto cego: a falsificação de XFF não é só sobre injeção

Qualquer coisa que confie no IP do cliente é envenenada: burla de rate-limit, burla de lista de permissão de IP, evasão de banimento, logs de auditoria forjados. Mesmo com defesas de injeção sólidas como rocha, isto é um problema separado. Inventarie "onde eu confio no IP?" e verifique a base dessa confiança (= sua config de proxy).

Correção: higienizar na fronteira → definir a confiança corretamente

1

Higienize os cabeçalhos de IP na fronteira (patch, instantâneo)

Antes de a lógica de proxy confiável rodar, divida o XFF, valide cada parte como "isto é um IP válido?", mantenha só IPs válidos; se nenhum, descarte o cabeçalho por completo. Os IPs reais dos usuários reais passam, então nenhum impacto; as strings de sondagem são descartadas.
2

Defina os proxies confiáveis corretamente (raiz)

Largue o curinga. Sem proxy na frente → confie em nada. A confiança no proxy também afeta a detecção de http/https, então mudanças podem causar laços de redirecionamento — verifique em um ambiente primeiro.
3

Não confunda 'IP válido' com 'IP real'

Higienizar é higiene. Um IP bem-formado ainda pode ser um valor forjado. A confiança no IP real vem apenas da config de proxy do passo 2.

Verifique: após corrigir, repita o ataque para confirmar

Transforme "acho que corrigi" em "definitivamente parou". ① aplique a um ambiente só → ② envie você mesmo o mesmo cabeçalho malformado → ③ confirme que as contagens de erro não sobem → ④ confirme que o acesso normal ainda funciona → ⑤ implante em todo lugar. A implantação em etapas evita acidentes.

Continuação: bloqueie o cabeçalho de IP, seja atingido no User-Agent (a armadilha do bate-toupeira)

Dezenas de minutos após adicionar a higienização, o mesmo tipo de erro voltou — em uma coluna diferente desta vez: user_agent, não ip_address. Bloqueado no cabeçalho de IP, o atacante moveu a mesma carga para o cabeçalho User-Agent. Isso força uma reformulação.

Entradas: XFF · User-Agent · Referer · … (infinitas)
↓ mas as que de fato são armazenadas são
Sink: só ip_address e user_agent na tabela de sessões
↓ então as entradas a higienizar são
só os cabeçalhos que alimentam essas duas (cabeçalhos de IP + User-Agent)
Matar 'qual cabeçalho é perigoso' um a um é um jogo de bate-toupeira. Raciocine de trás para frente a partir de 'onde a entrada não confiável é armazenada no fim (o sink)' e as entradas a selar ficam finitas.

A pergunta certa não é "qual cabeçalho é perigoso?", mas "em qual coluna de BD a entrada não confiável aterrissa no fim?" Se os únicos sinks são ip_address e user_agent, higienizar as entradas que os alimentam (cabeçalhos de IP + User-Agent, mais Referer por garantia) deter os erros derivados de sessão por design — outros cabeçalhos não armazenados não podem ser usados como pivô. User-Agents legítimos são ASCII/UTF-8 válido, então retirar só os bytes inválidos não tem impacto.

A visão deste site: raciocine de trás para frente a partir do sink (não sele entradas uma a uma)

Selar cabeçalhos um de cada vez é bate-toupeira — as entradas que um atacante pode usar são infinitas. Raciocine de trás para frente a partir do sink (a coluna armazenada / saída) e enumere toda entrada que chega a ele, de uma vez. Como "bloqueie o IP, seja atingido no User-Agent" nos ensinou: quando você corrige um cabeçalho, sele ao mesmo tempo as outras entradas que chegam ao mesmo sink. E de novo — repetimos o segundo ataque e confirmamos que parou antes de implantar.

E mais uma vez: o filtro de entrada foi burlado — defenda na saída

Logo após selar também o User-Agent, o mesmo erro da coluna ip_address voltou. A higienização de entrada estava implantada, e repetir o mesmo cabeçalho malformado em um autoteste não o reproduzia — mas a produção continuava recorrendo. Este é o cerne.

O motivo: a lógica do framework para "resolver o IP do cliente" é internamente complexa — múltiplas notações de cabeçalho (X-Forwarded-For, Forwarded, cabeçalhos customizados), combinações com configurações de confiança de proxy, ordem de parsing e cache. Em algum lugar desses caminhos internos, um valor contaminado ressurgiu como o "IP do cliente" por uma rota que não havíamos higienizado. O ponto de entrada não consegue pegar tudo.

✗ Defender na entrada

Higienizar cabeçalhos da requisição → o IP do cliente ressurge por outro caminho interno → burlado

✓ Defender na saída

Verificar logo antes da gravação no BD → o único gargalo por onde tudo passa → sem burla

Um filtro de entrada (verificando valores recebidos) pode ser burlado por caminhos internos de resolução. A saída (logo antes da gravação no BD) é o único gargalo por onde tudo passa — nada escapa.

Então invertemos e defendemos na saída (logo antes da gravação no BD). Subclasse a classe de armazenamento de sessão e sobrescreva só as partes que produzem o valor (conceito): o IP armazenado vira null a menos que seja um IP válido; o User-Agent armazenado tem os bytes inválidos retirados e o comprimento limitado. Por mais que a resolução do IP do cliente se comporte internamente, a verificação sempre roda logo antes da gravação, então nada escapa. Martelando ambas as rotas de ataque dezenas de vezes cada, as contagens de erro não subiram e as sessões legítimas continuaram funcionando.

A visão deste site: a última linha de defesa é a saída (ponto de uso)

A máxima de segurança é "não confie na entrada; sempre valide/escape no ponto de uso (a saída)." Verificações de entrada ajudam a rejeitar cedo, mas informação que o framework resolve/deriva internamente (como o IP do cliente) pode passar pela entrada e ressurgir. Validar/escapar logo antes de um sink perigoso — BD, HTML, um comando — é o gargalo por onde tudo passa, e essa é a última linha de defesa. Faça entrada e saída como duas coisas separadas.

A maior lição: ser ruidosamente rejeitado é mais seguro

Aqui o ataque falhou e foi tornado visível como uma enxurrada de notificações de erro. Ironicamente, isso não é ruim: ser ruidosamente rejeitado vence ser silenciosamente deixado passar. Não descarte as notificações de erro como "ruído" — use-as como um sensor de anomalia. Não pare em "bloqueamos o impacto"; corrija "por que chegou às entranhas". Essa é a postura deste site.

Leia a seguir

FAQ

QPosso confiar no X-Forwarded-For?
A

Não como está. XFF é um cabeçalho que o cliente pode definir livremente — um atacante pode forjar um IP de aparência real ou uma string de sondagem de injeção. O único XFF confiável é um adicionado por um proxy que VOCÊ colocou na frente e confia explicitamente.

QE se eu não uso um proxy reverso ou CDN?
A

Não confie em XFF nenhum (deixe o padrão do framework). Se não há proxy na frente mas você 'confia em todos os proxies', qualquer um pode alimentar seu app com um IP falso. Sem proxy no caminho → confiar em nada é a configuração correta.

QHigienizar o cabeçalho de IP basta?
A

Isso deter as sondagens de injeção e a enxurrada de erros, mas é higiene (estancar o sangramento). Um 'IP de aparência válida' não é um 'IP real'. A confiança no IP real do cliente deve vir da configuração de proxy confiável, não da higienização.

QCorrigi o cabeçalho de IP mas o mesmo erro voltou. Por quê?
A

A mesma carga foi movida para outro ponto de entrada (ex.: o cabeçalho User-Agent) e chegou à mesma coluna armazenada. Corrigir cabeçalhos um a um é um jogo de bate-toupeira. Raciocine de trás para frente a partir de 'a qual coluna de BD / saída a entrada não confiável chega no fim?' e higienize juntas todas as entradas que a alimentam (cabeçalhos de IP + User-Agent etc.).