El 4 de agosto de 2021, Popsicle Finance sufrió una enorme pérdida financiera (más de $20M) a causa de un ataque [1].
Tras un análisis manual, confirmamos que se trata de un ataque de reclamación doble (double-claiming), es decir, una laguna en su sistema de recompensas permite al atacante reclamar recompensas repetidamente.
A continuación, utilizaremos una transacción del ataque para ilustrar el proceso del ataque y la causa raíz de la vulnerabilidad.
Contexto
Popsicle Finance es una plataforma de optimización de rendimiento que soporta múltiples bóvedas para diferentes cadenas (p. ej., Ethereum y BSC).
Específicamente, un usuario primero invoca la función deposit para proporcionar liquidez y obtiene el token LP de Popsicle (PLP, por sus siglas). Después de eso, Popsicle Finance gestionará la liquidez (interactuando con plataformas como Uniswap) para generar ganancias al usuario.
El usuario puede invocar la función withdraw para recuperar la liquidez de Popsicle Finance, que calculará el monto en función de los tokens PLP.
La recompensa de incentivo proviene de la liquidez, que se irá acumulando con el paso del tiempo.
El usuario puede invocar la función collectFees para reclamar las recompensas, que es la clave de este ataque.
Análisis de la Vulnerabilidad
En la función collectFees, se calculan token0Reward y token1Reward (recompensas del par de tokens LP correspondiente) para el usuario. Toda la lógica de cálculo es sencilla. Sin embargo, la función utiliza un modificador llamado updateVault, que se usa para actualizar las recompensas en consecuencia.


En resumen, updateVault realizará lo siguiente:
- primero invoca la función
_earnFeespara obtener la tarifa acumulada del pool; - luego invoca la función
_tokenPerSharepara actualizartoken0PerShareStoredytoken1PerShareStored, que representan las cantidades de token0 y token1 en el pool por cada participación; - finalmente invoca las funciones
_fee0Earnedy_fee1Earnedpara actualizar las recompensas del usuario (es decir,token0Rewardsytoken1Rewardsrespectivamente).

Las funciones _fee0Earned y _fee1Earned comparten la misma lógica, es decir, implementan la siguiente fórmula (usando token0 como ejemplo):
user.token0Rewards += PLP.balanceOf(account) * (fee0PerShare - user.token0PerSharePaid) / 1e18
Nótese que el cálculo es incremental, lo que significa que incluso si el usuario NO posee el token PLP, la recompensa calculada mantiene el valor almacenado en token0Rewards.
Por lo tanto, podemos concluir las siguientes dos observaciones:
- las recompensas del usuario se almacenan en
token0Rewardsytoken1Rewards, las cuales no están asociadas a ningún token PLP; - la función
collectFeessolo depende del estado detoken0Rewardsytoken1Rewards, lo que significa que las recompensas pueden retirarse sin poseer el token PLP.
En un escenario del mundo real, esto significa que un usuario deposita dinero en un banco y el banco le entrega un certificado de depósito. Desafortunadamente, este certificado no es ni anti-falsificación, ni está asociado al usuario. En tal caso, es posible hacer duplicados y distribuirlos a otros para obtener ganancias del banco.
Flujo del Ataque
En resumen, el atacante siguió los siguientes pasos para lanzar el ataque:
- creó tres contratos. Uno de ellos se utilizó para lanzar el ataque, mientras que los otros dos se usaron para invocar la función
collectFeesy obtener las recompensas; - utilizó el Flash Loan, es decir, tomó prestada una gran cantidad de liquidez de AAVE;
- lanzó el ciclo Depositar-Retirar-ReclamarTarifas (Deposit-Withdraw-CollectFees) para realizar el ataque (hay 8 ciclos en total, y se retiró mucha liquidez de múltiples bóvedas de Popsicle Finance);
- devolvió el Flash Loan a AAVE y blanqueó las ganancias a través de Tornado.Cash.

Específicamente, el ciclo Depositar-Retirar-ReclamarTarifas consta de varios pasos, que pueden etiquetarse fácilmente y resumirse claramente usando nuestra herramienta en línea [2]:

Análisis de Ganancias
En total, el atacante obtuvo $20M de Popsicle Finance, incluyendo 2,56K WETH, 96,2 WBTC, 160K DAI, 5,39M USDC, 4,98M USDT y 10,5K UNI. Tras esa explotación, el atacante primero intercambió todos los demás tokens por ETH a través de Uniswap y WETH, y luego realizó el blanqueo de dinero utilizando Tornado.Cash.
Créditos
Yufeng Hu, Ziling Lin, Junjie Fei, Lei Wu, Yajin Zhou @BlockSec
(En orden alfabético por apellido)
Medium: https://blocksecteam.medium.com/
Twitter: https://twitter.com/BlockSecTeam
Contacto: [email protected]
Referencia
[1] https://twitter.com/defiprime/status/1422708265423556611



