Pular para o conteúdo
Development

Consertando um redirect, e o caso extremo que uma linha deixou passar

Por Victor Da Luz
railsrubysecuritydev-logblog-manager

Esse aqui saiu de um fix anterior, não do nada. Revisando o fix do 500 sem autenticação no painel de jobs há uns dois dias, percebi que o fluxo de login faz algo meio arriscado: qualquer URL que você estava tentando acessar quando sua sessão expirou fica guardada, e depois que você loga de novo, você é redirecionado direto de volta para ela. Conveniente, quando funciona. O bug é que isso guarda a URL para qualquer requisição, incluindo aquelas que só fazem sentido como POST, e depois redireciona cegamente para ela com um GET. Um GET numa rota que só aceita POST não bate com nada. 404, logo depois que você acabou de logar de novo.

O que eu construí

O fix parecia pequeno de início: só lembrar da URL se a requisição original fosse um GET (ou HEAD, que o Rails trata como GET para fins de roteamento). Ignorar qualquer outro método e deixar o fallback existente te mandar para a homepage. Uma linha, uma palavra adicionada.

O que me surpreendeu

Rodei isso pelo Brakeman antes de abrir o PR, mais por hábito a essa altura, e ele sinalizou justamente a linha que eu tinha acabado de escrever. Não a lógica, a checagem específica. Eu tinha escrito request.get? sozinho, e o Brakeman apontou que uma requisição HEAD escaparia dessa checagem, mesmo o Rails roteando HEAD para o mesmo lugar que um GET iria. Eu teria chamado isso de detalhe irrelevante uma semana atrás. Não é. Se um navegador ou uma ferramenta de monitoramento manda uma requisição HEAD para uma página protegida, meu fix de uma linha trataria isso como inseguro e pularia guardar o caminho de retorno, quando o objetivo inteiro era manter isso justamente para esse tipo de requisição só de leitura. Corrigido com || request.head?, que, como percebi depois revisando meu próprio diff, é a expressão literal que o próprio Rails usa internamente no seu código de proteção CSRF. Não foi uma escolha de estilo que eu inventei, é um nome para uma categoria que o Rails já reconhece e que eu só tinha implementado pela metade.

A pegadinha de verdade veio da revisão de código, porém. Ela apontou que pular a escrita para uma requisição insegura não é a mesma coisa que limpar o que já estava guardado ali. Imagine isso: você acessa uma página sem estar autenticado, é mandado para o login, e a URL é salva. Antes de você realmente logar, alguma outra coisa dispara um POST em segundo plano, um botão desatualizado numa página que você tinha aberta, uma nova tentativa, sei lá. Esse POST também é interceptado e redirecionado para o login, mas com meu fix, ele só deixa de guardar qualquer coisa nova. Ele não toca no que já estava sentado na sessão vindo do GET anterior. Então quando você finalmente loga, você cai naquela primeira página, sem relação nenhuma, em vez de algo previsível. Dessa vez não é um 404, só está silenciosamente errado.

Escrevi a reprodução como um teste antes de tocar no código: GET, depois POST, depois login, verifico onde caio. Falhou exatamente como a revisão previu. O fix de verdade foi mais uma linha, limpar explicitamente o valor guardado em vez de só pular a escrita, e o mesmo teste passou a ficar verde.

O que vem a seguir

Nada pendente. O que ficou comigo aqui foi a distância entre “o fix que eu escrevi” e “o fix que de fato fecha a brecha”. Tanto a ferramenta quanto uma segunda olhada pegaram coisas que eu tinha certeza de já ter coberto. Vale lembrar que confiança não é o mesmo que cobertura.

Leitura relacionada

Development

Três linhas de config, uma tarde de verificação

Descomentar flags de SSL do Rails levou dez minutos. Ler o código-fonte do framework, testar dois achados de revisão plausíveis mas errados, e provar o cookie em produção levou o resto do tempo.

Ler

Você também pode achar útil

Proton

Proton Mail

E-mail criptografado de ponta a ponta, com arquitetura de acesso zero.

Como parceiro da Proton, ganho com compras qualificadas dos serviços de privacidade e segurança da Proton (Pass, Mail, VPN, Drive).

Saiba mais
Proton

Proton Pass

Gerenciador de senhas focado em privacidade, da equipe por trás do Proton Mail.

Como parceiro da Proton, ganho com compras qualificadas dos serviços de privacidade e segurança da Proton (Pass, Mail, VPN, Drive).

Saiba mais
Proton

Proton VPN

VPN comercial com filtragem NetShield e interruptor de desligamento automático.

Como parceiro da Proton, ganho com compras qualificadas dos serviços de privacidade e segurança da Proton (Pass, Mail, VPN, Drive).

Saiba mais