Injection

Utilizando do sistema e analisando como posso interagir com ele, comecei interagindo com uma funcionalidade de busca.

Ao carregar a página principal, o client faz uma request para /rest/products/search?q=, que traz um JSON de todos os produtos:

Fazendo uma busca por Apple, nenhuma outra comunicação é feita para o backend, parece que o filtro é feito totalmente pelo client:

Podemos reparar também que nossa busca é refletida no frontend, temos controle sobre elementos HTML renderizados no browser, além de reparar uma parâmetro ?q na consulta para o backend, aqui nos surge algumas possibilidades:

  • Testar o front-end para XSS, visto que controlamos o texto HTML renderizado na tela;

  • Testar o back-end para um possível SQL Injection, pois uma funcionalidade que retorna uma lista de itens muito provavelmente está lendo os itens de uma base de dados SQL, e o parâmetro q é um input que controlamos que possivelmente é utilizado na query, nos dando controle sobre a query SQL;

SQL Injection inicial

Ainda temos o endoint /rest/products/search?q= para testar, e começar a manipular esse input ?q. Fazendo uma busca por "Apple", me retorna apenas os itens com Apple no nome no JSON:

Provavelmente, esse input que controlamos é usado em uma query sql, podemos manipular então manipular essa query. Fazendo essa request GET /rest/products/search?q=xxdd%27%20or%20apple, temos uma exception na tela que nos mostra a query utilizada, nos ajudando a craftar uma payload melhor para esse ataque:

Com isso, podemos ver que a query utilizada é:

e que input é utilizado diretamente na URL, então de fatos temos controle sobre a query.

usando order by consegui identificar que existem 9 colunas na tabela, e com union all select: aaaaa%' )) UNION ALL SELECT 1, 2, 3, 4, 5, 6, 7, 8, 9 -- -

Irei colocar a sequencia de payloads utilizadas e os outputs obtidos:

aaaaa' )) UNION ALL SELECT 1, sqlite_version() as version, 3, 4, 5, 6, 7, 8, 9 -- -

version: 3.44.2

aaaaa%' )) UNION ALL SELECT sql , 2, 3, 4, 5, 6, 7, 8, 9 from sqlite_master -- -

input: aaaaa%' )) UNION ALL SELECT group_concat(tbl_name) , 2, 3, 4, 5, 6, 7, 8, 9 FROM sqlite_master WHERE type='table' and tbl_name NOT like 'sqlite_%' -- -

output:

input: aaaaa%' )) UNION ALL SELECT sql , 2, 3, 4, 5, 6, 7, 8, 9 FROM sqlite_master WHERE type!='meta' AND sql NOT NULL AND name ='Users' -- -

output:

input: aaaaa%' )) UNION ALL SELECT id , username, email, password, role, deluxeToken, totpSecret, 8, 9 FROM Users -- - output:

Logando como administrador | Crack Hash

Com isso, um dos desafios e logar como administrador do sistema, podemos tentar quebrar a Hash da senha dele (usando Hashcat ou similar) e tentar logar, vamos tentar primeiro no Crackstation:

Então podemos logar com [email protected]:admin124

Logando como Jim

Logamos como admin e concluímos mais um desafio. Agora, o próximo é logar como jim, e o processo é o mesmo, quebramos a hash de sua senha, e ai temos as credenciais [email protected]:ncc-1701

Logando como Bender | Auth bypass

O próximo relacionado ao login, é logar como bender, porém dessa vez, a hash dele não é crackeavel, vamos então analisar a interação entre o client e o server para executar o login de uma conta

Podemos ver que é bem simples, mandamos as credenciais de login e senha, e ele nos devolve um token JWT e alguns meta-dados

Testando esses inputs, facilmente conseguimos triggar outra exception que nos revela mais um sql injection

Podemos criar então outra payload de SQL Injection para bypassar esse mecanismo de login, colocando um breaking point nesse endpoint, assim que meu client enviar a request, a proxy irá interceptar para que eu altere antes de enviar para o server, com isso posso injetar a payload `[email protected]' -- - ` para controlar a query fazendo com que, assim que ela pegue os dados do email inserido, o resto seja comentado e não tenha verificação pela senha, e assim o server irá concluir a autenticação com os dados do Bender:

Executando a request:

Estou agora logado como Bender, e mais um desafio foi concluído.

Ephemeral Accountant

Esse desafio nos pede para logar com uma conta com email "[email protected]", mas sem registrar ela.

Minha primeira ideia foi aproveitar o controle do banco de dados pelo SQL Injection e fazer com que a query seja algo como "select * from Users where email = '[email protected]' -- - para não fazer nenhuma verificação, porém com isso, nenhum dado seria retornado e daria erro na aplicação por não ter dados, então imediatamente fui pensar em outra ideia.

Usando union select, posso fazer uma tabela virtual que preencha os campos da tabela User com dados controlados por mim, e como já temos o schema da tabela, podemos saber quais campos preencher, então a payload fica sendo essa:

Temos o seguinte erro: SQLITE_CONSTRAINT: FOREIGN KEY constraint failed

Parece que em alguma parte do processo o Id é usado para recuperar a tabela Basket do usuário, então preciso de um ID válido para o join ser feito no corretamente com os dados dessa conta, vou usar o ID de uma conta existente, como 8:

e deu certo:

Com isso, posso fazer um breakingpoint na hora do login, e passar essa request que cria uma conta virtualmente na base de dados:

Com isso logamos com a conta inexistente e fechando os desafios da parte de autenticação, podemos começar a explorar melhor agora a área logada do sistema e ver as novas interações que temos dessa forma.

Christmas special

Um desafio da plataforma é comprar um produto que não está mais disponível, para isso, precisamos primeiro tentar achar o produto que não está disponível, e temos um endpoint que lista produtos que é vulnerável a SQL Injection, podemos fazer essa chamada: GET /rest/products/search?q=%25%27%20or%201%3D1%29%29%20--%20- HTTP/1.1, onde a payload na query string `q` é %' or 1=1)) -- -, essa payload fecha o a consulta "like" e lista todos os produtos se 1 for igual 1.

Na resposta, podemos ver que temos um registro com deletedAt diferente de null, então é o produto deletado que devemos comprar:

Agora, vou ver como é a interação do client com o server para adicionar um produto ao carrinho:

Basicamente enviamos o ID do produto, o ID do carrinho e a quantidade. Como controlamos o produto e o carrinho, e sabendo que o ID do produto do desafio é 10, podemos apenas tentar adicionar ao carrinho o produto de ID 10 (e futuramente, tentar adicionar um produto em um carrinho que não seja nosso, mas fica para outro post). Usando minha proxy, fiz uma request trocando o ID do produto para 10, e funcionou, ele adicionou o produto deletado em meu carrinho:

Agora, vou excluir o Apple Juice que adicionei apenas para para entender como funciona o processo de adicionar item ao carrinho, e finalizar a compra com do Christmas Super-Surprise-Box (2014 Edition) Precisamos adicionar o endereço:

Escolher um tipo de entrega:

Adicionar um cartão e continuar a compra:

E finalmente, efetuar a compra:

E com isso, foi concluído o desafio de comprar um produto já deletado, através de SQL Injection (e da falta de validação do back-end, que deveria verificar se o produto está disponível antes de adicionar ao carrinho, mas não o fez).

NoSQL Manipulation

Quando estamos autenticado, podemos deixar uma avaliação em um produto, usando uma proxy, podemos ver a interação entre o client e o server:

E na hora de ler as avaliações, podemos ver também com qual endpoint o client interage, e nele podemos ver que se trata de um schema de mongodb (caso não saiba o motivo, o campo __ id normalmente pertence ao mongodb):

Analisando o endpoint usado para criar o comentário, podemos tirar algumas hipóteses pela request:

Podemos notar que controlamos o ID do produto, a mensagem e o autor. Se tratando de um pentest black-box, não sabemos como está estruturado o código no servidor, mas podemos imaginar um pseudo-código parecido com isso:

ao invés de buscar o produto pelo ID, podemos tentar usar um query operator no nosso input para fazer o mongodb criar esse comentário em todos os produtos (https://www.mongodb.com/docs/manual/reference/operator/query/).

Tentando injetar query operator na URL:

Porém não funcionou, tentei a mesma ideia de outras formas e também não obtive resultado, então é provável que o servidor apenas crie um novo registro no mongodb, com um campo product_id que vem da URL, independe se o produto existe ou não, e então não buscar por um produto existente primeiro.

Função de editar review: Continuando a análise, ainda na parte de avaliação temos uma funcionalidade para editar uma avaliação, e com a proxy podemos ver como o client interage com o endpoint de edição:

Com essa request, podemos notar que um dos inputs de o ID do documento editado:

Depois de buscar no google por exemplos de update usando mongodb, podemos imaginar um pseudo-código parecido com isso para editar uma review:

Onde dessa vez, tempos mais certeza de que o servidor primeiro busca por uma review específica (e controlamos o input que está sendo usado para essa busca com parâmetro ID) para depois editar seu conteúdo. A ideia então é tentar usar outro query operator já que temos maiores certezas de um filtro sendo utilizado, então, ao invés do servidor buscar por uma review especifica, ele vai fazer uma query que pode trazer diversos reviews. Ataque:

Com essa request, o servidor no momento de buscar um produto pelo ID, vai fazer uma query "$ne": null, que buscar por todos os produtos onde o ID é diferente de null (ne = not equal. https://www.mongodb.com/docs/manual/reference/operator/query/ne/ ). Então irá nos trazer todas as reviews, e depois editar com o nosso comentário. Resultado:

Podemos ver mais abaixo na response os diversos itens com o valor que editamos:

Editamos todas as reviews de produtos salvas no banco e fechamos mais um desafio.

Com esse, concluímos todos os desafios do módulo de injection (disponíveis em docker):

Last updated