Photo by Tolga Ulkan on Unsplash

Considerações sobre cookies, SameSite e iframes em arquiteturas que usam OAuth e OpenID Connect

Dores de cabeça podem ser evitadas se você entender como esses atores funcionam

André Ravazzi
Semantix
Published in
10 min readJul 1, 2021

--

Atualmente temos acompanhado diversos vazamentos de dados de empresas grandes e governos, bem como atuação de grupos de ransomware que sequestram os sistemas de empresas críticas através da exploração de vulnerabilidades dos próprios sistemas ou de bibliotecas de terceiros.

Uma das vulnerabilidades clássicas em aplicações web é o trânsito indesejado de cookies através de sites em diferentes domínios. Se um cookie pode transitar arbitrariamente entre domínios, as informações neles contidas podem ser roubadas através de ataques de adversariais como o XSS e CSRF. Com dados de sessão que geralmente são armazenados em cookies, um atacante pode personificar um usuário e ter acesso a dados privados. Ao longo dos anos, os desenvolvedores dos browsers adicionaram defesas contra esse tipo de problema, que na maioria das vezes tem origem em falhas no código dos próprios sites e aplicativos, não no navegador em si. Com o aumento incrível de vazamentos de dados e preocupações com privacidade, muitas mudanças em termos de segurança foram adicionados nos últimos tempos. Uma delas é a propriedade SameSite para os cookies. Por exemplo, a partir da versão 84 do Google Chrome se um cookie é recebido sem propriedade SameSite, o próprio navegador adicionará ao cookie SameSite=Lax e sua escrita poderá ser bloqueada caso tenha como origem uma requisição cross-site, ou seja, de um domínio diferente (isso será abordado logo à frente).

1: Alerta do Google Chrome sobre cookies que não tem a propriedade SameSite e são cross-origin

Em um cenário, se a escrita de cookies for devido à navegação realizada pelo usuário, cujo nome técnico é top-level navigation (ou seja, clicar em um link ou mudar a URL no navegador), os cookies recebidos são escritos normalmente mesmo se forem de uma requisição cross-site, e a propriedade SameSite=Lax é considerada pelo navegador. Contudo, se for resultado de uma navegação low-level (ou seja, redirecionamentos e scripts do próprio site realizando redirecionando), a escrita destes cookies serão bloqueadas se a requisição for cross-site. Vale pontuar que o "antônimo" de cross-site é same-site. Esses dois termos serão bastante usados neste artigo. Um outro fato importante é que as requisições podem ser cross/same-site e os cookies, first/third-party. Ou seja, se é um cookie first-party é escrito através de uma requisição same-site e um cookie third-party é escrito através de uma requisição (ou request) cross-site.

Mas às vezes, escrita cruzada de cookies (third-party cookies) é interessante e necessária para a aplicação, por exemplo, quando depende-se de mecanismos de OAuth2 para autenticação.

OAuth e OpenID Connect (OIDC), resumidamente

2: Fluxo OAuth “visível” ao usuário final.

Vamos supor que um app de gestão financeira queira acessar os dados do seu extrato bancário para mostrar como está a sua saúde financeira. Ao invés de pedir seu login e senha do internet banking, você clicará em um botão de login especial (A) e o app pedirá ao banco uma autorização para coletar dados da sua conta em seu nome. Para isso, o app irá te redirecionar para o site do banco que irá exibir uma tela com uma lista de itens que o app de gestão financeira irá fazer no seu internet banking (B). Se você não estiver logado no site do banco, deverá fazê-lo antes de ver essa tela. Ao clicar em aceitar, o site do banco irá te redirecionar de volta pro app e então, depois de alguns segundos, você verá os demonstrativos através do seu novo app de gestão financeira (C).

Ao clicar em "aceitar" você consentiu com os escopos de atividade do app, ou seja, foi pedida uma “autorização” para você, detentor da informação (ou resource owner) para acessar seus extratos bancários e essa autorização foi confirmada (B). Assim, o banco (neste contexto, Authorization Server) verificou se você confia no app e prosseguiu para entregar as informações a eles (C).

3: O Id Token é equivalente a um crachá na qual as pessoas conseguem saber seu nome e e-mail sem precisar te perguntar.

O OpenID Connect entra apenas como uma camada adicional no protocolo OAuth, transitando informações extras sobre o Resource Owner (ou seja, você, neste caso) através de um outro token chamado Id Token. Este é obtido juntamente com o access token (que é a chave de "identifica" quem está pedindo informações, no caso, o app) entre os passos (B) e (C) e contém informações como seu nome e e-mail. É como se fosse um crachá em um evento. Um dos objetivos deste Id Token é fornecer informações públicas do usuário sem a necessidade de requisições ao Resource Server, além de adicionar a funcionalidade de autenticação ao framework de OAuth.

Simplificando, o OAuth é um protocolo que trabalha com a ideia de autorização e delegação de autoridade, mas não necessariamente com autenticação. Basicamente o que queremos com o OAuth é permitir que uma aplicação client (o app de finanças) tenha autoridade para receber informações de uma fonte de dados (o seu banco, Resource Server) sem precisar se autenticar como um usuário (Resource Owner) dessa fonte, mas sim como um client dela buscando dados em nome do usuário.

4: Uma das primeiras versões do Yelp pedindo suas credenciais da conta de e-mail.

Há um tempo atrás, antes da convenção e disseminação do OAuth, alguns aplicativos de gestão financeira pediam o login e senha dos usuários para entrar na conta bancária pelo internet banking, coletavam os dados financeiros e então estruturavam as dashboards do app, por exemplo. Ou se um outro app quisesse fazer algo muito legal com seus contatos, você deveria fornecer as credenciais da sua conta do Gmail. Apesar de isso parecer absurdo, era uma prática comum de uns 10–15 anos atrás. Pode-se imaginar que práticas como o phishing eram bem mais comuns e fáceis de se aplicar, e que perda inadvertida de dados na fonte de dados também aconteciam.

Mesmo depois da criação do OAuth, sua disseminação demorou um pouco principalmente devido ao funcionamento dos navegadores. O fluxo de OAuth depende de redirecionamentos e tráfego de dados entre diferentes domínios, o chamado cross-site requests, mas os navegadores daquela época não permitiam essa mecânica. Hoje em dia as coisas são bem diferentes nesta. Em um texto futuro iremos explorar mais sobre essas duas tecnologias muito presentes no dia-a-dia dos usuários.

Um fluxo de OAuth e OpenID Connect na vida real

Na Semantix há um produto chamado SDP, ou Semantix Data Plataform, que engloba diversas aplicações para extração, mineração e apresentação de dados integradas entre si. Algumas dessas aplicações possuem interfaces próprias e são disponibilizadas aos usuários para que desfrutem de suas funcionalidades. Elas são provisionadas separadamente para cada cliente através de deployments de um cluster de kubernetes, logo, cada uma delas possui uma URL própria. E tudo isso precisa ser servido na aplicação front-end, que é um SPA em feito com React, de maneira transparente. Para isso são utilizados iframes que embeddam as aplicações (vamos chamar de aplicações externas) nesta interface front-end chamada SPD-UI.

5: Aplicação externa de visualização servida através de um iframe dentro da aplicação front-end principal.

Existe ainda uma API de autorização que implementa OAuth2 e OpenID Connect para autenticar o usuário e autorizar cada aplicação de acordo com seus escopos. Quando o usuário acessa a página Visualization por exemplo (após fazer login na plataforma), a aplicação de mesmo nome é carregada dentro de um iframe, realizando um fluxo de autorização antes de exibir o conteúdo da dashboard. Esse fluxo ocorre em várias etapas com redirecionamentos sucessivos para cada uma, na qual os cookies necessários transitam de uma request para outra.

6: Fluxo simplificado de autorização de uma aplicação externa usando OAuth2 na qual os cookies de autenticação transitam entre as requests através de redirecionamentos HTTP.

O "caminho feliz" consiste nos seguintes passos:

1. Aplicação Visualization é iniciada dentro do iframe

2. Redirect #1 para /login do Visualization sem credenciais. Um cookie de sessão é enviado para o passo (3) para identificar a sessão requisitante

3. Redirect #2 para /auth do Autenticador para coletar as credenciais. Este recebe o cookie de sessão do passo (2) e envia um novo cookie com o access token e outras informações

4. Redirect #3 para /dashboard do Visualization e início da aplicação, já com o IdToken e o Access Token validados

Um detalhe importante é que quem "forma" a URL de redirect é o backend do respectivo passo. Ou seja, cada redirecionamento é primeiramente processado por um backend, que por sua vez retorna ao navegador a URL de redirect corretamente formada. É neste momento que o backend recebe os tokens que serão trocados por um token de acesso, seguindo os passos descritos na seção acima sobre o OAuth.

Se pegarmos uma lupa e olharmos o passo (4) com mais detalhes, veremos que ele possui mais algumas etapas antes de chegar na dashboard da aplicação.

7: Passo (4) “ampliado”, com detalhes sobre a autenticação com OpenID Connect.

Continuando com os passos do "caminho feliz":

4.1. Redirect #4 do Autenticador para o serviço de OIDC dentro da aplicação Visualization, no endpoint /oidc. É esse serviço que vai processar a autenticação via OpenID Connect e devolver para o frontend um IdToken válido para realizar as transações. Este recebe os cookies do Autenticador com os tokens pertinentes

4.2. Redirect #5 novamente para /login, agora com o IdToken corretamente presente nos cookies

4.3. Redirect #6 finalmente para /dashboard, iniciando a aplicação

O problema da autorização com cross-site

Nem sempre o caminho feliz é o que acontece na fase de implementação. Durante o desenvolvimento foi notado que no Chrome (a partir da versão 84) ocorria um looping de redirecionamentos (dentro do iframe) dos passos (2) a (4.2) até que o próprio navegador cancelasse as requisições, culminando na aplicação Visualization não sendo carregada.

8: Quando o navegador percebe que há um looping infinito de redirecionamentos, ele cancela a requisição e retorna este erro.

Antes de falarmos sobre a solução que chegamos, precisamos entender como funciona a mecânica de escrita de cookies neste contexto.

Os browsers levam em consideração, entre outros parâmetros, a definição de same-site para permitir ou não a escrita de cookies. A regra geral define que duas páginas podem ser consideradas same-site se tiverem o mesmo domínio + eTDL (binômio conhecido como eTLD+1).

9: Fonte: https://web.dev/same-site-same-origin/#site

Ou seja, semantix.io, www.semantix.io e deployment.semantix.io são considerados same-site. Então um cookie que transita de oauth2.semantix.io para deployment.semantix.io não geraria problema de cross-site ou third-party cookies. Já entre semantix.io e semantix.com pela regra geral não podem ser considerados same-site pois possuem o TDL diferente, um é .io e outro .com. Se uma requisição tentar escrever cookies através de uma requisição entre semantix.io e semantix.com o browser bloqueará a escrita mostrando a mensagem da imagem 1 do início do artigo. Para realizar essa operação é necessário então adicionar as propriedades SameSite=None; Secure; ao cookie da requisição cross-site.

Durante a análise, foi verificado que todos os redirecionamentos entre os passos (1) e (4) enviavam cookies com as propriedades SameSite=None; Secure; menos o passo (4.1), que recebia os cookies do Autenticador, realizava o processamento de OIDC e enviava os cookies resultantes para a rota /login da aplicação de Visualização, representado pelo passo (4.2). Na realidade o passo (4.1) acontecia em um proxy que ficava dentro da própria aplicação de Visualização. Esse fato provavelmente estava causando o looping de redirects e impedindo o acesso à aplicação externa em questão, pois se os cookies do passo (4.1) não são enviados ao passo (4.2), o endpoint /login irá entender que o usuário acabou de iniciar a autenticação e do passo (4.1) ele redireciona ao passo (2) ao invés do (4.2).

10: looping entre os passos (2) e (4.2).

Porém, analisando os diagramas, vemos que tanto o passo (4.1) quanto todos os outros aconteciam em um contexto same-site pois o binômio eTDL+1 era o mesmo para a cadeia de eventos dentro do iframe.

O entendimento e a solução

Assim, devemos nos perguntar do porquê o Chrome bloquear os cookies no passo (4.1). A resposta estava na regra de navegação top/low-level explicada anteriormente: como os redirecionamentos para iniciar a aplicação de Visualização ocorriam dentro de um iframe e o eTDL+1 de dentro de tal iframe (.semantix.io) era diferente do contexto-pai, ou seja, da página que continha o iframe (.semantix.com), o navegador entendia que esses redirecionamentos eram cross-site, logo, podendo ocorrer o bloqueio de escrita dos cookies se as propriedades corretas não estiverem presentes, SameSite=None; Secure;.

Esse proxy que fazia o meio de campo no passo (4.1) era uma biblioteca disponível no Github. Logo a primeira solução pensada foi ver na documentação como conseguiríamos adicionar mais uma propriedade nos cookies enviados. Mas não fora tão simples, pois descobrimos que a biblioteca não fornecia dispositivos para alterar esses cookies. A solução final foi um fork realizado na biblioteca para que pudéssemos alterar o código-fonte dela e adicionar os cookies SameSite=None; Secure; que precisávamos.

Conclusões

Este foi um problema interessante de se resolver. Em primeiro lugar, ler as documentações e entender o mecanismo das ferramentas usadas são de suma importância. E em segundo, não confiar totalmente em bibliotecas de terceiros, pois podem conter falhas não-intencionais que o autor não imaginou que o seu caso de uso poderia trazer.

Mais informações interessantes e referências

Bonus: OAuth em aba anônima no Google Chrome

Também durante o desenvolvimento percebemos que nenhuma aplicação externa realizava login via OAuth ou funcionava corretamente quando abríamos no Google Chrome usando a aba anônima. Com uma breve análise verificamos que o navegador não aceita mais cookies de terceiros em aba anônima de forma padrão. E para que a aplicação funcionasse seria necessário configurar o Google Chrome para tal.

--

--

André Ravazzi
Semantix

Product Engineer @ Builderbinder | Aerospace Engineer | Photography enthusiast | https://unsplash.com/@amravazzi