- Como protocolo de proxy para encaminhar requisições por socket a backends de longa duração, ele pode ser adotado quase sem mudar a estrutura existente dos handlers HTTP
- O proxy reverso com HTTP/1.1 tende a gerar divergências de implementação na interpretação dos limites das mensagens, o que continua criando problemas graves de segurança como desync e request smuggling
- O FastCGI fornece framing de mensagens claro desde 1996 e separa estruturalmente os cabeçalhos do cliente das informações confiáveis adicionadas pelo proxy
- Em 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 - Embora tenha limitações como falta de suporte a WebSockets, ecossistema de ferramentas fraco e menor throughput em algumas cargas de trabalho, ele ainda parece uma escolha prática se você não precisa de WebSockets e o desempenho for suficiente
Posição e forma de adoção do FastCGI
- FastCGI não serve apenas para o modelo de executar um processo por arquivo; também pode ser usado como protocolo entre proxy e backend para enviar requisições a daemons de longa duração por TCP ou socket 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
- Proxies importantes como Apache, Caddy, nginx e HAProxy oferecem suporte a backends FastCGI, e a configuração costuma ser simples
Problemas de parsing ao usar HTTP como protocolo de backend
- O reverse proxy com HTTP é quase um campo minado de segurança, e continuam surgindo problemas como a vulnerabilidade de desync no proxy de mídia do Discord, que podia permitir espionar anexos privados
- O HTTP/1.1 parece um protocolo de texto simples à primeira vista, mas há maneiras demais de representar a mesma mensagem e muitas exceções, o que facilita interpretações diferentes 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 formas
- Implementações diferentes podem interpretar de forma diferente onde uma mensagem termina e a próxima começa
- Essas divergências são 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 formas diferentes
- Continuar corrigindo diferenças de parser dificilmente é uma solução fundamental
- James Kettle continua encontrando novos tipos
- Depois de encontrar casos adicionais no ano passado, ele chegou a usar a expressão "HTTP/1.1 must die"
Tratamento dos limites de mensagem no FastCGI e no HTTP/2
- O HTTP/2, quando usado de forma consistente entre proxy e backend, pode resolver o problema de desync ao deixar claros os limites das mensagens
- O FastCGI já oferecia essa distinção clara de limites desde 1996 com um protocolo mais simples
- O nginx suportava backends FastCGI desde sua primeira versão, mas o suporte a backend HTTP/2 só foi adicionado no fim de 2025
- O suporte do Apache a backend HTTP/2 ainda permanece em estado "experimental"
O problema dos cabeçalhos não confiáveis e a forma de separação do FastCGI
- Não é só uma questão de desync: o HTTP também carece de uma forma robusta de transportar dados que o proxy precisa encaminhar com confiança, como o IP real do cliente, o nome de usuário autenticado processado pelo proxy ou informações do certificado do cliente em mTLS
- Na prática, essas informações acabam indo para cabeçalhos HTTP, mas não há separação estrutural entre os dados confiáveis adicionados pelo proxy e os cabeçalhos não confiáveis enviados pelo cliente
- Cabeçalhos 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 todos os cabeçalhos existentes e suas variações de maiúsculas e minúsculas antes de adicionar o seu - Essa abordagem é um terreno muito perigoso, e há muitos caminhos pelos quais o backend pode acabar confiando em dados inseridos por um atacante
- O proxy precisa apagar não só
X-Real-IP, mas qualquer cabeçalho usado com essa finalidade - Por exemplo, o middleware Chi primeiro verifica
True-Client-IPpara determinar o IP real do cliente e só usaX-Real-IPse ele não existir- Mesmo que o proxy trate
X-Real-IPcorretamente, um atacante ainda pode causar problemas enviandoTrue-Client-IP
- Mesmo que o proxy trate
- O FastCGI separa os cabeçalhos do cliente e as informações adicionadas pelo proxy por meio de separação de domínio
- Ambos são passados como listas de parâmetros chave/valor, mas nomes de cabeçalhos HTTP recebem o prefixo
HTTP_ - Assim, não se cria uma estrutura em que um cabeçalho enviado pelo cliente possa ser interpretado como dado confiável do proxy
- Ambos são passados como listas de parâmetros chave/valor, mas nomes de cabeçalhos HTTP recebem o prefixo
Tratamento de informações confiáveis com FastCGI em Go
- O FastCGI define parâmetros padrão como
REMOTE_ADDRpara transmitir o IP real do cliente - O
net/http/fcgido Go preenche esse valor automaticamente emhttp.Request.RemoteAddr, funcionando sem middleware adicional - O proxy também pode transmitir, como parâmetros não padronizados, informações como uso de HTTPS, cipher suite TLS negociada e certificado do cliente
- Em Go, quando a requisição usa HTTPS, o campo
TLSdeRequesté definido automaticamente como um valor diferente de nil- Mesmo vazio, isso já é útil para verificar a exigência 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 envelhecido do próprio nome quanto à falta de 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 o problema não seria fácil de resolver, mas esses ataques passaram mais de dez anos sem receber a atenção devida
- O FastCGI continua viável em uso real, e a SSLMate o utiliza em produção há mais de 10 anos
- Ainda assim, por ser uma tecnologia antiga, ele tem fraquezas
- Não foi atualizado para suportar WebSockets
- O ecossistema de ferramentas é limitado
- Por exemplo, o curl suporta até FTP, Gopher e SMTP, mas não consegue enviar requisições FastCGI
- Ao fazer benchmark de um servidor FastCGI em Go atrás de vários proxies reversos, algumas cargas de trabalho tiveram throughput menor que HTTP/1.1 ou HTTP/2
- Isso parece menos uma limitação do protocolo em si e mais o resultado de caminhos de código FastCGI não tão otimizados quanto os de HTTP
Avaliação final
- Se você não precisa de WebSockets e o desempenho atual é suficiente, o FastCGI continua sendo uma opção válida
- Mesmo que surja um gargalo, a visão apresentada é que adicionar hardware é uma escolha melhor 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