Open Redirect – Vulnerabilidade de redirecionamento aberto

Dando sequência aos artigos sobre segurança de aplicações web, no último artigo eu falei sobre Cross-Site Request Forgery (CSRF) e abordagens para mitigá-lo.

Hoje veremos sobre “Open Redirect Vulnerabilities”, que a OWASP define como “Unvalidated Redirects and Forwards”, que basicamente é um ataque que explora redirecionamentos arbitrários que acontecem a partir da interação dos usuários.

Vamos supor que você é usuário de uma corretora de criptomoedas que responde ao endereço mytestexchange.com. E, navegando pelo twitter, você viu um tweet que parecia ser do perfil oficial dela sobre alguma nova moeda que foi incluída na plataforma:

A URL do link é:

https://mytestexchange.com/login?next=https://mytestexchange1.com/new

Observe também que o perfil de quem postou o tweet é @mytestexchange1 e não @mytestexchange que seria o oficial da corretora.

Aparentemente, tudo correto. Na página de login você entrará com os seus dados e, depois de logado, será redirecionado para a URL que se encontra no parâmetro next.

O problema aqui é que essa URL do parâmetro next é um site arbitrário que não condiz com o da sua corretora. É possível que ele seja praticamente um clone do site da sua corretora. Um clássico ataque de phishing. Como também é muito possível que ele pedirá pra você informar os dados da sua conta com o falso pretexto de mantê-la protegida, mas com a escusa intenção de roubá-los.

Esse cenário pode parecer nonsense, não praticável etc, mas, acredite, acontece com muita frequência, principalmente com usuários com menos malícia em verificar a URL ou o site que se está acessando após algum redirecionamento. Muitas recompensas já foram pagas (bug bounty program) para vulnerabilidades desse tipo e muitas delas foram encontradas em grandes sites e plataformas de ecommerce, pois open redirect é algo comum em aplicações web.

De quem é e onde está o problema?

Veja que no cenário acima o ataque utilizou-se de uma vulnerabilidade do site da corretora que aceita a manipulação da URL de redirecionamento para qualquer outro domínio que não o dela. A responsabilidade aqui é do site da coretora. Não podemos pressupor que o cliente (usuário) seguirá todos os caminhos seguros possíveis ao interagir com as nossas aplicações.

Vamos transportar para um código genérico o que possivelmente o site da corretora faz:

Nota: O exemplo aqui é em PHP (minha linguagem favorita), mas o paralelo pode ser feito com qualquer outra linguagem e/ou qualquer framework web.

<?php

namespace App;

class LoginController extends Controller
{
    public function postLogin(Request $request)
    {
        // TODO: processo de autenticação

        return redirect()->to($request->get('next'));
    }
}

Da forma com que com o parâmetro foi recebido, o redirecionamento foi feito, sem nenhuma restrição ou validação.

Ok, mas como posso me proteger?

A primeira recomendação é: não receba do usuário a próxima URL na qual ele será redirecionado. Tente sempre tomar esse tipo de decisão na lógica da sua aplicação. No entanto, há muitos casos em que receber a próxima URL pelo parâmetro é necessário, no sentido de melhorar a experiência do seu usuário de alguma forma, levá-lo a uma página interna sem que ele precise “quebrar a cabeça”. E, quando isso for necessário, não tem problema, não é pecado, desde que você valide essa URL recebida do usuário.

A validação vai depender da necessidade da sua aplicação. Em muitos casos, validar se a URL refere-se ao domínio do seu site já é suficiente. Dessa forma, há segurança de que o usuário será redirecionado para algum recurso seguro (seu próprio site). Também há casos em que há a necessidade de um redirecionamento para outro domínio diferente mas seguro no contexto da sua aplicação. Tudo isso vai interferir na forma com que a validação será desenvolvida.

Vamos supor que para o cenário da corretora, tudo o que ela precisa é de uma whitelist de domínios/sub-domínios seguros para os quais ela pode fazer redirecionamentos. E ela não abre mão de receber essas informações da URL.

Um pseudo-código para validar essa URL do redirecionamento seria:

<?php

namespace App;

final class UrlHostValidator
{
    public static function validate(?string $url) : bool
    {
        if (null === $url || false === filter_var($url, FILTER_VALIDATE_URL)) {
            return false;
        }

        $validHosts = [
            'mytestexchange.com',
            'www.mytestexchange.com',
            'wallet.mytestexchange.com',
        ];

        return \in_array(
            parse_url($url, PHP_URL_HOST),
            $validHosts,
            true
        );
    }
}

Recebe a URL e, usando filter_var(), valida se é bem formada, se não tem nenhum caractere inválido que poderia levar a algum bypass.

Em seguida, o host é extraído da URL e validado. No caso, a corretora limitou para aqueles informados na variável $validHosts (informação essa que poderia ter vindo de um arquivo de configuração, por exemplo. Mas foge do nosso escopo aqui).

Agora, no processo de login, poderíamos usar a nossa lógica de validação da URL do redirecionamento:

<?php

namespace App;

class LoginController extends Controller
{
    public function postLogin(Request $request)
    {
        // TODO: processo de autenticação

        if (UrlHostValidator::validate($next = $request->get('next'))) {
            return redirect()->to($next);
        }

        return redirect()->to('/account');
    }
}

Para a necessidade do site da corretora, isso já seria suficiente. No entanto, há casos em que o site precisa confiar apenas em algumas páginas específicas, por exemplo:

  • https://mytestexchange.com/login?next=/login
  • https://mytestexchange.com/login?next=/signup
  • https://mytestexchange.com/login?next=/account

O ideal é que você também crie uma whitelist dos valores seguros e valide. Se você for utilizar alguma regex, tome muito cuidado para que ela dê match apenas no necessário. Não pode ser uma regex muito expansiva, senão ela pode ser explorada.

Se não quiser expor as URL’s e se você tiver uma identificação para cada uma delas (num arquivo de configuração, ou numa base de dados se for o caso), você poderia fazer assim:

  • https://mytestexchange.com/login?next=1
  • https://mytestexchange.com/login?next=2
  • https://mytestexchange.com/login?next=3

A sua aplicação saberia que o valor 1 refere-se à rota /login e assim por diante. Reiterando o que eu disse anteriormente, tudo vai depender da necessidade da sua aplicação.

Concluindo

A lição que fica é: Você precisa mesmo abrir um redirecionamento por URL? Então valide muito bem para que falhas como as citadas acima não sejam exploradas. Validar o que vem do usuário é uma regra universal. Vale para qualquer entrada e qualquer contexto. A sua aplicação consegue viver sem fazer esse tipo de redirecionamento por URL? Melhor ainda. Menos uma preocupação de segurança pra você.

Até a próxima!

Deixe seu comentário
Share

Head de desenvolvimento. Vasta experiência em desenvolvimento Web com foco em PHP. Graduado em Sistemas de Informação. Pós-graduando em Arquitetura de Software Distribuído pela PUC Minas. Zend Certified Engineer (ZCE) e Coffee Addicted Person (CAP). @KennedyTedesco

JUNTE-SE A MAIS DE 150.000 PROGRAMADORES