Prevenção de XSRF/CSRF no ASP.NET MVC e em páginas da Web

por Rick Anderson

A falsificação de solicitação entre sites (também conhecida como XSRF ou CSRF) é um ataque contra aplicativos hospedados na Web em que um site mal-intencionado pode influenciar a interação entre um navegador cliente e um site da Web confiável por esse navegador. Esses ataques são possibilitados porque os navegadores da Web enviarão tokens de autenticação automaticamente a cada solicitação para um site da Web. O exemplo canônico é um cookie de autenticação, como o tíquete de autenticação de formulários do ASP.NET. No entanto, sites que usam qualquer mecanismo de autenticação persistente (como Autenticação do Windows, Básico e assim por diante) podem ser alvo desses ataques.

Um ataque XSRF é diferente de um ataque de phishing. Os ataques de phishing exigem a interação da vítima. Em um ataque de phishing, um site mal-intencionado imitará o site de destino e a vítima será enganada para fornecer informações confidenciais ao invasor. Em um ataque XSRF, normalmente não é necessária nenhuma interação da vítima. Em vez disso, o invasor está contando com o navegador enviando automaticamente todos os cookies relevantes para o site de destino.

Para obter mais informações, consulte o XSRF doOpen Web Application Security Project (OWASP).

Anatomia de um ataque

Para percorrer um ataque XSRF, considere um usuário que deseja executar algumas transações bancárias online. Esse usuário primeiro visita WoodgroveBank.com e faz logon, momento em que o cabeçalho de resposta conterá seu cookie de autenticação:

HTTP/1.1 200 OK
Date: Mon, 18 Jun 2012 21:22:33 GMT
X-AspNet-Version: 4.0.30319
Set-Cookie: .ASPXAUTH={authentication-token}; path=/; secure; HttpOnly;
{ Cache-Control, Content-Type, Location, Server and other keys/values not listed. }

Como o cookie de autenticação é um cookie de sessão, ele será limpo automaticamente pelo navegador quando o processo do navegador for encerrado. No entanto, até esse momento, o navegador incluirá automaticamente o cookie com cada solicitação para WoodgroveBank.com. O usuário agora deseja transferir US$ 1.000 para outra conta, para que ela preencha um formulário no site bancário e o navegador faça essa solicitação para o servidor:

POST /DoTransfer HTTP/1.1
Host: WoodgroveBank.com
Content-Type: application/x-www-form-urlencoded
Cookie: .ASPXAUTH={authentication-token}
toAcct=12345&amount=1,000.00

Como essa operação tem um efeito colateral (inicia uma transação monetária), o site bancário optou por exigir um HTTP POST para iniciar essa operação. O servidor lê o token de autenticação da solicitação, pesquisa o número da conta do usuário atual, verifica se existem fundos suficientes e inicia a transação na conta de destino.

Seu banco online é concluído, o usuário navega para longe do site bancário e visita outros locais na Web. Um desses sites – fabrikam.com – inclui a seguinte marcação em uma página inserida em um <iframe>:

<form id="theForm" action="https://WoodgroveBank.com/DoTransfer" method="post">
    <input type="hidden" name="toAcct" value="67890" />
    <input type="hidden" name="amount" value="250.00" />
</form>
<script type="text/javascript">
    document.getElementById('theForm').submit();
</script>

Em seguida, faz com que o navegador faça essa solicitação:

POST /DoTransfer HTTP/1.1
Host: WoodgroveBank.com
Content-Type: application/x-www-form-urlencoded
Cookie: .ASPXAUTH={authentication-token}
toAcct=67890&amount=250.00

O invasor está explorando o fato de que o usuário ainda pode ter um token de autenticação válido para o site de destino e está usando um pequeno snippet de Javascript para fazer com que o navegador faça um HTTP POST para o site de destino automaticamente. Se o token de autenticação ainda for válido, o site bancário iniciará uma transferência de US$ 250 para a conta de escolha do invasor.

Mitigações ineficazes

É interessante observar que, no cenário acima, o fato de que WoodgroveBank.com estava sendo acessado via SSL e tinha um cookie de autenticação somente SSL era insuficiente para impedir o ataque. O invasor é capaz de especificar o esquema de URI (https) em seu <elemento de formulário> , e o navegador continuará a enviar cookies não expirados para o site de destino, desde que esses cookies sejam consistentes com o esquema de URI do destino pretendido.

Pode-se argumentar que o usuário simplesmente não deve visitar sites não confiáveis, pois visitar apenas sites confiáveis ajuda a permanecer seguro online. Há alguma verdade nisso, mas infelizmente este conselho nem sempre é prático. Talvez o usuário "confie" no site de notícias local ConsolidatedMessenger.com e vá visitar esse site, mas esse site tem uma vulnerabilidade XSS que permite que um invasor injete o mesmo snippet de código que estava em execução no fabrikam.com.

Você pode verificar se as solicitações de entrada têm um cabeçalho Referer referenciando seu domínio. Isso interromperá solicitações enviadas involuntariamente de um domínio de terceiros. No entanto, algumas pessoas desabilitam o cabeçalho do Encaminhador do navegador por motivos de privacidade, e os invasores às vezes podem falsificar esse cabeçalho se a vítima tiver determinado software inseguro instalado. Verificar se o cabeçalho do Encaminhador não é considerado uma abordagem segura para evitar ataques XSRF.

Mitigações do XSRF do Web Stack Runtime

O ASP.NET Web Stack Runtime usa uma variante do padrão de token do sincronizador para se defender contra ataques XSRF. A forma geral do padrão de token do sincronizador é que dois tokens anti-XSRF são enviados ao servidor com cada HTTP POST (além do token de autenticação): um token como um cookie e o outro como um valor de formulário. Os valores de token gerados pelo runtime ASP.NET não são determinísticos ou previsíveis por um invasor. Quando os tokens forem enviados, o servidor permitirá que a solicitação prossiga somente se ambos os tokens passarem por uma comparação marcar.

O token de sessão de verificação de solicitação XSRF é armazenado como um cookie HTTP e atualmente contém as seguintes informações em seu conteúdo:

  • Um token de segurança, que consiste em um identificador aleatório de 128 bits.
    A imagem a seguir mostra o token de sessão de verificação de solicitação XSRF exibido com as ferramentas de desenvolvedor da Internet Explorer F12: (Observe que esta é a implementação atual e está sujeita, ainda que, provavelmente, a ser alterada.)

Captura de tela que mostra a página My A SP dot NET M V C Application Index. A guia Rede está aberta.

O token de campo é armazenado como um <input type="hidden" /> e contém as seguintes informações em seu conteúdo:

As cargas dos tokens anti-XSRF são criptografadas e assinadas, portanto, você não pode exibir o nome de usuário ao usar ferramentas para examinar os tokens. Quando o aplicativo Web está direcionando ASP.NET 4.0, os serviços criptográficos são fornecidos pela rotina MachineKey.Encode . Quando o aplicativo Web está direcionando ASP.NET 4.5 ou superior, os serviços criptográficos são fornecidos pela rotina MachineKey.Protect , que oferece melhor desempenho, extensibilidade e segurança. Confira as seguintes postagens no blog para obter mais detalhes:

Gerando os tokens

Para gerar os tokens anti-XSRF, chame o método @Html.AntiForgeryToken de uma exibição MVC ou @AntiForgery.GetHtml() de uma página razor. Em seguida, o runtime executará as seguintes etapas:

  1. Se a solicitação HTTP atual já contiver um token de sessão anti-XSRF (o cookie anti-XSRF __RequestVerificationToken), o token de segurança será extraído dele. Se a solicitação HTTP não contiver um token de sessão anti-XSRF ou se a extração do token de segurança falhar, um novo token anti-XSRF aleatório será gerado.
  2. Um token de campo anti-XSRF é gerado usando o token de segurança da etapa (1) acima e a identidade do usuário conectado atual. (Para obter mais informações sobre como determinar a identidade do usuário, consulte a seção Cenários com suporte especial abaixo.) Além disso, se um IAntiForgeryAdditionalDataProvider estiver configurado, o runtime chamará seu método GetAdditionalData e incluirá a cadeia de caracteres retornada no token de campo. (Consulte a seção Configuração e extensibilidade para obter mais informações.)
  3. Se um novo token anti-XSRF tiver sido gerado na etapa (1), um novo token de sessão será criado para contê-lo e será adicionado à coleção de cookies HTTP de saída. O token de campo da etapa (2) será encapsulado em um <input type="hidden" /> elemento e essa marcação HTML será o valor retornado de Html.AntiForgeryToken() ou AntiForgery.GetHtml().

Validando os tokens

Para validar os tokens anti-XSRF de entrada, o desenvolvedor inclui um atributo ValidateAntiForgeryToken em sua ação ou controlador MVC ou ela chama @AntiForgery.Validate() de sua página Razor. O runtime executará as seguintes etapas:

  1. O token de sessão de entrada e o token de campo são lidos e o token anti-XSRF extraído de cada um. Os tokens anti-XSRF devem ser idênticos por etapa (2) na rotina de geração.
  2. Se o usuário atual for autenticado, seu nome de usuário será comparado com o nome de usuário armazenado no token de campo. Os nomes de usuário devem corresponder.
  3. Se um IAntiForgeryAdditionalDataProvider estiver configurado, o runtime chamará seu método ValidateAdditionalData . O método deve retornar o valor booliano true.

Se a validação for bem-sucedida, a solicitação poderá continuar. Se a validação falhar, a estrutura lançará um HttpAntiForgeryException.

Condições de falha

A partir do ASP.NET Web Stack Runtime v2, qualquer HttpAntiForgeryException que for lançada durante a validação conterá informações detalhadas sobre o que deu errado. As condições de falha definidas no momento são:

  • O token de sessão ou token de formulário não está presente na solicitação.
  • O token de sessão ou token de formulário é ilegível. A causa mais provável disso é um farm que executa versões incompatíveis do The ASP.NET Web Stack Runtime ou de um farm em que o <elemento machineKey> no Web.config difere entre computadores. Você pode usar uma ferramenta como o Fiddler para forçar essa exceção adulterando qualquer token anti-XSRF.
  • O token de sessão e o token de campo foram trocados.
  • O token de sessão e o token de campo contêm tokens de segurança incompatíveis.
  • O nome de usuário inserido no token de campo não corresponde ao nome de usuário do usuário conectado atual.
  • O método IAntiForgeryAdditionalDataProvider.ValidateAdditionalData retornou false.

As instalações anti-XSRF também podem executar verificação adicional durante a geração ou validação de token, e falhas durante essas verificações podem resultar na geração de exceções. Consulte as seções de autenticação baseada em declarações/WIF/ACSe Configuração e extensibilidade para obter mais informações.

Cenários com suporte especial

Autenticação anônima

O sistema anti-XSRF contém suporte especial para usuários anônimos, em que "anonymous" é definido como um usuário em que a propriedade IIdentity.IsAuthenticated retorna false. Os cenários incluem o fornecimento de proteção XSRF para a página de logon (antes que o usuário seja autenticado) e esquemas de autenticação personalizados em que o aplicativo usa um mecanismo diferente de IIdentity para identificar usuários.

Para dar suporte a esses cenários, lembre-se de que os tokens de sessão e de campo são unidos por um token de segurança, que é um identificador opaco gerado aleatoriamente de 128 bits. Esse token de segurança é usado para acompanhar a sessão de um usuário individual enquanto ela navega pelo site, portanto, ele atende efetivamente à finalidade de um identificador anônimo. Uma cadeia de caracteres vazia é usada no lugar do nome de usuário para as rotinas de geração e validação descritas acima.

Autenticação baseada em declarações/WIF/ACS

Normalmente, as classes IIdentity incorporadas ao .NET Framework têm a propriedade que IIdentity.Name é suficiente para identificar exclusivamente um determinado usuário em um aplicativo específico. Por exemplo, FormsIdentity.Name retorna o nome de usuário armazenado no banco de dados de associação (que é exclusivo para todos os aplicativos dependendo desse banco de dados), WindowsIdentity.Name retorna a identidade qualificada pelo domínio do usuário e assim por diante. Esses sistemas fornecem não apenas autenticação; eles também identificam usuários para um aplicativo.

Por outro lado, a autenticação baseada em declarações não requer necessariamente a identificação de um usuário específico. Em vez disso, os tipos ClaimsPrincipal e ClaimsIdentity são associados a um conjunto de instâncias de declaração, em que as declarações individuais podem ter "mais de 18 anos" ou "é um administrador" para qualquer outra coisa. Como o usuário não foi necessariamente identificado, o runtime não pode usar a propriedade ClaimsIdentity.Name como um identificador exclusivo para esse usuário específico. A equipe viu exemplos do mundo real em que ClaimsIdentity.Name retorna nulo, retorna um nome amigável (exibição) ou retorna uma cadeia de caracteres que não é apropriada para uso como um identificador exclusivo para o usuário.

Muitas das implantações que usam a autenticação baseada em declarações estão usando o ACS (Serviço de Controle de Acesso do Azure) em particular. O ACS permite que o desenvolvedor configure provedores de identidade individuais (como o ADFS, o provedor de Conta da Microsoft, provedores OpenID como Yahoo!etc.) e os provedores de identidade retornam identificadores de nome. Esses identificadores de nome podem conter PII (Informações de Identificação Pessoal) como um endereço de email ou podem ser anonimizados como um PPID (Identificador Pessoal Privado). Independentemente disso, a tupla (provedor de identidade, identificador de nome) serve suficientemente como um token de acompanhamento apropriado para um usuário específico enquanto ela está navegando no site, para que o ASP.NET Web Stack Runtime possa usar a tupla no lugar do nome de usuário ao gerar e validar tokens de campo anti-XSRF. Os URIs específicos para o provedor de identidade e o identificador de nome são :

  • https://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider
  • http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier

(consulte esta página de documentos do ACS para obter mais informações.)

Ao gerar ou validar um token, o ASP.NET Web Stack Runtime tentará associar em runtime aos tipos:

  • Microsoft.IdentityModel.Claims.IClaimsIdentity, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35 (Para o SDK do WIF.)
  • System.Security.Claims.ClaimsIdentity (Para .NET 4.5).

Se esses tipos existirem e se a IIIIdentity do usuário atual implementar ou subclasse um desses tipos, a instalação anti-XSRF usará a tupla (provedor de identidade, identificador de nome) no lugar do nome de usuário ao gerar e validar os tokens. Se essa tupla não estiver presente, a solicitação falhará com um erro descrevendo ao desenvolvedor como configurar o sistema anti-XSRF para entender o mecanismo de autenticação baseado em declarações específico em uso. Consulte a seção Configuração e extensibilidade para obter mais informações.

Autenticação OAuth/OpenID

Por fim, a instalação anti-XSRF tem suporte especial para aplicativos que usam autenticação OAuth ou OpenID. Esse suporte é baseado em heurística: se o IIdentity.Name atual começar com http:// ou https://, as comparações de nome de usuário serão feitas usando um comparador Ordinal em vez do comparador OrdinalIgnoreCase padrão.

Configuração e extensibilidade

Ocasionalmente, os desenvolvedores podem querer um controle mais rígido sobre os comportamentos de geração e validação anti-XSRF. Por exemplo, talvez o comportamento padrão dos auxiliares do MVC e das Páginas da Web de adicionar cookies HTTP automaticamente à resposta seja indesejável, e o desenvolvedor pode desejar persistir os tokens em outro lugar. Existem duas APIs para ajudar com isso:

AntiForgery.GetTokens(string oldCookieToken, out string newCookieToken, out string formToken);
AntiForgery.Validate(string cookieToken, string formToken);

O método GetTokens usa como entrada um token de sessão de verificação de solicitação XSRF existente (que pode ser nulo) e produz como saída um novo token de sessão de verificação de solicitação XSRF e token de campo. Os tokens são simplesmente cadeias de caracteres opacas sem decoração; o valor formToken , por exemplo, não será encapsulado em uma <marca de entrada> . O novo valorCookieToken pode ser nulo; se isso ocorrer, o valor oldCookieToken ainda será válido e nenhum novo cookie de resposta precisará ser definido. O chamador do GetTokens é responsável por persistir os cookies de resposta necessários ou gerar qualquer marcação necessária; O próprio método GetTokens não alterará a resposta como um efeito colateral. O método Validate usa os tokens de campo e sessão de entrada e executa a lógica de validação mencionada acima sobre eles.

AntiForgeryConfig

O desenvolvedor pode configurar o sistema anti-XSRF de Application_Start. A configuração é programática. As propriedades do tipo AntiForgeryConfig estático são descritas abaixo. A maioria dos usuários que usam declarações deseja definir a propriedade UniqueClaimTypeIdentifier.

Propriedade Descrição
AdditionalDataProvider Um IAntiForgeryAdditionalDataProvider que fornece dados adicionais durante a geração de token e consome dados adicionais durante a validação do token. O valor padrão é null. Para obter mais informações, consulte a seção IAntiForgeryAdditionalDataProvider .
Cookiename Uma cadeia de caracteres que fornece o nome do cookie HTTP usado para armazenar o token de sessão anti-XSRF. Se esse valor não estiver definido, um nome será gerado automaticamente com base no caminho virtual implantado do aplicativo. O valor padrão é null.
Requiressl Um booliano que determina se os tokens anti-XSRF devem ser enviados por um canal protegido por SSL. Se esse valor for true, todos os cookies gerados automaticamente terão o sinalizador "seguro" definido e as APIs anti-XSRF serão lançadas se forem chamadas de dentro de uma solicitação que não é enviada via SSL. O valor padrão é false.
SuppressIdentityHeuristicChecks Um booliano que determina se o sistema anti-XSRF deve desativar seu suporte para identidades baseadas em declarações. Se esse valor for true, o sistema assumirá que IIdentity.Name é apropriado para uso como um identificador exclusivo por usuário e não tentará iClaimsIdentity ou ClClaimsIdentity de caso especial, conforme descrito na seção autenticação baseada em declarações/WIF/ACS . O valor padrão é false.
UniqueClaimTypeIdentifier Uma cadeia de caracteres que indica qual tipo de declaração é apropriado para uso como um identificador exclusivo por usuário. Se esse valor for definido e a IIdentity atual for baseada em declarações, o sistema tentará extrair uma declaração do tipo especificado por UniqueClaimTypeIdentifier e o valor correspondente será usado no lugar do nome de usuário do usuário ao gerar o token de campo. Se o tipo de declaração não for encontrado, o sistema falhará na solicitação. O valor padrão é nulo, o que indica que o sistema deve usar a tupla (provedor de identidade, identificador de nome), conforme descrito anteriormente no lugar do nome de usuário do usuário.

IAntiForgeryAdditionalDataProvider

O tipo IAntiForgeryAdditionalDataProvider permite que os desenvolvedores estendam o comportamento do sistema anti-XSRF ao arredondar dados adicionais em cada token. O método GetAdditionalData é chamado sempre que um token de campo é gerado e o valor retornado é inserido no token gerado. Um implementador pode retornar um carimbo de data/hora, um nonce ou qualquer outro valor desejado desse método.

Da mesma forma, o método ValidateAdditionalData é chamado sempre que um token de campo é validado e a cadeia de caracteres "dados adicionais" que foi inserida no token é passada para o método . A rotina de validação poderia implementar um tempo limite (verificando a hora atual em relação ao tempo armazenado quando o token foi criado), uma rotina de verificação de nonce ou qualquer outra lógica desejada.

Decisões de design e considerações de segurança

O token de segurança que vincula os tokens de sessão e de campo é tecnicamente necessário apenas ao tentar proteger usuários anônimos/não autenticados contra ataques XSRF. Quando o usuário é autenticado, o próprio token de autenticação (presumivelmente enviado na forma de um cookie) pode ser usado como metade de um par de tokens de sincronizador. No entanto, há cenários válidos para proteger páginas de logon atingidas por usuários não autenticados e a lógica anti-XSRF ficou mais simples ao sempre gerar e validar o token de segurança, mesmo para usuários autenticados. Ele também fornece alguma proteção adicional caso um token de campo seja comprometido por um invasor, pois definir ou adivinhar o token de sessão seria outro obstáculo para o invasor superar.

Os desenvolvedores devem ter cuidado quando vários aplicativos são hospedados em um único domínio. Por exemplo, embora example1.cloudapp.net e example2.cloudapp.net sejam hosts diferentes, há uma relação de confiança implícita entre todos os hosts no domínio *.cloudapp.net . Essa relação de confiança implícita permite que hosts potencialmente não confiáveis afetem os cookies uns dos outros (as políticas de mesma origem que regem as solicitações AJAX não se aplicam necessariamente a cookies HTTP). O ASP.NET Web Stack Runtime fornece alguma mitigação em que o nome de usuário é inserido no token de campo, portanto, mesmo que um subdomínio mal-intencionado seja capaz de substituir um token de sessão, ele não poderá gerar um token de campo válido para o usuário. No entanto, quando hospedado em tal ambiente, as rotinas internas anti-XSRF ainda não podem se defender contra o sequestro de sessão ou o logon XSRF.

Atualmente, as rotinas anti-XSRF não se defendem contra o sequestro de cliques. Os aplicativos que desejam se defender contra o sequestro de cliques podem facilmente fazê-lo enviando um cabeçalho X-Frame-Options: SAMEORIGIN com cada resposta. Esse cabeçalho é compatível com todos os navegadores recentes. Para obter mais informações, consulte o blog do IE, o blog do SDL e o OWASP. O ASP.NET Web Stack Runtime pode, em alguma versão futura, tornar os auxiliares de MVC e Páginas da Web anti-XSRF automaticamente definir esse cabeçalho para que os aplicativos sejam protegidos automaticamente contra esse ataque.

Os desenvolvedores da Web devem continuar a garantir que seu site não esteja vulnerável a ataques XSS. Os ataques XSS são muito poderosos e uma exploração bem-sucedida também interromperia as defesas do ASP.NET Web Stack Runtime contra ataques XSRF.

Confirmação

@LeviBroderick, que escreveu grande parte do código de segurança ASP.NET a maior parte dessas informações.