Estamos expondo uma API que os parceiros só podem usar em domínios que eles tenham registrado conosco. Seu conteúdo é parcialmente público (mas de preferência apenas para ser mostrado nos domínios que conhecemos), mas é principalmente privado para nossos usuários. Assim:
Para determinar o que é mostrado, nosso usuário deve estar logado conosco, mas isso é tratado separadamente.
Para determinar onde os dados são mostrados, uma chave de API pública é usada para limitar o acesso aos domínios que conhecemos e, acima de tudo, para garantir que os dados privados do usuário não sejam vulneráveis a CSRF .
Esta chave API é realmente visível para qualquer pessoa, não autenticamos nosso parceiro de nenhuma outra forma e não precisamos de REFERER . Ainda assim, é seguro:
Quando o nosso get-csrf-token.js?apiKey=abc123
é solicitado:
Procure a chave abc123
no banco de dados e obtenha uma lista de domínios válidos para essa chave.
Procure o cookie de validação CSRF. Se não existir, gere um valor aleatório seguro e coloque-o em um cookie de sessão somente HTTP . Se o cookie existisse, obtenha o valor aleatório existente.
Crie um token CSRF a partir da chave da API e o valor aleatório do cookie e assine-o . (Em vez de manter uma lista de tokens no servidor, estamos assinando os valores. Ambos os valores poderão ser lidos no token assinado, tudo bem.)
Defina a resposta para não ser armazenada em cache, adicione o cookie e retorne um script como:
var apiConfig = apiConfig || {};
if(document.domain === 'expected-domain.com'
|| document.domain === 'www.expected-domain.com') {
apiConfig.csrfToken = 'API key, random value, signature';
// Invoke a callback if the partner wants us to
if(typeof apiConfig.fnInit !== 'undefined') {
apiConfig.fnInit();
}
} else {
alert('This site is not authorised for this API key.');
}
Notas:
O exposto acima não impede que um script do lado do servidor falsifique uma solicitação, mas apenas garante que o domínio corresponda se solicitado por um navegador.
A mesma política de origem para JavaScript garante que um navegador não possa usar XHR (Ajax) para carregar e inspecionar a fonte JavaScript. Em vez disso, um navegador normal só pode carregá-lo usando <script src="https://our-api.com/get-csrf-token.js?apiKey=abc123">
(ou um equivalente dinâmico) e, em seguida, executará o código. Claro, seu servidor não deve suportar Compartilhamento de recursos de origem cruzada nem JSONP para o JavaScript gerado.
Um script de navegador pode alterar o valor de document.domain
antes de carregar o script acima. Mas a mesma política de origem só permite encurtar o domínio removendo prefixos, como reescrever subdomain.example.com
apenas para example.com
, ou myblog.wordpress.com
para wordpress.com
, ou em alguns navegadores até bbc.co.uk
para co.uk
.
Se o arquivo JavaScript for obtido usando algum script do lado do servidor, o servidor também obterá o cookie. No entanto, um servidor de terceiros não pode fazer o navegador de um usuário associar esse cookie ao nosso domínio. Portanto, um token CSRF e um cookie de validação que foram buscados usando um script do lado do servidor só podem ser usados por chamadas subsequentes do lado do servidor, não em um navegador. No entanto, essas chamadas do lado do servidor nunca incluirão o cookie do usuário e, portanto, só podem buscar dados públicos. Esses são os mesmos dados que um script do lado do servidor pode extrair diretamente do site do parceiro.
Quando um usuário efetua login, defina algum cookie de usuário da maneira que desejar. (O usuário pode já ter feito login antes de o JavaScript ser solicitado.)
Todas as solicitações de API subsequentes para o servidor (incluindo solicitações GET e JSONP) devem incluir o token CSRF, o cookie de validação CSRF e (se conectado) o cookie do usuário. O servidor agora pode determinar se a solicitação é confiável:
A presença de um token CSRF válido garante que o JavaScript foi carregado do domínio esperado, se carregado por um navegador.
A presença do token CSRF sem o cookie de validação indica falsificação.
A presença do token CSRF e do cookie de validação CSRF não garante nada: pode ser uma solicitação forjada do lado do servidor ou uma solicitação válida de um navegador. (Não pode ser uma solicitação de um navegador feita a partir de um domínio não compatível.)
A presença do cookie do usuário garante que o usuário esteja conectado, mas não garante que o usuário seja um membro do parceiro fornecido, nem que o usuário esteja visualizando o site correto.
A presença do cookie do usuário sem o cookie de validação CSRF indica falsificação.
A presença do cookie do usuário garante que a solicitação atual seja feita por meio de um navegador. (Supondo que um usuário não insira suas credenciais em um site desconhecido e supondo que não nos importamos com os usuários usando suas próprias credenciais para fazer alguma solicitação do lado do servidor.) Se também tivermos o cookie de validação CSRF, então esse cookie de validação CSRF foi também recebeu usando um navegador. Em seguida, se também tivermos um token CSRF com uma assinatura válida, eo número aleatório no cookie de validação CSRF corresponde ao daquele token CSRF, então o JavaScript para esse token também foi recebido durante a mesma solicitação anterior durante a qual o cookie CSRF foi definido, portanto, usando um navegador. Isso também implica que o código JavaScript acima foi executado antes que o token fosse definido e que, naquele momento, o domínio era válido para a chave API fornecida.
Portanto: o servidor agora pode usar com segurança a chave API do token assinado.
Se em algum ponto o servidor não confiar na solicitação, um 403 Forbidden será retornado. O widget pode responder a isso mostrando um aviso ao usuário.
Não é necessário assinar o cookie de validação CSRF, pois o estamos comparando ao token CSRF assinado. Não assinar o cookie torna cada solicitação HTTP mais curta e a validação do servidor um pouco mais rápida.
O token CSRF gerado é válido indefinidamente, mas apenas em combinação com o cookie de validação, de forma eficaz até que o navegador seja fechado.
Podemos limitar o tempo de vida da assinatura do token. Poderíamos excluir o cookie de validação CSRF quando o usuário fizer logout, para atender à recomendação do OWASP . E para não compartilhar o número aleatório por usuário entre vários parceiros, pode-se adicionar a chave de API ao nome do cookie. Mas mesmo assim, não é possível atualizar facilmente o cookie de validação CSRF quando um novo token é solicitado, pois os usuários podem estar navegando no mesmo site em várias janelas, compartilhando um único cookie (que, ao ser atualizado, seria atualizado em todas as janelas, após o que o O token de JavaScript nas outras janelas não corresponderia mais a esse único cookie).
Para aqueles que usam OAuth, consulte também OAuth e Widgets do lado do cliente , de onde tive a ideia do JavaScript. Para o uso da API no lado do servidor , em que não podemos contar com o código JavaScript para limitar o domínio, estamos usando chaves secretas em vez das chaves API públicas.