WeCTF
Este CTF, iniciado este ano, é focado apenas em desafios web e, embora eu tenha chegado no finalzinho, deu tempo de um pouquinho de diversão e aprendizado.
FasterShop
Como eu não fiz todos os prints a tempo (explico no final), vou fazer algumas adaptações aqui. Foi um pouco mais fácil do que os posts anteriores, mas foi interessante.
Neste desafio, você tem uma loja onde pode comprar uma lista de produtos. Cada produto que você compra entra na lista de histórico abaixo e você pode vender novamente, recuperando o saldo.
Ao vender todos os produtos, você fica com um saldo máximo de 20 “bucks”, mas o objetivo é comprar a “Fancy Flag”, que custa 21!
Tentando o básico
As primeiras tentativas de ataque foram nas urls /buy (comprar) e /sell (vender).
- /buy/1: Compra produto com ID 1
- /sell/45: vende o produto com histórico de compra ID 45
Exemplos de chamadas:
curl 'http://faster.w-va.cf:1002/sell/45' \
-X 'POST' \
-H 'Connection: keep-alive' \
-H 'Content-Length: 0' \
-H 'Pragma: no-cache' \
-H 'Cache-Control: no-cache' \
-H 'Upgrade-Insecure-Requests: 1' \
-H 'Origin: http://faster.w-va.cf:1002' \
-H 'Content-Type: application/x-www-form-urlencoded' \
-H 'User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36' \
-H 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9' \
-H 'Referer: http://faster.w-va.cf:1002/' \
-H 'Accept-Language: en-US,en;q=0.9,pt-BR;q=0.8,pt;q=0.7,es;q=0.6,fr;q=0.5' \
-H 'Cookie: token=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' \
--compressed \
--insecurecurl 'http://faster.w-va.cf:1002/buy/1' \
-X 'POST' \
-H 'Connection: keep-alive' \
-H 'Content-Length: 0' \
-H 'Pragma: no-cache' \
-H 'Cache-Control: no-cache' \
-H 'Upgrade-Insecure-Requests: 1' \
-H 'Origin: http://faster.w-va.cf:1002' \
-H 'Content-Type: application/x-www-form-urlencoded' \
-H 'User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36' \
-H 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9' \
-H 'Referer: http://faster.w-va.cf:1002/' \
-H 'Accept-Language: en-US,en;q=0.9,pt-BR;q=0.8,pt;q=0.7,es;q=0.6,fr;q=0.5' \
-H 'Cookie: token=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' \
--compressed \
--insecure
Após várias tentativas de acesso indevido brincando com os IDs, o máximo que consegui foi causar alguns erros 500 incluindo aspas duplas, mas sem SQL Injections gerados com sucesso. Segui a vida.
Fuçando IDs
Também tentei vender o produto dos outros, pra ver se funcionava e aumentava o meu saldo. Fiz um loop pra tentar encontrar históricos diferentes pra vender.
Encontro alguns IDs dos outros, mas recebo a mensagem “Not the purchase you made bro…”. O maldito validava isso :)
Já não tinha muita esperança de resolver dessa forma, porque o desafio anterior foi mais ou menos assim (mas não custava nada tentar).
Tudo ao mesmo tempo
Provavelmente, o algoritmo de compra era algo como isso (pseudo-código simplificado):
// "db" é o objeto de banco de dados
saldo_atual = session['saldo_atual']
id_usuario = session['id_usuario']
id_produto = request['id']db.start_transaction()valor_produto = db.get("select valor from produtos where prod_id = :prod_id", id_produto)
db.execute("insert into historico_transacao (id_usuario) values (:id_usuario, :id_produto)", id_usuario, id_produto)
db.commit()session['saldo_atual'] = saldo_atual - valor_produto
Se esta chamada for feita várias vezes em paralelo, existe a probabilidade de que a primeira linha, que recebe o saldo atual, pegue um saldo desatualizado de alguma transação que ainda não terminou. Quando a subtração for feita, o saldo continua mais alto do que deveria. Race Condition!
Não custa nada testar. Neste caso, opto por um script NodeJS, que já trabalha nativamente de forma assíncrona, pra chamar vários requests praticamente ao mesmo tempo:
De repente, me vejo com mais produtos do que deveria!
Após a venda, tenho 25 “bucks” de saldo! O suficiente pra comprar a Fancy Flag. Race Condition!
Final Infeliz
Pouco após aumentar o meu saldo, o site parou de funcionar. Até aguardei um tempinho pra ver se colocariam no ar de novo, mas não tinha percebido que o maldito CTF havia terminado!!
Achei que iria até 19h, mas finalizava 14h. Ainda daria tempo de pegar a flag e submeter no site, mas eu parei pra tentar aumentar ainda mais o saldo e perdi o timing! Por isso não peguei a flag e nem o restante dos prints.
Fica pra próxima. O próximo evento do WeCTF está previsto pra dezembro :)
O site do CTF já saiu do ar, mas descobri agorinha que o código-fonte dos desafios foi disponibilizado not github (link nas referências) e você pode checar o verdadeiro código de comprar produto (/buy), que foi atacado neste desafio.
Referências
- CTF no CTFTime: https://ctftime.org/event/1044
- Meu perfil no CTF Time: https://ctftime.org/team/122851
- Código-Fonte deste desafio: https://github.com/shouc/wectf-2020/tree/master/faster_shop
- Outro write-up (em inglês) do mesmo desafio, resolvido de forma diferente: https://medium.com/@ekkarin.t/faster-shop-wectf-2020-write-ups-9f8979cbbe45