BSC Flash Loan Attack: PancakeBunny

En mayo de 2021, fuimos testigos de varios ataques contra los productos BSC DeFi. En particular, se aprovechó una laguna relacionada con la acuñación de recompensas en el agregador de rendimiento, PancakeBunny, para generar ~ 7M de tokens BUNNY de la nada, lo que generó una enorme pérdida financiera de $ 45M. Después del sangriento hack, tres proyectos bifurcados (AutoShark, Merlin Labs y PancakeHunny) fueron atacados con técnicas similares. El equipo de seguridad de la cadena de bloques de Amber Group, dirigido por el Dr. Chiachih Wu, profundiza en la laguna jurídica y da una descripción paso a paso del exploit reproduciendo el ataque contra PancakeBunny.

Hidden Attack Surface: balanceOf ()

Many la gente cree que la composibilidad es crucial para el éxito de DeFi. Los contratos de tokens (por ejemplo, ERC20) juegan un papel esencial en la capa inferior de los legos DeFi. Sin embargo, los desarrolladores pueden pasar por alto algunas condiciones incontrolables e impredecibles al integrar ERC20 en sus proyectos DeFi. Por ejemplo, no puede predecir cuándo ni cuántos tokens recibirá cuando recupere el saldo actual de tokens. Esta incertidumbre crea una superficie de ataque oculta.

En muchos casos, los contratos inteligentes hacen referencia a los saldos de los ERC20 en su lógica empresarial. Por ejemplo, cuando un usuario deposita algunos tokens XYZ en el contrato inteligente, se invoca XYZ.balanceOf () para comprobar cuánto dinero se recibe. Si está familiarizado con el código base de Uniswap, probablemente sepa que el contrato UniswapV2Pair tiene muchas llamadas balanceOf ().

En el fragmento de código, UniswapV2Pair.mint () usa los saldos actuales (saldo0, saldo1) y los datos contables (monto0, monto1) para derivar los montos depositados por el usuario (monto0, monto1). Sin embargo, si un mal actor transfiere algunos activos (token1 o token2) justo antes de la llamada mint (), la víctima proporcionaría más liquidez de la esperada, es decir, se acuñan más tokens LP. Si las recompensas se calculan en función de la cantidad de tokens LP, el mal actor puede beneficiarse cuando las recompensas superan los gastos.

El UniswapV2Pair.burn () tiene un riesgo similar. La persona que llama a la función mint () podría ponerse en peligro sin una comprensión completa de los riesgos involucrados. Esto es lo que sucedió en el caso de PancakeBunny.

En el fragmento de código anterior, la línea 140 recupera el saldo del token LP a través de balanceOf () y lo almacena en liquidez. En las líneas 144-145, la parte del total de tokens LP que posee UniswapV2Pair (es decir, liquidez de _totalSupply) se usa para derivar (monto0, monto1) con los saldos actuales (saldo0, saldo1) de los dos activos (es decir, token0 y token1). Posteriormente, (monto0, monto1) de los dos activos se transfieren a la dirección en las líneas 148-149.

Aquí, un mal actor podría manipular (saldo0, saldo1) y la liquidez enviando algún token0 + token1 o el LP token en el contrato UniswapV2Pair justo antes de que se invoque la función mint () para que la persona que llama obtenga más token0 + token1. Lo guiaremos a través del código fuente de PancakeBunny y le mostraremos cómo el mal actor puede beneficiarse de hacer esto.

Análisis de lagunas: BunnyMinterV2

En el código fuente de PancakeBunny , la función BunnyMinterV2.mintForV2 () se encarga de acuñar tokens BUNNY como recompensa. Específicamente, la cantidad a acuñar (es decir, mintBunny) se deriva de los parámetros de entrada, _withdrawalFees y _performanceFee. El cálculo está relacionado con tres funciones: _zapAssetsToBunnyBNB () (línea 213), priceCalculator.valueOfAsset () (línea 219) y amountBunnyToMint () (línea 221). Dado que el mal actor puede acuñar una gran cantidad de BUNNY, el problema radica en una de las tres funciones mencionadas anteriormente.

Empecemos por la función _zapAssetsToBunnyBNB (). Cuando el activo transferido es un Cake-LP (línea 267), se usa una cierta cantidad de tokens LP para eliminar liquidez y tomar (amountToken0, amountToken1) de (token0, token1) del fondo de liquidez (línea 278). Con la ayuda del contrato zapBSC, esos activos se intercambian por tokens LP BUNNY-BNB (líneas 287–288). A continuación, se devuelve una cantidad correspondiente de tokens LP BUNNY-BNB a la persona que llama (línea 298). Aquí tenemos un problema. ¿Coincide la cantidad con la cantidad de tokens LP que supone que se quemarán?

En la implementación de PancakeV2Router.removeLiquidity (), liquidez de los tokens LP (La cantidad en zapAssetsToBunnyBNB ()) se enviaría al contrato PancakePair (línea 500) y se invocaría PancakePair.burn (). Si el saldo actual de tokens LP de PancakePair es mayor que 0, la cantidad real a quemar sería mayor que la cantidad, lo que indirectamente aumenta la cantidad de BUNNY a acuñar.

Otro problema en _zapAssetsToBunnyBNB () es el zapBSC .zapInToken () llamada. La lógica detrás de esto es intercambiar los dos activos recolectados por removeLiquidity () en tokens BUNNY-BNB LP. Dado que zapBSC intercambia activos a través de PancakeSwap, el mal actor podría usar préstamos flash para manipular la cantidad de BUNNY-BNB intercambiados.

De vuelta a BunnyMinterV2.mintForV2 (), el bunnyBNBAmount devuelto por zapAssetsToBunnyBNB () se pasaría a priceCalculator.valueOfAsset ) para citar el valor basado en BNB (es decir, vauleInBNB), similar a un mecanismo de oráculo.

Sin embargo, priceCalculator.valueOfAsset () hace referencia a la cantidad de BNB y BUNNY (reserva0, reserva1) en el BUNNY_BNB PancakePair como fuente de precios, que permite al mal actor utilizar préstamos flash para manipular la cantidad de fichas BUNNY acuñadas.

La función amountBunnyToMint () es un cálculo matemático simple. La contribución de entrada se multiplica por cinco (bunnyPerProfitBNB = 5e18), que en sí mismo no tiene superficie de ataque, pero la amplificación magnifica la manipulación mencionada anteriormente.

Prepárate para el combate

Dado que el ataque es desencadenado por getReward (), necesitamos para calificar para las recompensas primero.

Como se muestra en la captura de pantalla de Etherscan anterior, el pirata informático PancakeBunny invocó la función init () del contrato de explotación para intercambiar 1 WBNB por tokens WBNB-USDT-LP y depositarlos () en el contrato VaultFlipToFlip, de modo que obtendría algunas recompensas invocando getReward ().

Como se muestra arriba, usando la función Exp.prepare () reproducimos la llamada vaultFlipToFlip.deposit () (línea 62). También usamos el contrato ZapBSC para simplificar la obtención de tokens LP (líneas 54-57). Sin embargo, uno no puede obtener recompensas hasta que el guardián de PancakeBunny activa la siguiente llamada de cosecha (). Por esta razón, el pirata informático PancakeBunny no desencadenó el ataque hasta la primera transacción harvest () después de la transacción init ().

En nuestra simulación, ningún cuidador puede desencadenar la cosecha (). Por lo tanto, aprovechamos la función de eth-brownie para hacerse pasar por el poseedor e iniciar manualmente la transacción de cosecha () (línea 25).

Préstamos flash recursivos

Para aprovechar los fondos, el explotador de PancakeBunny utilizó ocho grupos de fondos diferentes, incluidos siete Contratos PancakePair y ForTube Bank. En este caso, el equipo de seguridad de la cadena de bloques de Amber Group solo utilizó la función de intercambio flash de los siguientes siete contratos PancakePair para prestar 2,3 millones de WBNB:

dirección [7] pares = [ address(0x0eD7e52944161450477ee417DE9Cd3a859b14fD0), address(0x58F876857a02D6762E0101bb5C46A8c1ED44Dc16), address(0x74E4716E431f45807DCF19f284c7aA99F18a4fbc), address(0x61EB789d75A95CAa3fF50ed7E47b96c132fEc082), address(0x9adc6Fb78CEFA07E13E9294F150C1E8C1Dd566c0), address(0xF3Bc6FC080ffCC30d93dF48BFA2aA14b869554bb), address(0xDd5bAd8f8b360d76d12FdA230F8BAF42fe0022CF) ];

Para simplificar las llamadas flash-swap, empaquetamos dos parámetros en el cuarto argumento de entrada de las llamadas PancakePair.swap () (línea 72 o línea 74): nivel y activo. La variable de nivel indica en qué nivel de llamada swap () estamos; la variable del activo es 0 o 1, lo que significa que necesitamos pedir prestado token0 o token1.

Usando la función de devolución de llamada pancakeCall (), llamamos de forma recursiva a PancakePair.swap () con nivel + 1 hasta llegar al séptimo nivel. En el nivel superior, invocamos shellcode () para realizar la acción real en la línea 98. Cuando devuelve shellcode (), la variable de activo devuelve el activo prestado en cada nivel correspondiente (líneas 102-104).

Tire del gatillo [19659003]

La función shellcode () invocada por el séptimo nivel de pancakeCall () es el código de explotación real. Primero, mantenemos el saldo actual de WBNB en wbnbAmount (línea 108), intercambiamos 15,000 WBNB en tokens WBNB-USDT-LP (línea 112) y los enviamos al contrato que acuñó esos tokens LP (es decir, el contrato PancakePair) en línea 113. Este paso tiene como objetivo manipular la llamada removeLiquidity () dentro de la función _zapAssetsToBunnyBNB () como se analizó anteriormente, lo que nos permite recibir más WBNB + USDT de lo esperado.

El segundo paso es manipular el precio USDT al que hace referencia _zapAssetsToBunnyBNB () para intercambiar USDT por WBNB. Dado que _zapAssetsToBunnyBNB () usa WBNB-USDT PancakePair para intercambiar USDT por WBNB, podríamos intercambiar el resto del WBNB prestado en flash por USDT en PancakeSwap. Hacerlo haría que WBNB fuera extremadamente barato, y _zapAssetsToBunnyBNB () recibiría una cantidad desproporcionadamente grande de WBNB cuando se cambiara de USDT. Tenga en cuenta que la manipulación de precios aquí se produce en el grupo de Pancake V1, no en PancakePair de Pancake V2 como en el paso anterior.

El paso final es la llamada getReward (). La simple llamada de contrato podría acuñar 6,9 millones de tokens BUNNY (línea 125). Los tokens BUNNY podrían cambiarse por WBNB en PancakeSwap para devolver el préstamo flash.

En nuestra simulación, el mal actor paga 1 WBNB y se marcha con 104k WBNB + 3.8M USDT (equivalente a ~ $ 45M).

Acerca de Amber Group

Amber Group es un proveedor líder mundial de servicios financieros criptográficos operando en todo el mundo y las 24 horas del día con presencia en Hong Kong, Taipei, Seúl y Vancouver. Fundado en 2017, Amber Group atiende a más de 500 clientes institucionales y ha negociado acumulativamente más de $ 500 mil millones en más de 100 intercambios electrónicos, con más de $ 1,5 mil millones en activos bajo administración. En 2021, Amber Group recaudó $ 100 millones en fondos de la Serie B y se convirtió en el último unicornio de FinTech valorado en más de $ 1 mil millones. Para obtener más información, visite: www.ambergroup.io.