Por que é comum colocar tokens de prevenção de CSRF em cookies?

283

Estou tentando entender todo o problema com o CSRF e maneiras apropriadas de evitá-lo. (Recursos que li, compreendi e concordo com: Folha de dicas sobre prevenção de RSC do OWASP , Perguntas sobre CSRF .)

Pelo que entendi, a vulnerabilidade em torno do CSRF é introduzida pela suposição de que (do ponto de vista do servidor da web) um cookie de sessão válido em uma solicitação HTTP recebida reflete os desejos de um usuário autenticado. Mas todos os cookies para o domínio de origem são magicamente anexados à solicitação pelo navegador; portanto, na verdade, todo o servidor pode inferir da presença de um cookie de sessão válido em uma solicitação: a solicitação vem de um navegador que possui uma sessão autenticada; não pode mais assumir nada sobre o códigoexecutando nesse navegador ou se realmente reflete os desejos do usuário. A maneira de evitar isso é incluir informações adicionais de autenticação (o "token CSRF") na solicitação, realizada por alguns outros meios que não o tratamento automático de cookies do navegador. Em termos gerais, o cookie da sessão autentica o usuário / navegador e o token CSRF autentica o código em execução no navegador.

Portanto, se você estiver usando um cookie de sessão para autenticar usuários do seu aplicativo da Web, também deverá adicionar um token CSRF a cada resposta e exigir um token CSRF correspondente em cada solicitação (mutante). O token CSRF faz uma viagem de ida e volta do servidor para o navegador, provando ao servidor que a página que faz a solicitação é aprovada por (gerado por, mesmo) por esse servidor.

Na minha pergunta, que é sobre o método de transporte específico usado para esse token CSRF nessa viagem de ida e volta.

Parece comum (por exemplo, no AngularJS , Django , Rails ) enviar o token CSRF do servidor para o cliente como um cookie (ou seja, no cabeçalho Set-Cookie) e, em seguida, fazer com que o Javascript no cliente o retire e o anexe. como um cabeçalho XSRF-TOKEN separado para enviar de volta ao servidor.

(Um método alternativo é o recomendado por exemplo , Express , em que o token CSRF gerado pelo servidor é incluído no corpo da resposta por meio da expansão do modelo no servidor, anexado diretamente ao código / marcação que o fornecerá de volta ao servidor, por exemplo como uma entrada de formulário oculta. Esse exemplo é uma maneira mais isométrica de fazer as coisas na Web 1.0, mas generalizaria bem para um cliente mais pesado em JS.)

Por que é tão comum usar o Set-Cookie como o transporte a jusante do token CSRF / por que essa é uma boa idéia? Imagino que os autores de todas essas estruturas considerassem suas opções com cuidado e não entendessem isso errado. Mas, à primeira vista, o uso de cookies para contornar o que é essencialmente uma limitação de design em cookies parece estúpido. De fato, se você usasse cookies como transporte de ida e volta (Set-Cookie: cabeçalho a jusante para o servidor informar ao navegador o token CSRF e Cookie: cabeçalho a montante para o navegador retorná-lo ao servidor), você reintroduziria a vulnerabilidade está tentando consertar.

Percebo que as estruturas acima não usam cookies para toda a ida e volta para o token CSRF; eles usam Set-Cookie a jusante, depois outra coisa (por exemplo, um cabeçalho X-CSRF-Token) a montante, e isso fecha a vulnerabilidade. Mas mesmo o uso de Set-Cookie como transporte a jusante é potencialmente enganador e perigoso; o navegador agora anexará o token CSRF a cada solicitação, incluindo solicitações XSRF maliciosas genuínas; na melhor das hipóteses, torna a solicitação maior do que precisa e, na pior das hipóteses, algum código de servidor bem-intencionado, porém mal orientado, pode realmente tentar usá-lo, o que seria muito ruim. Além disso, como o destinatário real do token CSRF é o Javascript do cliente, isso significa que esse cookie não pode ser protegido apenas com http.

metamatt
fonte
É uma ótima pergunta para o lugar certo.
kta 23/03

Respostas:

262

Uma boa razão pela qual você meio que tocou é que, uma vez recebido o cookie CSRF, ele fica disponível para uso em todo o aplicativo no script do cliente para uso em formulários regulares e em POSTs AJAX. Isso fará sentido em um aplicativo pesado JavaScript, como o empregado pelo AngularJS (o uso do AngularJS não exige que o aplicativo seja um aplicativo de página única, portanto, seria útil onde o estado precisa fluir entre diferentes solicitações de página onde o valor de CSRF normalmente não pode persistir no navegador).

Considere os seguintes cenários e processos em um aplicativo típico para alguns prós e contras de cada abordagem que você descreve. Estes são baseados no padrão de token do sincronizador .

Abordagem do corpo da solicitação

  1. O usuário efetua login com êxito.
  2. O servidor emite o cookie de autenticação.
  3. O usuário clica para navegar para um formulário.
  4. Se ainda não foi gerado para esta sessão, o servidor gera o token CSRF, o armazena na sessão do usuário e o envia para um campo oculto.
  5. O usuário envia o formulário.
  6. O servidor verifica se o campo oculto corresponde ao token armazenado da sessão.

Vantagens:

  • Simples de implementar.
  • Funciona com AJAX.
  • Trabalha com formulários.
  • O cookie pode realmente ser apenas HTTP .

Desvantagens:

  • Todos os formulários devem gerar o campo oculto em HTML.
  • Quaisquer POSTs AJAX também devem incluir o valor.
  • A página deve saber com antecedência que requer o token CSRF para poder incluí-lo no conteúdo da página, para que todas as páginas contenham o valor do token em algum lugar, o que pode levar tempo para ser implementado em um site grande.

Cabeçalho HTTP personalizado (downstream)

  1. O usuário efetua login com êxito.
  2. O servidor emite o cookie de autenticação.
  3. O usuário clica para navegar para um formulário.
  4. A página é carregada no navegador e, em seguida, é feita uma solicitação AJAX para recuperar o token CSRF.
  5. O servidor gera o token CSRF (se ainda não foi gerado para a sessão), armazena-o na sessão do usuário e o envia para um cabeçalho.
  6. O usuário envia o formulário (o token é enviado via campo oculto).
  7. O servidor verifica se o campo oculto corresponde ao token armazenado da sessão.

Vantagens:

Desvantagens:

  • Não funciona sem uma solicitação AJAX para obter o valor do cabeçalho.
  • Todos os formulários devem ter o valor adicionado ao seu HTML dinamicamente.
  • Quaisquer POSTs AJAX também devem incluir o valor.
  • A página deve fazer uma solicitação AJAX primeiro para obter o token CSRF, portanto, isso significa uma viagem de ida e volta extra a cada vez.
  • Também pode simplesmente enviar o token para a página que salvaria a solicitação extra.

Cabeçalho HTTP personalizado (upstream)

  1. O usuário efetua login com êxito.
  2. O servidor emite o cookie de autenticação.
  3. O usuário clica para navegar para um formulário.
  4. Se ainda não foi gerado para esta sessão, o servidor gera o token CSRF, o armazena na sessão do usuário e o gera no conteúdo da página em algum lugar.
  5. O usuário envia o formulário via AJAX (o token é enviado via cabeçalho).
  6. O servidor verifica o cabeçalho personalizado corresponde ao token armazenado da sessão.

Vantagens:

Desvantagens:

  • Não funciona com formulários.
  • Todos os POSTs AJAX devem incluir o cabeçalho.

Cabeçalho HTTP personalizado (upstream e downstream)

  1. O usuário efetua login com êxito.
  2. O servidor emite o cookie de autenticação.
  3. O usuário clica para navegar para um formulário.
  4. A página é carregada no navegador e, em seguida, é feita uma solicitação AJAX para recuperar o token CSRF.
  5. O servidor gera o token CSRF (se ainda não foi gerado para a sessão), armazena-o na sessão do usuário e o envia para um cabeçalho.
  6. O usuário envia o formulário via AJAX (o token é enviado via cabeçalho).
  7. O servidor verifica o cabeçalho personalizado corresponde ao token armazenado da sessão.

Vantagens:

Desvantagens:

  • Não funciona com formulários.
  • Todos os AJAX POSTs também devem incluir o valor
  • A página deve fazer uma solicitação AJAX primeiro para obter o token CRSF, portanto, isso significa uma viagem de ida e volta extra a cada vez.

Set-Cookie

  1. O usuário efetua login com êxito.
  2. O servidor emite o cookie de autenticação.
  3. O usuário clica para navegar para um formulário.
  4. O servidor gera o token CSRF, o armazena na sessão do usuário e o envia para um cookie.
  5. O usuário envia o formulário via AJAX ou via formulário HTML.
  6. O servidor verifica o cabeçalho personalizado (ou campo de formulário oculto) corresponde ao token armazenado da sessão.
  7. O cookie está disponível no navegador para uso em AJAX adicional e solicitações de formulário sem solicitações adicionais ao servidor para recuperar o token CSRF.

Vantagens:

  • Simples de implementar.
  • Funciona com AJAX.
  • Trabalha com formulários.
  • Não requer necessariamente uma solicitação AJAX para obter o valor do cookie. Qualquer solicitação HTTP pode recuperá-la e pode ser anexada a todos os formulários / solicitações AJAX via JavaScript.
  • Depois que o token CSRF é recuperado, como é armazenado em um cookie, o valor pode ser reutilizado sem solicitações adicionais.

Desvantagens:

  • Todos os formulários devem ter o valor adicionado ao seu HTML dinamicamente.
  • Quaisquer POSTs AJAX também devem incluir o valor.
  • O cookie será enviado para cada solicitação (ou seja, todos os GETs para imagens, CSS, JS, etc, que não estejam envolvidos no processo CSRF), aumentando o tamanho da solicitação.
  • O cookie não pode ser apenas HTTP .

Portanto, a abordagem do cookie é bastante dinâmica, oferecendo uma maneira fácil de recuperar o valor do cookie (qualquer solicitação HTTP) e usá-lo (a JS pode adicionar o valor a qualquer formulário automaticamente e pode ser empregada nas solicitações AJAX como cabeçalho ou como valor do formulário). Depois que o token CSRF for recebido para a sessão, não há necessidade de regenerá-lo, pois um invasor que utiliza uma exploração de CSRF não tem método de recuperar esse token. Se um usuário mal-intencionado tentar ler o token CSRF do usuário em qualquer um dos métodos acima, isso será impedido pela mesma política de origem . Se um usuário mal-intencionado tentar recuperar o lado do servidor do token CSRF (por exemplo, viacurl), esse token não será associado à mesma conta de usuário, pois o cookie da sessão de autenticação da vítima estará ausente da solicitação (seria o invasor - portanto, não será associado ao servidor com a sessão da vítima).

Além do padrão de token do sincronizador , também existe o cookie de envio duploMétodo de prevenção de CSRF, que obviamente usa cookies para armazenar um tipo de token de CSRF. É mais fácil de implementar, pois não requer nenhum estado do lado do servidor para o token CSRF. De fato, o token CSRF pode ser o cookie de autenticação padrão ao usar esse método, e esse valor é enviado via cookies como de costume com a solicitação, mas o valor também é repetido em um campo ou cabeçalho oculto, do qual um invasor não pode replicar como eles não podem ler o valor em primeiro lugar. No entanto, seria recomendável escolher outro cookie, além do cookie de autenticação, para que o cookie de autenticação possa ser protegido com a marcação HttpOnly. Portanto, esse é outro motivo comum para encontrar a prevenção de CSRF usando um método baseado em cookie.

SilverlightFox
fonte
7
Não sei se entendi como "a solicitação AJAX é feita para recuperar o token CSRF" (a etapa 4 nas duas seções "cabeçalho personalizado: jusante") pode ser realizada com segurança; como essa é uma solicitação separada, o servidor não sabe de onde vem; como ele sabe que é seguro divulgar o token CSRF? Parece-me que, se você não pode obter o token do carregamento inicial da página, perde (o que torna o cabeçalho de resposta downstream personalizado um iniciador, infelizmente).
metamatt
6
Porque o falsificador não terá o cookie da sessão. Eles podem ter seu próprio cookie de sessão, mas como o token CSRF está associado a uma sessão, seu token CSRF não corresponde ao da vítima.
SilverlightFox
32
Na minha compreensão do ataque CSRF, o falsificador possui meu cookie de sessão. Bem, eles não conseguem realmente ver o cookie, mas têm a capacidade de fornecê-lo em suas solicitações forjadas, porque as solicitações são provenientes do meu navegador e meu navegador fornece meu cookie de sessão. Portanto, do ponto de vista do servidor, apenas o cookie da sessão não consegue distinguir uma solicitação legítima de uma solicitação forjada. Este é de fato o ataque que estamos tentando impedir. Entre, obrigado pela sua paciência em discutir isso, especialmente se estou confuso sobre isso.
metamatt
8
Eles têm a capacidade de fornecer o cookie de autenticação, mas não podem ler a resposta que contém o token CSRF.
SilverlightFox
8
@metamatt Desculpem o necro, mas farei isso para as pessoas que entram. Pelo meu entendimento, o atacante normalmente não tem acesso à resposta. O CSRF é usado principalmente para causar efeitos colaterais , em vez de coletar dados diretamente. Por exemplo, um script de ataque CSRF pode forçar um usuário privilegiado a escalar os privilégios do invasor, desativar uma configuração de segurança ou forçar um usuário paypal conectado a enviar uma transferência para um endereço de email específico. Em nenhum desses casos, o atacante se importa com a resposta, que ainda é enviada ao navegador da vítima; apenas o resultado do ataque.
Jonathanbruder
61

O uso de um cookie para fornecer o token CSRF ao cliente não permite um ataque bem-sucedido porque o invasor não pode ler o valor do cookie e, portanto, não pode colocá-lo onde a validação do CSRF do lado do servidor exige.

O invasor poderá causar uma solicitação ao servidor com o cookie do token de autenticação e o cookie CSRF nos cabeçalhos da solicitação. Mas o servidor não está procurando o token CSRF como um cookie nos cabeçalhos da solicitação, está procurando na carga útil da solicitação. E mesmo que o invasor saiba onde colocar o token CSRF na carga, precisará ler seu valor para colocá-lo lá. Mas a política de origem cruzada do navegador impede a leitura de qualquer valor de cookie no site de destino.

A mesma lógica não se aplica ao cookie do token de autenticação, porque o servidor espera isso nos cabeçalhos da solicitação e o invasor não precisa fazer nada de especial para colocá-lo lá.

Tongfa
fonte
Certamente, porém, um invasor não precisa ler o cookie em primeiro lugar. Eles podem simplesmente inserir uma imagem no site invadido com o src='bank.com/transfer?to=hacker&amount=1000qual o navegador solicitará, com os cookies associados para esse site ( bank.com)?
Developius
2
O CSRF é para validar o usuário no lado do cliente e não para proteger o site geralmente de um comprometimento no servidor, conforme sugerido.
Tongfa 24/03
2
O @developius enviar o cookie não é suficiente para satisfazer a proteção CSRF. O cookie contém o token csrf, enviado pelo servidor. O cliente legítimo deve ler o token csrf do cookie e passá-lo na solicitação em algum lugar, como um cabeçalho ou na carga útil. A proteção CSRF verifica se o valor no cookie corresponde ao valor da solicitação, caso contrário, a solicitação é rejeitada. Portanto, o invasor precisa ler o cookie.
Will M.
1
Essa resposta foi muito direta à pergunta do pôster original e foi muito clara. +1 Obrigado.
Java-addict301
@ Tongfa - obrigado, isso me ajudou a entender melhor. Tenho razão em supor que o token CSRF NÃO deve ser colocado no cabeçalho? deve estar em algum lugar do corpo?
Zerohedge 12/10/19
10

Meu melhor palpite quanto à resposta: considere estas três opções para obter o token CSRF do servidor para o navegador.

  1. No corpo da solicitação (não um cabeçalho HTTP).
  2. Em um cabeçalho HTTP personalizado, não em Set-Cookie.
  3. Como um cookie, em um cabeçalho Set-Cookie.

Acho que o primeiro, o corpo da solicitação (embora demonstrado pelo tutorial do Express que vinculei na pergunta ), não é tão portátil para uma ampla variedade de situações; nem todo mundo está gerando todas as respostas HTTP dinamicamente; onde você acaba precisando colocar o token na resposta gerada pode variar bastante (em uma entrada de forma oculta; em um fragmento de código JS ou em uma variável acessível por outro código JS; talvez até em uma URL, embora isso geralmente pareça ruim. para colocar tokens CSRF). Portanto, embora exequível com alguma personalização, o número 1 é um lugar difícil de se fazer uma abordagem de tamanho único.

O segundo, cabeçalho personalizado, é atraente, mas na verdade não funciona, porque, embora o JS possa obter os cabeçalhos de um XHR invocado, ele não pode obter os cabeçalhos da página de onde foi carregada .

Isso deixa o terceiro, um cookie carregado por um cabeçalho Set-Cookie, como uma abordagem fácil de usar em todas as situações (o servidor de qualquer pessoa poderá definir cabeçalhos de cookie por solicitação e não importa que tipo de dados estão no corpo da solicitação). Portanto, apesar de suas desvantagens, era o método mais fácil para as estruturas implementarem amplamente.

metamatt
fonte
7
Eu posso estar afirmando o óbvio, isso significa que o cookie não pode estar correto;
Photon
1
somente para solicitações ajax (onde JS precisa saber o valor do cookie csrf para reenviá-lo na próxima solicitação no segundo canal (como dados do formulário ou cabeçalho)). Não há motivo para exigir que o token csrf seja HttpOnly se o cookie da sessão já for HttpOnly (para proteger contra o XSS), pois o token csrf não é valioso por si só sem a sessão associada.
cowbert
2

Além do cookie de sessão (que é um tipo de padrão), não quero usar cookies extras.

Encontrei uma solução que funciona para mim ao criar um SPA (aplicativo de página única), com muitas solicitações AJAX. Nota: Estou usando Java do lado do servidor e JQuery do lado do cliente, mas não há coisas mágicas, então acho que esse princípio pode ser implementado em todas as linguagens de programação populares.

Minha solução sem cookies extras é simples:

Lado do Cliente

Armazene o token CSRF que é retornado pelo servidor após um login bem-sucedido em uma variável global (se você quiser usar armazenamento na Web em vez de um global, tudo bem, é claro). Instrua o JQuery a fornecer um cabeçalho X-CSRF-TOKEN em cada chamada AJAX.

A página principal "índice" contém este snippet JavaScript:

// Intialize global variable CSRF_TOKEN to empty sting. 
// This variable is set after a succesful login
window.CSRF_TOKEN = '';

// the supplied callback to .ajaxSend() is called before an Ajax request is sent
$( document ).ajaxSend( function( event, jqXHR ) {
    jqXHR.setRequestHeader('X-CSRF-TOKEN', window.CSRF_TOKEN);
}); 

Lado do servidor

No logon sucessivo, crie um token CSRF aleatório (e longo o suficiente), armazene-o na sessão do lado do servidor e devolva-o ao cliente. Filtre determinadas solicitações de entrada (confidenciais) comparando o valor do cabeçalho X-CSRF-TOKEN com o valor armazenado na sessão: elas devem corresponder.

Chamadas AJAX confidenciais (dados de formulário POST e dados GET JSON) e o filtro do lado do servidor que as captura, estão no caminho / dataservice / *. As solicitações de logon não devem atingir o filtro, portanto, elas estão em outro caminho. As solicitações de HTML, CSS, JS e recursos de imagem também não estão no caminho / dataservice / *, portanto, não são filtradas. Eles não contêm nada secreto e não podem causar danos, por isso está bem.

@WebFilter(urlPatterns = {"/dataservice/*"})
...
String sessionCSRFToken = req.getSession().getAttribute("CSRFToken") != null ? (String) req.getSession().getAttribute("CSRFToken") : null;
if (sessionCSRFToken == null || req.getHeader("X-CSRF-TOKEN") == null || !req.getHeader("X-CSRF-TOKEN").equals(sessionCSRFToken)) {
    resp.sendError(401);
} else
    chain.doFilter(request, response);
}   
Stephan van Hoof
fonte
Eu acho que você gostaria de CSRF em uma solicitação de login. Você parece estar usando o token CSRF também como um token de sessão de logon. Ele também trabalha para tê-los como tokens separados e, em seguida, você pode usar o CSRF em qualquer terminal, independentemente de o usuário estar conectado ou não.
Tongfa