Back to Blog

El Análisis del Incidente de Seguridad de Popsicle Finance

Code Auditing
August 4, 2021
4 min read

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:

  1. primero invoca la función _earnFees para obtener la tarifa acumulada del pool;
  2. luego invoca la función _tokenPerShare para actualizar token0PerShareStored y token1PerShareStored, que representan las cantidades de token0 y token1 en el pool por cada participación;
  3. finalmente invoca las funciones _fee0Earned y _fee1Earned para actualizar las recompensas del usuario (es decir, token0Rewards y token1Rewards respectivamente).

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:

  1. las recompensas del usuario se almacenan en token0Rewards y token1Rewards, las cuales no están asociadas a ningún token PLP;
  2. la función collectFees solo depende del estado de token0Rewards y token1Rewards, 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:

  1. creó tres contratos. Uno de ellos se utilizó para lanzar el ataque, mientras que los otros dos se usaron para invocar la función collectFees y obtener las recompensas;
  2. utilizó el Flash Loan, es decir, tomó prestada una gran cantidad de liquidez de AAVE;
  3. 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);
  4. 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)

https://www.blocksecteam.com

Medium: https://blocksecteam.medium.com/

Twitter: https://twitter.com/BlockSecTeam

Contacto: [email protected]

Referencia

[1] https://twitter.com/defiprime/status/1422708265423556611

[2] https://tx.blocksecteam.com/

Best Security Auditor for Web3

Validate design, code, and business logic before launch. Aligned with the highest industry security standards.

BlockSec Audit