Really Awesome CTF — Quarentena!

RACTF 2020

Esta foi a primeira edição do Really Awesome CTF, com diversos desafios Web, em sua maioria com aplicações Python/Flask. Os desafios continuam online em https://2020.ractf.co.uk/

Quarantine é um grupo de desafios em torno da mesma aplicação. Relativamente simples, mas foi divertido.

Original em inglês (para o ctftime): https://github.com/Neptunians/ractf/tree/master/2020/quarantine

First Access

Neste desafio, temos que fazer um primeiro acesso à aplicação, sem ter a senha.

A aplicação está vulnerável a SQL Injection no Login, que você percebe logo com a tentativa mais básica (estou assumindo que você tenha conhecimentos básicos do assunto aqui).

Mas neste caso ele traz a mensagem de erro:

Isso significa que, estranhamente, ele está buscando todos os usuários com esta mesma senha. OK, temos que dar um jeito de trazer apenas um usuário.

Assumindo que a query da aplicação seja: select * from users where password = 'pwd'

Vamos simular desta forma (senha é o tercho em negrito):

Primeira flag!

Finding Server Information

Neste desafio, “Encontrando Informações do Servidor”, você precisa tentar obter o código-fonte do servidor. Ele já dá a dica: O nome do arquivo é app.py.

Os links para assistir os vídeos têm o formato abaixo:

Isso já dá a dica de que temos um possível Local File Inclusion, já que ele deve estar lendo o arquivo passado como parâmetro para o endpoint /watch e enviando para o cliente.

Então, basta tentar:

http://95.216.233.106:62020/watch/app.py

E sucesso!

ractf{qu3ry5tr1ng_m4n1pul4ti0n}

Ele não traz realmente o código-fonte, mas traz a Flag.

Hidden Information

Desafio mais básico, pede pra encontrarmos alguma informação escondida no site. Um teste padrão pra esse tipo de cenário é acessar o /robots.txt, que é utilizado pelas ferramentas de busca, pra tentar ver o que a aplicação quer, veja bem, esconder :)

Claramente é a pista que precisamos:

Bem facinho, só pra pontuar mesmo.

Getting Admin

Somos desafiados a obter um acesso de administrador (e possivelmente chamar o /admin, que é proibido de primeira).

Pra isso, usei uma série de ataques de SQL Injection, pra entender o modelo de dados. Usei uma estratégia similar à do primeiro SQL Injection, mas tive que quebrar em fases.

Ainda vou fazer um post específico sobre esse tipo de técnica, que merece maior descrição, mas aqui vou ser mais breve.

Infelizmente não salvei todas as informações, então vou usar apenas as informações que tenho aqui.

Pegando todos os nomes de usuários

Usei substrings pra checar o valor de cada letra do username (confirmando com Login OK ou não). Uma espécie de brute-force, mas com espaço de busca reduzido.

Usei o script abaixo pra fazer esse brute-force: https://github.com/Neptunians/ractf/blob/master/2020/quarantine/brute_usernames.py

— Usuário encontrado: ‘Dave’

Lembro que encontrei outros, mas não salvei os nomes :(

Pegando a senha do usuário

Mesma técnica, mas com um script no campo da senha: https://github.com/Neptunians/ractf/blob/master/2020/quarantine/brute_passwords.py

Encontrei a senha:

Não foi suficiente pra virar Admin. Aparentemente, nenhum usuário era admin.

Mais busca no banco de dados

Testando funcionalidades, descobri que se tratava de um banco de dados sqlite, que não tem uma information_schema pra fazer a busca. Ele tem um dicionário de dados, mas é uma merda pegar a estrutura (via SQL Injection) porque fica tudo em uma string, com o comando CREATE TABLE. Identifiquei coluna por coluna, fazendo brute-force pelo nome da coluna (de novo).

Com o SQL abaixo, confirmei o número de colunas pelo número de vírgulas no comando de criação e depois fiz o brute-force no nome da coluna, com a mesma técnica anterior. Deu um pouco mais de trabalho, porque eu tive que descobrir onde começava o nome de cada coluna:

Usei o código abaixo:

https://github.com/Neptunians/ractf/blob/master/2020/quarantine/brute_column_names.py

ID, username, password e PRIVILEGE!

Escalando privilégios

Mais algumas fuçadas e descobri que o meu usuário tinha privilégio = 1, mas eu precisava de privilégio = 2 (ou zero, mas descobri que era 2).

Usando o próprio SQL Injection, é possível forçar o retorno da coluna privilégio, com o valor que eu quiser (“ninguém me manda” 😆).

Payload Final:

E depois, indo no /admin, owned!

A solução “Real”

Depois de olhar o texto da Flag, percebo que a solução pretendida era quebrar o token JWT. Descobri a técnica lendo outro write-up, então não vou escrever de novo. Link mais abaixo.

De qualquer forma, foi legal descobrir que resolvi de uma maneira diferente do esperado pelos donos do desafio!

Se tiver um tempinho, posto um pouco da outra parte dos desafios Web, que também tem partes interessantes.

Referências

Hacker tiozão do pavê de final de semana

Hacker tiozão do pavê de final de semana