La reciente vulnerabilidad del grupo Curve es diferente de los incidentes de piratería de criptomonedas que hemos visto antes, porque esta vez el problema no está directamente relacionado con la vulnerabilidad del contrato inteligente en sí, sino con el compilador subyacente del lenguaje de programación utilizado por Vyper.
Vyper es un lenguaje de programación inteligente orientado a contratos que adopta un estilo similar a Python y está diseñado específicamente para interactuar con la máquina virtual Ethereum (EVM).
El impacto de esta vulnerabilidad es muy grave y cada día aparecen en las noticias enormes cantidades de pérdidas. La situación parece estar bajo control, pero no antes de que los piratas informáticos robaran más de 70 millones de dólares. Según la evaluación posterior de LlamaRisk, algunos grupos de proyectos DeFi también fueron atacados por piratas informáticos, incluido el grupo pETH/ETH de PEGD que perdió 11 millones de dólares, el grupo msETH/ETH de Metronome que perdió 3,4 millones de dólares y el grupo alETH/ETH de Alchemix que perdió 11 dólares. millones 22,6 millones de dólares estadounidenses, mientras que el grupo Curve DAO perdió aproximadamente 24,7 millones de dólares estadounidenses.
Esta vulnerabilidad se denomina error de reentrada y aparece principalmente en algunas versiones del lenguaje de programación Vyper, específicamente v0.2.15, v0.2.16 y v0.3.0. Por lo tanto, los proyectos que utilizan estas versiones específicas de Vyper pueden convertirse en blanco de ataques.
¿Qué es la reentrada?
Para comprender por qué ocurrió esta vulnerabilidad, primero comprendamos qué es la reentrada y cómo funciona.
La llamada reentrada significa que una función puede interrumpirse durante la ejecución y volver a llamarse de forma segura antes de que se complete la llamada anterior. Este mecanismo se utiliza a menudo en aplicaciones como la recursividad y el manejo de interrupciones de hardware.
Para que una función sea reentrante, debe cumplir algunas condiciones:
En primer lugar, no puede utilizar datos globales y estáticos. Esta es una convención, lo que significa que cuando se ejecuta la función, no se basa en datos globales o variables estáticas. Porque si una función se interrumpe durante la ejecución y luego se vuelve a llamar, los datos globales y estáticos pueden dañarse o perderse información.
En segundo lugar, no puede modificar su propio código. No importa cuándo se interrumpa una función, debería poder continuar su ejecución de la misma manera. Si una función modifica su propio código durante la ejecución, puede ocurrir un error cuando se vuelva a llamar.
Finalmente, no puede llamar a otras funciones que no sean reentrantes. Es decir, dentro de una función reentrante, no debes llamar a otras funciones que puedan no ser reentrantes. Porque si hace esto, puede provocar resultados impredecibles, provocando que el programa se bloquee o se produzcan errores.
Es demasiado difícil de entender, por lo que no entraremos en detalles.
¿Cómo fue explotado?
Expliquemos cómo los ataques de reentrada llevaron al robo de fondos y a la pérdida de 70 millones de dólares en el ataque Curve.
En primer lugar, sabemos que un ataque de reentrada se refiere a un método en el que un contrato malicioso llama repetidamente a una función en un contrato inteligente. En el ataque Curve, esta función se utiliza para retirar la liquidez del usuario en el grupo. Esta función tiene el defecto de que no realiza suficientes comprobaciones antes de actualizar el importe.
Veamos el proceso específico del ataque:
Digamos que un contrato inteligente vulnerable tiene 10 ether.
El atacante llama a la función de depósito y deposita 1 Ethereum.
Luego, el atacante llama a la función de retiro y se prepara para retirar 1 Ethereum. En esta función, comprueba si el atacante tiene 1 Ethereum en su cuenta.
Sin embargo, esta función no actualiza el saldo del contrato antes de transferir 1 Ether a la cuenta del atacante. Esto significa que el contrato todavía cree que hay 10 éteres dentro.
El atacante vuelve a llamar a la función de retiro (reingreso) y se prepara para retirar 1 Ethereum nuevamente.
Debido a que el contrato todavía cree que hay 10 éteres adentro, transferirá 1 éter al atacante nuevamente.
Este proceso se repite hasta que no haya más liquidez en el contrato.
De esta manera, el atacante puede llamar repetidamente a la función de retiro y transferir casi toda la liquidez del contrato a su propia cuenta, provocando el robo de los fondos del contrato.
Esta vulnerabilidad es muy beneficiosa para el atacante porque el monto del contrato no se actualiza en tiempo real, lo que le permite repetir el ataque hasta que no haya fondos disponibles para retirar en el contrato. #Crv