Em 4 de agosto de 2021, a Popsicle Finance sofreu uma enorme perda financeira (mais de $20M) devido a um ataque [1].
Após análise manual, confirmamos que se trata de um ataque de double-claiming (dupla reivindicação), ou seja, uma brecha em seu sistema de recompensas permite que o atacante reivindique recompensas repetidamente.
A seguir, usaremos uma transação de ataque para ilustrar o processo do ataque e a causa raiz da vulnerabilidade.
Contexto
A Popsicle Finance é uma plataforma de otimização de rendimento que suporta múltiplos vaults para diferentes redes (por exemplo, Ethereum e BSC).
Especificamente, um usuário primeiro invoca a função deposit para fornecer liquidez e recebe o token LP da Popsicle (PLP, abreviado). Após isso, a Popsicle Finance gerenciará a liquidez (interagindo com plataformas como a Uniswap) em benefício do usuário para gerar lucros.
O usuário pode invocar a função withdraw para recuperar a liquidez da Popsicle Finance, que calculará o valor com base nos tokens PLP.
A recompensa de incentivo provém da liquidez, que se acumula ao longo do tempo.
O usuário pode invocar a função collectFees para reivindicar as recompensas, que é o ponto central deste ataque.
Análise de Vulnerabilidade
Na função collectFees, token0Reward e token1Reward (recompensas do par de tokens LP correspondente) são calculados para o usuário. Toda a lógica de cálculo é direta. No entanto, a função utiliza um modificador chamado updateVault, que é usado para atualizar as recompensas de acordo.


Em resumo, o updateVault irá:
- primeiro invocar a função
_earnFeespara obter a taxa acumulada do pool; - em seguida invocar a função
_tokenPerSharepara atualizartoken0PerShareStoredetoken1PerShareStored, que representam as quantidades de token0 e token1 no pool para cada cota; - por fim invocar as funções
_fee0Earnede_fee1Earnedpara atualizar as recompensas do usuário (ou seja,token0Rewardsetoken1Rewardsrespectivamente).

As funções _fee0Earned e _fee1Earned compartilham a mesma lógica, ou seja, implementam a seguinte fórmula (usando token0 como exemplo):
user.token0Rewards += PLP.balanceOf(account) * (fee0PerShare - user.token0PerSharePaid) / 1e18
Observe que o cálculo é incremental, o que significa que mesmo que o usuário NÃO possua token PLP, a recompensa calculada permanece com o valor armazenado em token0Rewards.
Portanto, podemos concluir as seguintes duas observações:
- as recompensas do usuário são armazenadas em
token0Rewardsetoken1Rewards, que não estão associadas a nenhum token PLP; - a função
collectFeesdepende apenas do estado detoken0Rewardsetoken1Rewards, o que significa que as recompensas podem ser retiradas sem a posse do token PLP.
No cenário do mundo real, isso significa que um usuário deposita dinheiro em um banco e o banco lhe entrega um certificado de depósito. Infelizmente, esse certificado não possui mecanismos antifalsificação nem está associado ao usuário. Nesse caso, é possível fazer cópias e distribuí-las a outros para obter lucros do banco.
Fluxo do Ataque
De forma resumida, o atacante seguiu os seguintes passos para executar o ataque:
- criou três contratos. Um deles foi usado para lançar o ataque, enquanto os outros dois foram usados para invocar a função
collectFeese buscar as recompensas; - utilizou o Flash Loan, ou seja, tomou emprestado uma grande quantidade de liquidez da AAVE;
- executou o ciclo Deposit-Withdraw-CollectFees para realizar o ataque (há 8 ciclos no total, e grande quantidade de liquidez foi retirada de múltiplos vaults da Popsicle Finance);
- devolveu o Flash Loan à AAVE e lavou os lucros por meio do Tornado.Cash.

Especificamente, o ciclo Deposit-Withdraw-CollectFees consiste em várias etapas, que podem ser facilmente identificadas e claramente resumidas utilizando nossa ferramenta online [2]:

Análise de Lucro
No total, o atacante obteve $20M da Popsicle Finance, incluindo 2,56K WETH, 96,2 WBTC, 160K DAI, 5,39M USDC, 4,98M USDT e 10,5K UNI. Após a exploração, o atacante primeiro trocou todos os outros tokens por ETH por meio da Uniswap e WETH, e em seguida realizou a lavagem de dinheiro utilizando o Tornado.Cash.
Créditos
Yufeng Hu, Ziling Lin, Junjie Fei, Lei Wu, Yajin Zhou @BlockSec
(Em ordem alfabética pelo sobrenome)
Medium: https://blocksecteam.medium.com/
Twitter: https://twitter.com/BlockSecTeam
Contato: [email protected]
Referências
[1] https://twitter.com/defiprime/status/1422708265423556611



