Recentemente, descobri um novo tipo de vulnerabilidade XSS - DOM XSS usando Web Messaging. Depois de ler muitas páginas de documentação, decidi criar um único material para explorar o XSS por meio dessa tecnologia. Descreverei aqui as falhas de segurança mais comuns e como explorá-las.
O QUE É DOM?
DOM (Document Object Model) é uma interface de programação independente de plataforma e linguagem que permite que programas e scripts acessem o conteúdo de documentos HTML, XHTML, XML, bem como alterem seu conteúdo (conteúdo, estrutura, design). Vulnerabilidades baseadas em DOM ocorrem quando um site contém um script que pega um valor controlado pelo invasor e o passa para uma função insegura chamada coletor. Um dos tipos de vulnerabilidades baseadas em DOM é o XSS baseado em DOM. Vulnerabilidades desse tipo ocorrem quando o JavaScript recebe dados do usuário e os passa para um coletor que tem a capacidade de executar código dinamicamente, como eval(), document.write() ou innerHTML.
COMO OS XSS BASEADOS EM DOM SÃO EXPLORADOS?
A fonte mais popular de DOM XSS são os URLs das páginas. Esse valor é acessado pelo JavaScript por meio do objeto window.location. A URL é então processada dentro do script legítimo que existe na página. Nesse caso, o invasor pode criar um link com uma carga maliciosa e enviá-lo à vítima. Quando a vítima clicar no link, o script de origem da página, usando o objeto window.location, solicitará o endereço da página atual e executará o carregamento contido na URL. Aqui está o exemplo mais simples dessa vulnerabilidade.
<body>
<script>document.write(location.href);</script>
</body>
Ao receber essa página, o navegador executará automaticamente o script e gravará uma string no corpo da página (document.write), obtendo seu valor de location.href (o endereço completo da página). No entanto, o usuário controla o valor de location.href e pode colocar uma string arbitrária nele. Portanto, para explorar uma vulnerabilidade XSS, basta gerar o seguinte link, e ao abri-lo em um navegador, um script malicioso será executado.
http://website.com/index.html#<script>alert(1)</script>
MECANISMOS DE SEGURANÇA DE INTERAÇÃO ENTRE DOMÍNIOS
Vamos imaginar que temos uma página de um site que desenvolvemos. Colocamos um elemento <iframe> nele e especificamos um site arbitrário como sua fonte.
Se não houvesse mecanismos de segurança em vigor, a comunicação entre os elementos da página da Web pai e a página da Web renderizada no <iframe> poderia ser crítica para os dados do usuário. Qualquer script residente na página pai pode acessar qualquer dado colocado dentro do <iframe>.
Vejamos um exemplo. O invasor coloca em sua página um <iframe> com o endereço do banco da Internet no qual o usuário foi previamente autorizado. Então, de alguma forma, ele atrai a vítima para sua página. Como resultado, um invasor pode obter acesso a qualquer dado de usuário da conta bancária da vítima na Internet. Para evitar isso, dois mecanismos de proteção foram criados:
Same Origin Policy (SOP) Previne ataques entre domínios, bloqueando a leitura de recursos baixados de outra fonte. A origem é identificada pelo seguinte trio de parâmetros: esquema, nome de host totalmente qualificado e porta. Quando pelo menos um dos parâmetros das fontes não coincide, a troca de dados entre os recursos é proibida. Por exemplo, se a página em http://example.com/index.html tentar exibir dados da fonte https://example.com/ , essa ação será proibida, porque as fontes não correspondem ao protocolo.
Compartilhamento de recursos entre origens. As restrições da política da mesma fonte provaram ser muito restritivas. Portanto, para afinar o acesso aos recursos, eles criaram um "mecanismo de compartilhamento de recursos entre diferentes fontes" - CORS. Regula três categorias de interação em rede:
gravação de várias fontes. Esta categoria rege redirecionamentos, envios de formulários e cliques em links. Por padrão, todas essas ações são permitidas;
inserção de várias fontes. Esta categoria rege os elementos carregados via <link>, <script>, <img>, <video>, <audio>, <iframe> e outras tags. Por padrão, tudo isso é permitido, mas a tag <iframe> pode ser ainda mais restrita com o cabeçalho X-Frame-Options;
leitura de diferentes fontes. Esta categoria rege os elementos carregados via AJAX e fetch. Por padrão, esses recursos estão desativados.
API DE MENSAGENS DA WEB
Window.postMessage() é um método que permite passar dados entre documentos carregados em diferentes janelas ou frames, inclusive entre documentos recebidos de diferentes domínios. Se os mecanismos de segurança (SOP e CORS) estiverem corretamente configurados no site, o uso de postMessage será a única forma disponível para transferência de dados entre documentos em domínios diferentes. Solicitações feitas usando outros métodos (como XMLHttpRequest ou Fetch API) serão bloqueadas de acordo com SOP e CORS.
A busca por informações sobre a tecnologia postMessages me levou à documentação oficial do Mozilla. Normalmente, os scripts de origens diferentes têm permissão para acessar uns aos outros se e somente se estiverem em conformidade com a política de mesma origem (mesmo esquema, nome de host e porta), incluindo scripts dentro de um quadro que acessam o pai do quadro. Window.postMessage() fornece um mecanismo controlado para contornar essa limitação com segurança. Também encontrei maneiras na documentação de fornecer uma conexão entre a página pai e a página dentro do quadro. Em geral, o filho <iframe> deve ser inscrito no evento "mensagem":
window.addEventListener("message", (event) => {...}, false);
onde message é a mensagem esperada.
Nesse caso, a página pai pode enviar uma mensagem para o quadro filho usando este método:
postMessage(message, targetOrigin, transfer)
Onde message é a mensagem a ser enviada, esses dados são serializados automaticamente para passar para o quadro filho e targetOrigin especifica a origem da janela pai.
O targetOrigin pode ser um asterisco, o que indica que qualquer pessoa pode receber a mensagem. Ou você pode especificar um URI específico que será verificado dentro do ouvinte no quadro filho. Se a origem da página não corresponder ao targetOrigin dentro desta função, o evento não será despachado. Esse mecanismo fornece controle sobre para onde as mensagens são enviadas. Por exemplo, se postMessage for usado para enviar uma senha, esse argumento deverá corresponder ao URI de destino. Isso impedirá que um invasor roube a senha por meio de um recurso não confiável.
XSS BASEADO EM DOM VIA MENSAGEM DA WEB
Vamos dar uma olhada em como podemos usar mensagens da web como fonte para gerar e explorar DOM XSS em uma página de destino. Se a página inicial manipular as mensagens recebidas de maneira insegura (por exemplo, validando incorretamente a origem das mensagens recebidas), os eventos gerados pelo ouvinte podem se tornar coletores de carga e origem XSS inseguros.
Por exemplo, um invasor pode colocar conteúdo malicioso em sua página <iframe>e usar um método postMessage()para enviar dados por meio de uma mensagem da Web para um ouvinte de evento vulnerável. O ouvinte então passará a carga para o receptor na página filha.
Isso significa que as mensagens da web podem ser usadas como fonte de carregamento para qualquer um dos coletores de páginas da web filhos. O resultado da exploração da vulnerabilidade depende de como o documento de destino processa as mensagens recebidas.
Se a página de destino confiar totalmente no remetente, não verificar os dados recebidos dele e transmiti-los ao destinatário sem distorção, essa vulnerabilidade permitirá que um invasor execute qualquer ação em nome do usuário no contexto do recurso de destino (comprometer o usuário).
Vejamos diferentes opções de código vulnerável.
Verificação de origem ausente
A página inicial de um site legítimo contém o seguinte script.
Código:Copiar para área de transferência
<script>
window.addEventListener('message', function(e){
document.getElementById('qwe').innerHTML = e.data;
})
</script>
Nesse caso, a exploração da vulnerabilidade é possível por meio de um <iframe> colocado no servidor do invasor, com aproximadamente o seguinte conteúdo:
Código:Copiar para área de transferência
<iframe src="http://target.com/" onload="this.contentWindow.postMessage('<img src=1 onerror=alert`1`>','*')">
Como você pode ver, o ouvinte da mensagem não verifica a origem e o método postMessage especifica o "asterisco" targetOrigin. O processo de operação ficará assim:
postMessage() na carga útil criará uma mensagem que será enviada para a página no <iframe> após o conteúdo do <iframe> ser carregado;
O EventListener da página de destino receberá a mensagem da página de ataque;
O EventListener irá procurar o elemento com ID qwe;
O EventListener irá inserir a mensagem recebida na tag <div> encontrada;
como resultado, uma tag img contendo um endereço de origem incorreto será inserida na página de destino. Isso gerará um erro, e o tratamento desse erro executará um código arbitrário especificado pelo invasor, ou seja, chamará alert(1).
verificação de origem
Vamos imaginar que a página inicial de um site legítimo contenha o seguinte script:
Código:Copiar para área de transferência
window.addEventListener('message', function(e) {
if (e.origin.indexOf('normal-website.com') > -1) {
eval(e.data);
}
});
Caso o ouvinte verifique o Origin, pode haver problemas na implementação dessas verificações. Por exemplo, a verificação usando o método indexOf pode ser contornada escrevendo um script malicioso em um domínio multinível que contém a substring desejada. Por exemplo, assim:
Código:Copiar para área de transferência
<http://www.normal-website.com.attacker.com>
Da mesma forma, as verificações que são realizadas por meio dos métodos startsWith (), endsWith () e outros são ignoradas.
Análise JSON
Não é incomum que um ouvinte execute uma das várias ações predefinidas, dependendo de qual mensagem específica receberá quando postMessage for executado. Entende-se que o quadro filho será capaz de reagir de forma diferente a diferentes mensagens vindas do elemento pai. Essa variabilidade de ações será descrita no filho <iframe> com um script semelhante:
JavaScript:Copiar para área de transferência
<script>
window.addEventListener('message', function(e) {
var iframe = document.createElement('iframe'), ACMEplayer = {element: iframe}, d;
document.body.appendChild(iframe);
try {
d = JSON.parse(e.data);
} catch(e) {
return;
}
switch(d.type) {
case "img":
ACMEplayer.element.src = d.url;
break;
case "redirect":
window.location.replace(d.redirectUrl);
break;
}
}, false);
</script>
Nesse caso, dependendo da mensagem que o iframe filho recebe, ele pode fazer uma das duas coisas:
Se uma chamada postMessage resultar em um iframe filho transmitindo uma mensagem contendo {"type" : "img", "url" : "https://domain.com"}, o iframe filho atribuirá o URL especificado à propriedade src do elemento ACMEplayer da página exibida.
Se uma chamada postMessage para um iframe filho resultar em uma mensagem contendo {"type" : "redirect", "url" : "https://domain.com"}, o iframe filho navegará da página atual para o URL especificado.
Para explorar a vulnerabilidade, é necessário gerar um objeto JSON correto, que será passado para o frame filho e processado corretamente pelo elemento case interno. Exemplo de carga maliciosa:
Código:Copiar para área de transferência
<iframe src=https://victim.com/ onload='this.contentWindow.postMessage("{"type":"img","url":"javascript:alert(1)"}","*")'>
Como resultado do envio dessa mensagem, o elemento ACMEplayer em src será definido com o valor correspondente ao URL de nossa carga maliciosa. Como o segundo argumento especifica que qualquer targetOrigin é permitido para postMessage e o manipulador de eventos não inclui nenhuma forma de verificação de origem, a carga útil será instalada e executada quando a vítima navegar para essa página.
CONCLUSÕES
Analisamos detalhadamente as vulnerabilidades que surgem quando a Web Messaging API é tratada de forma descuidada para organizar a transferência de dados entre a página pai e aquela carregada dentro do <iframe>. Aqui estão algumas dicas para ajudá-lo a evitar esses problemas em seu site:
verifique a originalidade da fonte da mensagem para evitar a injeção de construções de sites não confiáveis;
usar métodos seguros para autenticar a fonte original da mensagem;
verifique as mensagens em busca de conteúdo e comandos potencialmente perigosos antes de executá-los;
usar mecanismos de criptografia para proteger a confidencialidade das mensagens durante a transmissão.
E se você é um pentester e não um web designer, não se esqueça de verificar essas técnicas quando vir postagens na web.