En la parte anterior de esta serie sobre la seguridad de blockchain vimos los riesgos asociados con la implementación autónoma y la ejecución de contratos inteligentes en un blockchain público. También presentamos algunos ejemplos de alto perfil de ataques a contratos inteligentes que han causado la pérdida de grandes sumas de dinero y han cambiado la forma en que vemos las interacciones comerciales en la cadena de bloques.
En este episodio, repasaremos algunos problemas conocidos y vulnerabilidades.
Fuga de clave privada
El uso de claves privadas no seguras es realmente un error del usuario, en lugar de una vulnerabilidad. Sin embargo, lo mencionamos, como sucede sorprendentemente a menudo, y ciertos actores se han especializado en robar fondos de direcciones inseguras.
Lo que generalmente ocurre es que las direcciones de desarrollo (como las utilizadas por las herramientas de prueba, como Ganache/TestPRC) se utilizan en producción. Estas son direcciones generadas a partir de claves privadas conocidas públicamente. Algunos usuarios incluso han importado estas claves sin saberlo en el software de cartera virtual, al usar las palabras de semilla originales usadas en la generación de la clave privada.
Los atacantes están monitorizando estas direcciones y cualquier cantidad transferida a dicha dirección en la red principal de Ethereum tiende a desaparecer inmediatamente (dentro de 2 bloques).
Esta muy lucrativa actividad de “barrido” ha sido investigada en este interesante estudio, que encontró que una cuenta barredora había logrado acumular fondos por valor de $ 23 millones.
Código reentrante y condiciones de carrera
Las vulnerabilidades de reentrada consisten en un comportamiento inesperado, si se llama a una función varias veces antes de que se complete la ejecución.
Veamos la siguiente función, que se puede utilizar para retirar el saldo total del llamador de un contrato:
La invocación de call.value() hace que se ejecute código externo del contrato. Se puede suponer que el llamador es un software de cartera virtual de criptomoneda, pero también puede ser otro contrato.
Si el llamador es otro contrato, esto significa que se ejecuta la función fallback del contrato. El propósito de una función fallback es recibir fondos.
Un contrato deshonesto puede implementar una función fallback que llama a payOut() de nuevo recursivamente, antes de que el saldo se establezca a 0, obteniendo así más fondos de los disponibles.
La solución a esto es usar las funciones alternativas send() o transfer(). Esto evita las llamadas recursivas, al reenviar solamente el gas suficiente para una contabilidad básica y cualquier intento de llamar a payOut() nuevamente fallaría. Alternativamente (o adicionalmente), el orden de operación puede invertirse, es decir, establecer el saldo a 0 antes de realizar la transferencia de dinero.
El ataque DAO mencionado en la parte I usó una variación de esta vulnerabilidad.
Under-/Overflow
Los saldos suelen estar representados por números enteros sin signo, normalmente números de 256 bits. Cuando los enteros sin signo sufren un underflow or overflow, su valor cambia drásticamente. Veamos el siguiente ejemplo de un underflow más común (números acortados para la legibilidad):
0x0003
– 0x0004
———–
0xFFFF
Es fácil ver el problema aquí. Restando 1 más que el saldo disponible causa un underflow. El saldo resultante ahora es un gran número.
También tengamos en cuenta que la división aritmética de enteros es problemático, debido a errores de redondeo.
La solución es verificar siempre si hay underflow or overflow en el código. Hay bibliotecas seguras para ayudar con esto, como SafeMath by OpenZeppelin.
Suposiciones de orden de transacción
Las transacciones ingresan a un conjunto de transacciones no confirmadas y pueden ser incluidas en bloques por mineros en cualquier orden, dependiendo de los criterios de selección de transacciones del minero, que probablemente sea algún algoritmo destinado a obtener las máximas ganancias de las tarifas de transacción, pero podría ser cualquier cosa.
Así que, el orden de las transacciones que se incluyen puede ser completamente diferente del orden en que se generan. Por lo tanto, el código de un contrato no puede hacer suposiciones sobre el orden de transacción.
Además de los resultados inesperados en la ejecución del contrato, existe un posible vector de ataque, ya que las transacciones son visibles y su ejecución puede predecirse. Esto tal vez sea un problema en el trading, donde retrasar una transacción puede ser utilizada para ventaja personal por un minero deshonesto. De hecho, el simple hecho de estar al tanto de ciertas transacciones antes de que se ejecuten puede ser una ventaja para cualquier persona, no solo para los mineros. Las transacciones pueden “ser adelantadas” al pagar un precio de gas más alto, lo que incentiva a los mineros a incluirlas rápidamente.
Dependencias de Timestamp
En el blockchains, los timestamp son generados por los mineros. Por lo tanto, ningún contrato debe basarse en el timestamp del bloque para operaciones críticas, como usarlo como semilla para la generación de números aleatorios. Consensys da una regla de 12 minutos en sus directrices, que establece que es seguro usar block.timestamp, si vuestro código dependiente del tiempo puede manejar una variación de tiempo de 12 minutos.
Conclusión
Hasta ahora, hemos visto varios casos de alto perfil de ataques a contratos inteligentes basados en blockchain. También discutimos algunas vulnerabilidades comunes que fueron explotadas en el pasado. En una próxima publicación, cubriremos algunos ataques más complejos que dependen de la forma en que funcionan el blockchain y las operaciones específicas.