Las 5 vulnerabilidades más habituales de los Smart Contracts

(Nota del editor: teniendo en cuenta que la mayor parte de la información sobre blockchain se encuentra en inglés y las traducciones al castellano de los conceptos son relativamente jóvenes, y para facilitar la comprensión del contenido, hemos evitado traducir algunos términos específicos de blockchain. Pedimos disculpas a los puristas, empezando por uno mismo)

El artículo de hoy está escrito por Stefan Beyer, CEO @ Cryptonics, Consultor de Blockchain y Auditor de Contratos Inteligentes. Disfruten.


Es difícil conseguir hacer bien un smart contract. Sus tres propiedades principales, la capacidad de representar valor, la transparencia y la inmutabilidad son esenciales para su funcionamiento. Sin embargo, estas propiedades también los convierten en un riesgo para la seguridad y un objetivo de gran interés para los ciberdelincuentes. Incluso sin ataques deliberados, hay muchos ejemplos de fondos que se bloquean y de empresas que pierden dinero debido a los fallos y vulnerabilidades de los smart contracts.

En los últimos dos años, en Cryptonics hemos auditado los smart contracts de más de 40 proyectos. Estos incluyen diferentes tipos de tokenización de activos, pólizas de seguro, plataformas financieras descentralizadas, fondos de inversión e incluso juegos de ordenador.

Como resultado, hemos observado ciertas tendencias en las vulnerabilidades que solemos encontrar, y algunas son más comunes que otras. En este artículo, describiremos los cinco problemas más comunes que hemos venido detectando en nuestras actividades de auditoría.

1. Errores aritméticos con números enteros

Arithmetic

Un error muy común es realizar mal la aritmética con números enteros. Los smart contracts generalmente expresan los valores con números enteros debido a la falta de soporte para coma flotante.

El uso de números enteros para representar valor, algo bastante común en los programas financieros, requiere reducir dicho valor a unidades más pequeñas para permitir una precisión suficiente. El ejemplo más sencillo es expresar el valor en centavos en lugar de dólares, ya que de otro modo sería imposible representar 0,5 dólares. De hecho, la precisión de los smart contracts suele ir mucho más allá, con soporte para 18 decimales en muchos tokens.

Un problema del que los desarrolladores son muy conscientes hoy en día es la posibilidad de desbordamiento de los números enteros. Al igual que los contadores de kilometraje en los coches, los números enteros expresados en los ordenadores tienen un valor máximo, y una vez que se alcanza ese valor, simplemente vuelven al principio y comienzan con el valor mínimo. De manera similar, si se resta 4 a 3 en un entero sin signo, se producirá un desbordamiento, lo que dará como resultado un número muy grande. Los desarrolladores son generalmente conscientes de este potencial problema y lo evitan utilizando una biblioteca matemática segura, como la excelente implementación de Open Zeppelin.

Sin embargo, lo que muchos desarrolladores parecen no apreciar es que la aritmética con números enteros puede conducir a una falta de precisión cuando se hace mal. En particular, el orden de las operaciones es importante, y un caso clásico es el cálculo de porcentajes. Por ejemplo, para calcular el 25 por ciento, normalmente dividimos por 100 y multiplicamos por 25. Digamos que queremos calcular el 25 por ciento de 80 usando sólo números enteros. Expresando esto como 80 / 100 * 25 dará como resultado 0, debido a los errores de redondeo. El error trivial aquí es haber realizado la división antes que la multiplicación.

Lo anterior es, por supuesto, un ejemplo muy simple, pero nos encontramos con errores aritméticos de números enteros con una frecuencia alarmante.

2. Vulnerabilidades del límite de block gas

El límite de block gas es la forma que tiene Ethereum de asegurarse de que los bloques no crezcan demasiado. Simplemente significa que los bloques están limitados en la cantidad de gas que las transacciones contenidas en dichos bloques pueden consumir. En pocas palabras, si una transacción consume demasiado gas nunca cabrá en un bloque y, por lo tanto, nunca se ejecutará.

Esto puede conducir a una vulnerabilidad que encontramos con bastante frecuencia: si los datos se almacenan en matrices de tamaño variable y luego se accede a ellos mediante bucles sobre estas matrices, la transacción puede simplemente quedarse sin gas y ser revertida. Esto ocurre cuando el número de elementos en el conjunto crece, lo que normalmente se produce en producción en lugar de durante las pruebas.

La razón es que los conjuntos de datos de prueba suelen ser más pequeños que los de producción, lo que convierte esta vulnerabilidad en algo muy peligroso, ya que los contratos con este problema suelen pasar las pruebas unitarias y funcionan bien con un número reducido de usuarios. Sin embargo, fracasan justo cuando el proyecto cobra impulso y la cantidad de datos aumenta. En estos casos, no es infrecuente que acaben con fondos irrecuperables si se utilizan bucles para realizar pagos.

3. Falta de parámetros o controles de precondición

Otro de los errores de programación más sencillos que encontramos es, o bien no validar los argumentos de una función, o bien olvidar las comprobaciones esenciales para que una operación sea válida. A menudo, esto incluye que los parámetros de dirección no se verifiquen para la dirección cero o, por ejemplo, que no se verifique que un usuario tiene suficiente saldo de tokens para ejecutar una determinada operación. Otro buen ejemplo es el control de acceso, en el que sólo un determinado tipo de usuario debería estar autorizado a llamar a una función, pero esta comprobación nunca se realiza.

Estos errores suelen ser el resultado de un proceso de diseño descuidado. Es una buena idea tener una especificación escrita de todas las funciones, en la que se indiquen los parámetros, las precondiciones y las operaciones que se han de realizar. Ceñirse a patrones de diseño, como Comprobación-Efecto-Interacción, también puede ayudar a prevenir este tipo de vulnerabilidad.

4. Frontrunning

El frontrunning potencial es probablemente el tema más difícil de prevenir en nuestra lista de vulnerabilidades comunes. Este puede definirse como la apropiación de una transacción no confirmada, y es consecuencia de la propiedad de transparencia del blockchain.

Todas las transacciones no confirmadas son visibles en el mempool antes de ser incluidas en un bloque por un minero, y aquellos interesados pueden simplemente monitorizar las transacciones por su contenido y “adelantarlas” pagando tarifas de transacción más altas. Esto puede automatizarse fácilmente y se ha convertido en algo muy común en las aplicaciones financieras descentralizadas.

No es fácil protegerse contra el frontrunning, y los problemas que detectamos a menudo requieren algún tipo de refactorización o rediseño significativo para poder ser solucionados.

5. Bugs de lógica simples

Algunos de los temas anteriores son más específicos de los smart contracts, mientras que otros son inherentes a la programación. Sin embargo, el problema más común que detectamos es la presencia de errores simples en la lógica del smart contract. Estos errores pueden ser el resultado de una simple errata, una mala comprensión de la especificación o un error de programación mayor, pero en todos los casos tienden a tener graves implicaciones en la seguridad y funcionalidad del smart contract.

Pero lo que todos tienen en común es el hecho de que solo pueden ser detectados si el auditor entiende completamente la base de código y tiene una visión de la funcionalidad prevista del proyecto y la especificación del contrato. Este tipo de cuestiones es la razón por la que las auditorías de los smart contracts llevan tiempo, no son baratas y requieren auditores muy experimentados.

Comentarios finales

En los últimos años las cosas han cambiado en la programación de smart contracts. Incontables casos de gran relevancia que han resultado en pérdidas económicas han hecho que los proyectos entiendan que deben tomarse la seguridad en serio.

En general, encontramos que los desarrolladores son conscientes de las vulnerabilidades comunes, y es habitual que empleen herramientas como el análisis de código estático o la ejecución simbólica para escanear automáticamente el código. Sin embargo, no nos hemos encontrado con ninguna auditoría en la que no hayamos descubierto al menos un problema menor en los primeros pasos, y hemos hallado vulnerabilidades críticas en el código de desarrolladores realmente experimentados.

Por nuestra experiencia, la naturaleza de los problemas más habituales hace imprescindible la realización de una auditoría manual completa de código, lo que siempre debe ser realizado por un equipo independiente que aporte una mirada nueva.

Para consultas sobre auditorías full-stack y de smart contracts, puede contactarnos en Cryptonics.


(Descargo de responsabilidad: Cryptonics Consulting es una empresa spin-off de S2 Grupo, especializada en blockchain y ciberseguridad).