Compreendendo o token de autenticidade do Rails

983

Estou com alguns problemas relacionados ao token de autenticidade no Rails, como já fiz muitas vezes agora.

Mas eu realmente não quero apenas resolver esse problema e continuar. Eu realmente gostaria de entender o token de autenticidade. Bem, minha pergunta é: você tem alguma fonte completa de informações sobre esse assunto ou gastaria seu tempo explicando aqui em detalhes?

Ricardo Acras
fonte
7
Veja também: "Por que o Google prefixa enquanto (1) sua resposta JSON?" stackoverflow.com/questions/2669690/…
Chloe

Respostas:

1463

O que acontece

Quando o usuário visualiza um formulário para criar, atualizar ou destruir um recurso, o aplicativo Rails cria um aleatório authenticity_token, armazena esse token na sessão e o coloca em um campo oculto no formulário. Quando o usuário envia o formulário, o Rails procura authenticity_token, compara-o com o armazenado na sessão e, se corresponderem à solicitação, poderá continuar.

Por que isso acontece

Como o token de autenticidade é armazenado na sessão, o cliente não pode saber seu valor. Isso impede que as pessoas enviem formulários para um aplicativo Rails sem visualizar o formulário no próprio aplicativo. Imagine que você está usando o serviço A, fez login no serviço e está tudo bem. Agora imagine que você foi usar o serviço B e viu uma foto de que gosta e pressionou a foto para vê-la em tamanho maior. Agora, se houver algum código malicioso no serviço B, ele poderá enviar uma solicitação para o serviço A (no qual você está conectado) e pedir para excluir sua conta, enviando uma solicitação para http://serviceA.com/close_account. Isso é conhecido como CSRF (falsificação de solicitação entre sites) .

Se o serviço A estiver usando tokens de autenticidade, esse vetor de ataque não será mais aplicável, pois a solicitação do serviço B não conteria o token de autenticidade correto e não poderá continuar.

Os documentos da API descrevem detalhes sobre a metatag:

A proteção CSRF é ativada com o protect_from_forgerymétodo, que verifica o token e redefine a sessão se não corresponder ao esperado. Uma chamada para esse método é gerada para novos aplicativos Rails por padrão. O parâmetro token é nomeado authenticity_tokenpor padrão. O nome e o valor desse token devem ser adicionados a todo layout que renderiza formulários, incluindo csrf_meta_tagsno cabeçalho HTML.

Notas

Lembre-se, o Rails apenas verifica métodos não idempotentes (POST, PUT / PATCH e DELETE). A solicitação GET não é verificada quanto ao token de autenticidade. Por quê? porque a especificação HTTP declara que as solicitações GET são idempotentes e não devem criar, alterar ou destruir recursos no servidor e a solicitação deve ser idempotentes (se você executar o mesmo comando várias vezes, deverá obter o mesmo resultado todas as vezes).

Além disso, a implementação real é um pouco mais complicada, conforme definido no início, garantindo melhor segurança. O Rails não emite o mesmo token armazenado em todos os formulários. Nem gera e armazena um token diferente sempre. Ele gera e armazena um hash criptográfico em uma sessão e emite novos tokens criptográficos, que podem ser comparados com o armazenado, toda vez que uma página é renderizada. Consulte request_forgery_protection.rb .

Lições

Use authenticity_tokenpara proteger seus métodos não idempotentes (POST, PUT / PATCH e DELETE). Além disso, certifique-se de não permitir solicitações GET que possam modificar recursos no servidor.


EDIT: Verifique o comentário de @erturne sobre as solicitações GET serem idempotentes. Ele explica isso de uma maneira melhor do que eu fiz aqui.

Faisal
fonte
25
@Faisal, é possível para um invasor simplesmente ler / capturar o elemento 'oculto' do formulário para o Serviço A e obter esse token exclusivo gerado para o usuário - já que eles obtiveram acesso à sessão iniciada pelo usuário para o serviço A?
Marcamillion 25/10/10
11
@ camcamillion: se alguém invadiu sua sessão no serviço A, o token de autenticidade não o protegerá. O seqüestrador poderá enviar uma solicitação e poderá continuar.
Faisal
12
@zabba: O Rails lança uma exceção ActionController :: InvalidAuthenticityToken se um formulário for enviado sem o token adequado. Você pode resgatar_da exceção e fazer o processamento que desejar.
Faisal
5
re "Além disso, certifique-se de não fazer solicitações GET que possam modificar recursos no servidor." - isso inclui não usar match () em rotas que poderiam permitir solicitações GET para ações do controlador destinado a receber apenas postagens
Steven Soroka
102
"... e a solicitação deve ser idempotente (se você executar o mesmo comando várias vezes, deverá obter o mesmo resultado todas as vezes)." Apenas um esclarecimento sutil aqui. Seguro significa sem efeitos colaterais. Idempotente significa o mesmo efeito colateral, não importa quantas vezes um serviço seja chamado. Todos os serviços seguros são inerentemente idempotentes porque não há efeitos colaterais. Chamar GET em um recurso de tempo atual várias vezes retornaria um resultado diferente a cada vez, mas é seguro (e, portanto, idempotente).
Erturne
137

O token de autenticidade foi projetado para que você saiba que seu formulário está sendo enviado a partir do seu site. É gerado a partir da máquina na qual é executado com um identificador exclusivo que somente sua máquina pode conhecer, ajudando assim a evitar ataques de falsificação de solicitação entre sites.

Se você está simplesmente tendo dificuldades com os trilhos para negar o acesso ao script AJAX, pode usar

<%= form_authenticity_token %>

para gerar o token correto ao criar seu formulário.

Você pode ler mais sobre isso na documentação .

Topher Fangio
fonte
88

O que é CSRF?

O token de autenticidade é uma contramedida para a falsificação de solicitação entre sites (CSRF). O que é CSRF, você pergunta?

É uma maneira que um invasor pode potencialmente seqüestrar sessões sem saber os tokens de sessão.

Cenário :

  • Visite o site do seu banco, faça o login.
  • Em seguida, visite o site do invasor (por exemplo, anúncio patrocinado de uma organização não confiável).
  • A página do atacante inclui um formulário com os mesmos campos do formulário "Transferir fundos" do banco.
  • O atacante conhece as informações da sua conta e possui campos de formulário pré-preenchidos para transferir dinheiro da sua conta para a conta do atacante.
  • A página do invasor inclui Javascript que envia o formulário ao seu banco.
  • Quando o formulário é enviado, o navegador inclui seus cookies para o site do banco, incluindo o token da sessão.
  • O banco transfere dinheiro para a conta do atacante.
  • O formulário pode estar em um iframe invisível, para que você nunca saiba que o ataque ocorreu.
  • Isso é chamado de falsificação de solicitação entre sites (CSRF).

Solução CSRF :

  • O servidor pode marcar formulários que vieram do próprio servidor
  • Todo formulário deve conter um token de autenticação adicional como um campo oculto.
  • O token deve ser imprevisível (o atacante não consegue adivinhar).
  • O servidor fornece token válido em formulários em suas páginas.
  • O servidor verifica o token quando o formulário é lançado, rejeita os formulários sem o token adequado.
  • Exemplo de token: identificador de sessão criptografado com chave secreta do servidor.
  • O Rails gera automaticamente esses tokens: veja o campo de entrada authenticity_token em todos os formulários.
Rose Perrone
fonte
1
Aqui está uma versão dessa mesma explicação que é menos preciso, mas também menos abstrato: stackoverflow.com/a/33829607/2810305
Lutz Prechelt
Não tenho certeza, mas os navegadores modernos permitem o envio de solicitações não idempotentes (POST / PUT / DELETE) para outro domínio? Eu acho que, deve haver proteção contra tais nas coisas no próprio navegador
divideByZero
45

Exemplo de ataque mínimo que seria evitado: CSRF

No meu site evil.com, convido você a enviar o seguinte formulário:

<form action="http://bank.com/transfer" method="post">
  <p><input type="hidden" name="to"      value="ciro"></p>
  <p><input type="hidden" name="ammount" value="100"></p>
  <p><button type="submit">CLICK TO GET PRIZE!!!</button></p>
</form>

Se você estiver conectado ao seu banco através de cookies de sessão, os cookies serão enviados e a transferência será feita sem que você saiba.

Ou seja, onde o token CSRF entra em jogo:

  • com a resposta GET que retornou o formulário, o Rails envia um parâmetro oculto aleatório muito longo
  • quando o navegador faz a solicitação POST, ele envia o parâmetro e o servidor o aceita apenas se corresponder

Portanto, o formulário em um navegador autêntico seria semelhante a:

<form action="http://bank.com/transfer" method="post">
  <p><input type="hidden" name="authenticity_token" value="j/DcoJ2VZvr7vdf8CHKsvjdlDbmiizaOb5B8DMALg6s=" ></p>
  <p><input type="hidden" name="to"                 value="ciro"></p>
  <p><input type="hidden" name="ammount"            value="100"></p>
  <p><button type="submit">Send 100$ to Ciro.</button></p>
</form>

Assim, meu ataque falharia, pois não estava enviando o authenticity_tokenparâmetro, e não há como eu adivinhar, pois é um número aleatório enorme.

Essa técnica de prevenção é chamada de Padrão de Token de Sincronizador .

Política de Mesma Origem

Mas e se o invasor fizer duas solicitações com JavaScript, uma para ler o token e a segunda para fazer a transferência?

O padrão de token do sincronizador por si só não é suficiente para impedir isso!

É aqui que a Política de mesma origem vem em socorro, como expliquei em: /security/8264/why-is-the-same-origin-policy-so-important/72569# 72569

Como o Rails envia os tokens

Coberto em: Rails: Como funciona o csrf_meta_tag?

Basicamente:

  • Ajudantes de HTML, como form_tagadicionar um campo oculto ao formulário para você, se não for um formulário GET

  • O AJAX é tratado automaticamente pelo jquery-ujs , que lê o token dos metaelementos adicionados ao seu cabeçalho por csrf_meta_tags(presente no modelo padrão) e o adiciona a qualquer solicitação feita.

    O uJS também tenta atualizar o token nos formulários em fragmentos em cache desatualizados.

Outras abordagens de prevenção

Ciro Santilli adicionou uma nova foto
fonte
Obrigado, mas seu argumento sobre confiar na mesma política de origem para não poder ler o token CSRF primeiro parece errado. Então, primeiro, você está dizendo que pode POSTAR para uma origem diferente, mas não consegue ler, parece estranho, mas acho que está correto, mas você pode injetar uma imagem ou tag de script com uma página de acesso à página e vincular um manipulador para analisar a resposta e entendeu sim?
bjm88
@ bjm88 injetar o script onde? No seu site ou no site atacado? Se o site for atacado, permitir a injeção de scripts é uma falha de segurança conhecida e efetivamente penhora o site. Todo site deve combatê-lo através do saneamento básico. Para imagens, não vejo como elas podem ser usadas para um ataque. No site de ataque: você pode modificar seu navegador para permitir a leitura e, assim, penhorar-se automaticamente à vontade :-) mas navegadores decentes o impedem por padrão, tente.
Ciro Santilli publicou
43

O token de autenticidade é usado para impedir ataques de falsificação de solicitação entre sites (CSRF). Para entender o token de autenticidade, você deve primeiro entender os ataques CSRF.

CSRF

Suponha que você é o autor de bank.com. Você tem um formulário em seu site usado para transferir dinheiro para uma conta diferente com uma solicitação GET:

insira a descrição da imagem aqui

Um hacker pode simplesmente enviar uma solicitação HTTP para o servidor dizendo GET /transfer?amount=$1000000&account-to=999999, certo?

insira a descrição da imagem aqui

Errado. O ataque dos hackers não funcionará. O servidor vai pensar basicamente?

Hã? Quem é esse cara tentando iniciar uma transferência. Não é o proprietário da conta, com certeza.

Como o servidor sabe disso? Porque não há session_idcookie para autenticar o solicitante.

Quando você entra com seu nome de usuário e senha, o servidor define um session_idcookie no seu navegador. Dessa forma, você não precisa autenticar cada solicitação com seu nome de usuário e senha. Quando o navegador envia o session_idcookie, o servidor sabe:

Oh, esse é John Doe. Ele fez login com sucesso há 2,5 minutos. Ele está pronto para ir.

Um hacker pode pensar:

Hmm. Uma solicitação HTTP normal não funcionará, mas se eu pudesse colocar minha mão naquele session_idcookie, seria dourado.

O navegador do usuário possui vários cookies configurados para o bank.comdomínio. Toda vez que o usuário faz uma solicitação ao bank.comdomínio, todos os cookies são enviados. Incluindo o session_idcookie.

Portanto, se um hacker conseguir que você faça a solicitação GET que transfere dinheiro para a conta dele, ele será bem-sucedido. Como ele poderia induzi-lo a fazer isso? Com falsificação de solicitação entre sites.

É bem simples, na verdade. O hacker pode levá-lo a visitar o site dele. Em seu site, ele poderia ter a seguinte tag de imagem:

<img src="http://bank.com/transfer?amount=$1000000&account-to=999999">

Quando o navegador dos usuários encontrar essa tag de imagem, ele fará uma solicitação GET para esse URL. E, como a solicitação vem do navegador, ela envia todos os cookies associados bank.com. Se o usuário tiver entrado recentemente em bank.com... o session_idcookie será definido e o servidor pensará que o usuário pretendia transferir US $ 1.000.000 para a conta 999999!

insira a descrição da imagem aqui

Bem, apenas não visite sites perigosos e você ficará bem.

Isso não basta. E se alguém postar essa imagem no Facebook e ela aparecer no seu mural? E se for injetado em um site que você está visitando com um ataque XSS?

Não é tão ruim. Somente solicitações GET são vulneráveis.

Não é verdade. Um formulário que envia uma solicitação POST pode ser gerado dinamicamente. Aqui está o exemplo do Guia de Segurança do Rails :

<a href="http://www.harmless.com/" onclick="
  var f = document.createElement('form');
  f.style.display = 'none';
  this.parentNode.appendChild(f);
  f.method = 'POST';
  f.action = 'http://www.example.com/account/destroy';
  f.submit();
  return false;">To the harmless survey</a>

Token de Autenticidade

Quando você ApplicationControllertem isso:

protect_from_forgery with: :exception

Este:

<%= form_tag do %>
  Form contents
<% end %>

É compilado para isso:

<form accept-charset="UTF-8" action="/" method="post">
  <input name="utf8" type="hidden" value="&#x2713;" />
  <input name="authenticity_token" type="hidden" value="J7CBxfHalt49OSHp27hblqK20c9PgwJ108nDHX/8Cts=" />
  Form contents
</form>

Em particular, o seguinte é gerado:

<input name="authenticity_token" type="hidden" value="J7CBxfHalt49OSHp27hblqK20c9PgwJ108nDHX/8Cts=" />

Para se proteger contra ataques CSRF, se o Rails não vir o token de autenticidade enviado junto com uma solicitação, ela não considerará a solicitação segura.

Como um invasor deve saber o que é esse token? Um valor diferente é gerado aleatoriamente cada vez que o formulário é gerado:

insira a descrição da imagem aqui

Um ataque de Cross Site Scripting (XSS) - é assim. Mas essa é uma vulnerabilidade diferente para um dia diferente.

Adam Zerner
fonte
39

O Authenticity Tokenmétodo is rails 'para impedir ' ataques de falsificação de solicitação entre sites (CSRF ou XSRF) ' .

Para simplificar, garante que as solicitações PUT / POST / DELETE (métodos que podem modificar o conteúdo) ao seu aplicativo Web sejam feitas no navegador do cliente e não de terceiros (invasor) que tenham acesso a um cookie criado no lado do cliente.

e eu
fonte
34

desde que Authenticity Tokené tão importante, e no Rails 3.0+ você pode usar

 <%= token_tag nil %>

para criar

<input name="authenticity_token" type="hidden" value="token_value">

qualquer lugar

Yuan He
fonte
Isso foi útil para mim. Na verdade, eu estava tentando fazer XSSna página de login, não para fins nefastos, mas para criar uma nova sessão com o nome de usuário pré-preenchido. Agora eu sei que posso apenas usar value="token_value".
Michael - Onde está Clay Shirky,
27

Cuidado com o mecanismo do token de autenticidade pode resultar em condições de corrida se você tiver várias solicitações simultâneas do mesmo cliente. Nessa situação, seu servidor pode gerar vários tokens de autenticidade quando deve haver apenas um, e o cliente que recebe o token anterior em um formulário falhará na próxima solicitação porque o token do cookie da sessão foi substituído. Há uma redação sobre esse problema e uma solução não totalmente trivial aqui: http://www.paulbutcher.com/2007/05/race-conditions-in-rails-sessions-and-how-to-fix-them/

jdp
fonte
11

Métodos Onde authenticity_tokené necessário

authenticity_token é necessário no caso de métodos idempotentes, como postar, colocar e excluir, porque os métodos idempotentes estão afetando os dados.

Por que é necessário

É necessário impedir ações más. authenticity_token é armazenado na sessão, sempre que um formulário é criado nas páginas da Web para criação ou atualização de recursos, um token de autenticidade é armazenado no campo oculto e enviado com o formulário no servidor. Antes de executar a ação, o usuário enviado authenticity_token é checado e authenticity_tokenarmazenado na sessão. Se authenticity_tokenfor o mesmo, o processo continuará, caso contrário, ele não executará ações.

uma
fonte
3
Na verdade, não é o contrário? GET é idempotente, pois sua chamada não deve alterar o estado do sistema, onde os verbos PUT POST e DELETE NÃO são verbos idempotentes, pois alteram o estado do sistema. IE: authenticity_token é necessário no caso de métodos NÃO idempotentes.
Jean-Théo
2
@ Jean-Daube, uma: idempotent significa que, se feito duas vezes, a ação acontece apenas uma vez. GET, PUT e DELETE são idempotentes: w3.org/Protocols/rfc2616/rfc2616-sec9.html A propriedade key aqui não é idempotency, mas se o método alterar ou não os dados, que é chamado "Método seguro" ou não.
Ciro Santilli escreveu
6

O que é um authentication_token?

Essa é uma sequência aleatória usada pelo aplicativo rails para garantir que o usuário esteja solicitando ou executando uma ação na página do aplicativo, não em outro aplicativo ou site.

Por que é necessário um authentication_token?

Para proteger seu aplicativo ou site contra falsificação de solicitação entre sites.

Como adicionar um authentication_token a um formulário?

Se você estiver gerando um formulário usando a tag form_for, um authentication_token será adicionado automaticamente, caso contrário você poderá usar <%= csrf_meta_tag %>.

Pradeep Sapkota
fonte