- Como um protocolo de proxy para encaminhar requisições por socket a backends de longa execução, pode ser adotado quase sem alterar a estrutura existente dos handlers HTTP
- O proxy reverso com HTTP/1.1 facilita divergências de interpretação dos limites das mensagens entre implementações, o que continua gerando problemas graves de segurança, como desync e request smuggling
- O FastCGI fornece framing de mensagens claro desde 1996 e separa estruturalmente os headers do cliente das informações confiáveis adicionadas pelo proxy
- No Go,
net/http/fcgipreencheREMOTE_ADDRemRequest.RemoteAddre também reflete o uso de HTTPS emRequest.TLS, permitindo lidar com a transmissão de informações confiáveis sem middleware adicional - Há limitações, como falta de suporte a WebSockets, um ecossistema de ferramentas fraco e menor throughput em algumas cargas de trabalho, mas, se WebSockets não forem necessários e o desempenho for suficiente, ele ainda parece uma opção prática
Posição do FastCGI e forma de adoção
- O FastCGI não é usado apenas no modelo de executar um processo por arquivo; ele também pode ser usado como protocolo entre proxy e backend para enviar requisições a daemons de longa execução via socket TCP ou UNIX
- Em Go, basta importar o pacote
net/http/fcgie trocarhttp.Serveporfcgi.Serve- Os handlers existentes continuam usando
http.ResponseWriterehttp.Request - O restante da estrutura da aplicação também permanece igual
- Os handlers existentes continuam usando
- Principais proxies como Apache, Caddy, nginx e HAProxy suportam backends FastCGI, e a configuração tende a ser simples
Problemas de parsing ao usar HTTP como protocolo de backend
- O reverse proxy com HTTP se aproxima de um campo minado de segurança, e continuam surgindo problemas como a vulnerabilidade de desync no proxy de mídia do Discord, que permitia espiar anexos privados
- O HTTP/1.1 parece, à primeira vista, um protocolo de texto simples, mas há formas demais de representar a mesma mensagem e muitas exceções de tratamento, o que facilita diferenças de interpretação entre implementações
- O maior problema é que mensagens HTTP não têm framing explícito
- O fim da mensagem é descrito pela própria mensagem de várias maneiras
- Implementações diferentes podem interpretar de forma diferente onde uma mensagem termina e a próxima começa
- Essas divergências formam a base de HTTP desync attacks, ou request smuggling, criando problemas graves de segurança quando o proxy reverso e o backend entendem os limites das mensagens de forma diferente
- Continuar corrigindo diferenças entre parsers dificilmente é uma solução fundamental
- James Kettle continua encontrando novos tipos
- Depois de identificar casos adicionais no ano passado, chegou a usar a expressão "HTTP/1.1 must die"
Tratamento dos limites de mensagem em FastCGI e HTTP/2
- O HTTP/2, se usado de forma consistente entre proxy e backend, pode resolver problemas de desync ao tornar claros os limites das mensagens
- O FastCGI já oferece essa separação clara de limites desde 1996, com um protocolo mais simples
- O nginx oferece suporte a backends FastCGI desde sua primeira versão, mas o suporte a backend HTTP/2 só foi adicionado no fim de 2025
- No Apache, o suporte a backend HTTP/2 ainda permanece em estado "experimental"
O problema dos headers não confiáveis e a forma de separação do FastCGI
- O problema não é só desync: o HTTP também carece de um meio robusto para transportar dados que o proxy precisa encaminhar com confiança, como o IP real do cliente, o nome do usuário autenticado pelo proxy ou informações do certificado do cliente em mTLS
- Na prática, essas informações acabam sendo colocadas em headers HTTP, mas não existe separação estrutural entre os dados confiáveis adicionados pelo proxy e os headers não confiáveis enviados pelo cliente
- Headers como
X-Real-IPsão usados com frequência para transmitir o IP real do cliente, mas isso só é seguro se o proxy remover completamente qualquer header pré-existente com esse nome, inclusive variações de maiúsculas e minúsculas, antes de adicioná-lo de novo - Essa abordagem é um terreno muito perigoso, com muitos caminhos para o backend acabar confiando em dados inseridos por um atacante
- O proxy precisa apagar não apenas
X-Real-IP, mas qualquer header usado para esse fim - Por exemplo, o middleware Chi define o IP real do cliente verificando
True-Client-IPprimeiro e só usaX-Real-IPse ele não existir- Mesmo que o proxy trate
X-Real-IPcorretamente, um atacante ainda pode causar problemas ao enviarTrue-Client-IP
- Mesmo que o proxy trate
- O FastCGI separa as informações adicionadas pelo proxy e os headers do cliente por separação de domínios
- Ambos são enviados como listas de parâmetros chave/valor, mas nomes de headers HTTP recebem o prefixo
HTTP_ - Assim, não se cria uma estrutura em que um header enviado pelo cliente possa ser interpretado como dado confiável do proxy
- Ambos são enviados como listas de parâmetros chave/valor, mas nomes de headers HTTP recebem o prefixo
Tratamento de informações confiáveis com FastCGI no Go
- O FastCGI define parâmetros padrão como
REMOTE_ADDRpara transmitir o IP real do cliente - O
net/http/fcgido Go preenche automaticamente esse valor emhttp.Request.RemoteAddr, funcionando sem middleware adicional - O proxy também pode transmitir por parâmetros não padronizados informações como uso de HTTPS, a cipher suite TLS negociada e o certificado do cliente
- No Go, quando a requisição usou HTTPS, o campo
TLSdeRequesté definido automaticamente com um valor diferente de nil- Mesmo vazio, isso é útil para verificar a imposição de HTTPS
- Com
fcgi.ProcessEnv, é possível acessar o conjunto completo de parâmetros confiáveis enviados pelo proxy
Por que a adoção foi lenta e quais são os limites práticos
- Se o FastCGI é melhor, por que não é amplamente usado? Parece que isso se deve tanto ao ar datado do próprio nome quanto à baixa percepção sobre os problemas de segurança do reverse proxy com HTTP
- A Watchfire já tratava de ataques de desync em 2005 e alertava que a solução não seria simples, mas esses ataques ficaram sem receber atenção adequada por mais de dez anos
- O FastCGI continua viável em uso real, e a SSLMate o utiliza em produção há mais de dez anos
- Ainda assim, por ser uma tecnologia antiga, ele tem fraquezas
- Não foi atualizado para oferecer suporte a WebSockets
- O ecossistema de ferramentas é limitado
- Por exemplo, o curl suporta até FTP, Gopher e SMTP, mas não consegue enviar requisições FastCGI
- Em benchmarks de um servidor FastCGI em Go atrás de vários reverse proxies, algumas cargas de trabalho tiveram throughput menor que HTTP/1.1 ou HTTP/2
- Isso é visto menos como um limite do protocolo em si e mais como resultado de caminhos de código FastCGI menos otimizados do que os de HTTP
Julgamento final
- Se WebSockets não forem necessários e o desempenho atual for suficiente, o FastCGI continua sendo uma opção válida
- Mesmo que surja um gargalo, parece melhor adicionar hardware do que aceitar a complexidade e o pesadelo de segurança do reverse proxy com HTTP
2 comentários
É impressionante o comentário sobre FastCGI do Twisted que encontrei nos comentários do Lobsters: https://web.archive.org/web/20160723091923/…
Comentários do Hacker News
Concordo com a ideia do texto. Para esse tipo de uso, FastCGI me parece melhor que HTTP
Também queria divulgar um protocolo chamado WAS (Web Application Socket). Há 16 anos, no trabalho, senti que até o FastCGI ainda não era bom o suficiente e acabei projetando um eu mesmo
Em vez de framing no socket principal, ele usa 1 socket de controle e 2 pipes para os corpos brutos de requisição/resposta, e tanto o app WAS quanto o servidor web podem usar
splice()nos pipesNão precisa de framing, permite cancelar requisições e foi feito para que os três file descriptors possam sempre ser recuperados
Usei isso por anos em aplicações internas e em ambientes de hospedagem web, e também escrevi um PHP SAPI por conta própria. Bastante site roda internamente sobre WAS
Tudo é open source
library: https://github.com/CM4all/libwas
documentation: https://libwas.readthedocs.io/en/latest/
non-blocking library: https://github.com/CM4all/libcommon/tree/master/src/was/asyn...
our web server: https://github.com/CM4all/beng-proxy
WebDAV: https://github.com/CM4all/davos
PHP fork with WAS SAPI: https://github.com/CM4all/php-src
HTTP serve para transportar dados entre as duas pontas, como navegador e servidor, enquanto FastCGI serve para processar esses dados entre o servidor e a aplicação
Dei uma passada rápida no texto e parece que o autor escreve de um jeito que confunde os dois como se fossem substituíveis. Na prática, não são nem um pouco
Para referência, eu também usei fcgi por 10 anos em serviços web voltados a clientes
Este texto é interessante justamente pelo que deixa de fora
Quando o debate FastCGI vs. SCGI vs. HTTP estava pegando fogo, fundei uma startup Web2.0 e montei a stack de frontend por conta própria, e no fim HTTP venceu pela simplicidade
Se você já precisa lidar com HTTP no gateway, usar HTTP também dali para frente evitava adicionar outro protocolo à stack, e isso tornava muito fácil colocar várias camadas de reverse proxy ou separar preocupações transversais como autenticação, sessão, terminação de SSL e filtragem de DDoS em servidores com papéis distintos
No ambiente de desenvolvimento, dava para conectar direto ao app server por HTTP; em produção, o reverse proxy cuidava de SSL, autenticação e detecção de abuso, reaproveitando exatamente o mesmo app server
Na época também pesava o fato de que o nginx era muito mais rápido e estável do que a maioria dos módulos FastCGI/SCGI. Começamos com
HTTP -> Lighttpd -> FastCGI -> Django, mas usar só nginx era muito mais rápidoUsar HTTP funcionava como uma espécie de End-to-End Principle da web. A ideia é que a rede e o protocolo devem ser indiferentes ao conteúdo transportado, e que a lógica da aplicação deve ficar nas pontas, não em nós de rede que filtram ou redirecionam
Ainda assim, o ponto central do texto é que, do ponto de vista de segurança, muitas vezes é melhor seguir o princípio do menor privilégio. É preciso deixar passar só as comunicações previstas por allowlist para não acabar contribuindo sem querer para comprometimentos em outros pontos
No fim, existe uma tensão entre os dois. E2E dá flexibilidade, mas essa flexibilidade também amplia o espaço para abuso; PoLP dá segurança, mas limita o sistema ao que foi planejado, dificultando a adaptação a novos requisitos
[1] https://en.wikipedia.org/wiki/End-to-end_principle
[2] https://en.wikipedia.org/wiki/Principle_of_least_privilege
Se um gateway intermediário multiplexa várias requisições HTTP em um único canal HTTP diferente, esse canal vai direto até o serviço que está escutando e não é demultiplexado antes do socket da aplicação, então isso quebra de forma fundamental a lógica end-to-end de várias maneiras
Essa analogia só se sustenta minimamente quando a simetria de conexão 1:1 é preservada
Na minha visão, todas as vulnerabilidades de reverse proxy surgiram diretamente de violações do end-to-end
Se a analogia fosse correta, a entrega de SMTP passando por vários MX também deveria ser end-to-end, mas não é, e ali aparecem muitos problemas parecidos com os de reverse proxy, como desync de fronteira de mensagem
Entendo a intenção de mapear requisições HTTP para mensagens, mas isso desmorona rápido por causa da semântica real de TCP e HTTP e de toda a infinidade de detalhes do protocolo
O princípio end-to-end não permite tratar a semântica de forma frouxa. Ele exige disciplina muito rígida no gerenciamento de estado e nos limites da camada de transporte. Algo meio parecido com end-to-end não é end-to-end
Por exemplo, nem havia multiplexing antes do HTTP 2.0, então usar HTTP puro entre reverse proxy e backend é bastante desperdiçador
Também há problemas de segurança. Parsers diferentes podem nem concordar sobre onde termina a fronteira de uma requisição
O Google também, há muito tempo, usa seu protocolo próprio Stubby entre o servidor web de frontend e as aplicações, em vez de HTTP direto
É muito mais rápido que o wire protocol de HTTP e tem mais recursos. Para a maioria das empresas isso é exagero, mas em grande escala o custo de criar outro wire protocol e o tooling em volta dele se justifica plenamente
O httpd também foi, em algum momento, na direção de tornar a configuração mais difícil, e eu abandonei quando mudaram de repente o formato de configuração
Eu até poderia ter me adaptado, mas em vez disso migrei para lighttpd, e depois o ruby passou a automatizar a geração da configuração, então tecnicamente até poderia voltar para httpd
Mesmo assim, não quero voltar. Se você desenvolve servidor web, precisa ter muito cuidado antes de forçar os usuários a se enquadrarem em um formato novo
Se a ideia é mudar o formato de configuração por uma decisão aparentemente simples, então pelo menos ofereça algo como configuração em yaml como opção adicional, em vez de de repente empurrar um novo estilo de configuração com if-clause
Agora que WHATWG streams se espalharam bastante pelos navegadores, ficou bem fácil implementar algo parecido com WebSocket em cima de uma requisição HTTP de longa duração
Basta enviar um stream de bytes e colocar um cabeçalho antes de cada mensagem; em muitos casos, só um campo de tamanho já basta
Há vantagens também. Não exige um caminho especial extra na camada do servidor como o WebSocket, permite usar backpressure, herda de graça as melhorias do HTTP/2 e HTTP/3 e ainda tem menos overhead de framing
Mas, AFAIK, ainda não há suporte para continuar fazendo streaming do corpo da requisição enquanto se recebe resposta ao mesmo tempo, então para streaming bidirecional completo ainda são necessárias duas requisições
Redescobri o antigo plain CGI, e ele é ótimo para permitir que usuários façam vibe code de páginas customizadas na nossa plataforma [1]
Temos task list e data viewer como recursos prontos, mas os usuários frequentemente querem customizações bem mais detalhadas, como visualizações Kanban ou dashboards personalizados com filtros e gráficos
Essa caixa tem um coding agent, então em vez de criarmos um report builder tradicional, o usuário pode simplesmente programar o que quiser
A stdlib do Go dá ótimo suporte tanto no lado do servidor quanto no user space, e se o coding agent criar
page-name/main.gopara se comunicar via CGI, o servidor delega a requisição para láComo o volume de dados e de pageviews é todo em escala pessoal, nem faz muita falta uma otimização como FastCGI
Na era dos agentes, tecnologias antigas voltam a parecer novas
A implementação de servidor CGI do Go não define
$HTTP_PROXY, então essa parte é segura, mas ainda assim não gosto da forma como o CGI usa variáveis de ambienteO lado do reverse proxy em geral só fazia tarefas simples, então os recursos embutidos do Nginx já bastavam
Mesmo assim, a ideia de recorrer a FastCGI quando fosse preciso algo mais complexo provavelmente nem teria me ocorrido
Há uns 10 anos usei um pouco de FastCGI para rodar parte de um código C++ na web, mas depois disso quase não usei mais
Você coloca um servidor HTTP dentro da própria aplicação e simplesmente faz o que precisa, sem gateway
A configuração PHP/Apache distribuída no ecossistema Red Hat é FPM (FastCGI Process Manager)
Não sei se as distribuições RHEL usam FastCGI em outros lugares também
$ rpm -qi php-fpm | grep ^SummarySummary : PHP FastCGI Process ManagerEle vem no pacote
httpd-coredo Fedora. No RHEL eu não sei: https://packages.fedoraproject.org/pkgs/httpd/httpd-core/fed...Também existe o uwsgi protocol
Na prática, ele também tem um caráter quase de RPC para tudo
FCGI também é um sistema de orquestração
Quando a carga sobe, ele inicia mais tasks de servidor; quando a carga cai, reduz; se uma task morre, sobe uma nova cópia
É como uma espécie de Kubernetes de sistema único
No papel parece ótima, mas era comum funcionar bem em carga baixa e, quando vinha carga alta, começar a criar mais workers até estourar a memória
Por isso, quase sempre era melhor trabalhar com número estático de workers
Ainda assim, recuperação de crash pode ser útil se você precisar dela
Vale a pena parar por um momento para admirar a absurdidade dos cabeçalhos HTTP
Se você só usa
X-Real-IPquandoTrue-Client-IPnão existe, então mesmo que o proxy preenchaX-Real-IPcorretamente, um atacante ainda pode te pegar simplesmente enviando o cabeçalhoTrue-Client-IPTem
X-Forwarded-For,X-Real-IPe até cabeçalhos customizados diferentes para cada CDN; alguns são listas separadas por vírgula e geralmente ainda incluem, sem utilidade nenhuma, o IP do nosso próprio LBEu entendo por que isso aconteceu, mas não ajuda em nada
Além disso, todos esses cabeçalhos também podem ser injetados por um user-agent malicioso. Parece que ninguém conseguiu chegar a um acordo sobre como servidores confiáveis deveriam transmitir informações importantes ao longo do pipeline
Essa confusão combina bem com a absurdidade do cabeçalho User-Agent
Nesse caso, a Apple levou isso a um extremo ainda maior ao decidir enviar informações totalmente falsas em nome da privacidade, como versões de sistema operacional inventadas
Há muito sentido nessa tese, mas o FastCGI perde informação por seguir o CGI/1.1 em partes como
PATH_INFOA decodificação de URL é forçada, então não dá para representar uma encoded slash como
%2FDependendo da implementação,
//no caminho também pode virar/, embora esse também seja um problema presente em várias implementações HTTPEm termos de expressividade, ele fica abaixo do HTTP, e a importância disso depende da aplicação
Eu prefiro lidar com URLs de forma exata