El hackeo de la red Poly Network explicado

polynetwork hack

A menos que estés viviendo bajo una roca, habrás leído que el martes pasado el mayor «hack de criptomonedas» de la historia tuvo como objetivo la plataforma financiera descentralizada de cadena cruzada (DeFi) Poly Network, y permitió que un atacante no revelado robara el equivalente a la friolera de 610 millones de USDT en tokens de criptomonedas.

La situación está en rápido desarrollo, y el análisis preliminar del ataque comenzó a circular en las últimas horas. En este post, trato de explicar lo sucedido en base a la información disponible.

Lo más básico

En esta entrada del blog asumo que se está familiarizado con el funcionamiento básico de un libro de contabilidad descentralizado sin permisos, y en particular con los conceptos de blockchain y consenso, y las aplicaciones de tokens de criptomonedas relacionadas (por ejemplo, Bitcoin), pero de todos modos recapitularé los conceptos más básicos.

En pocas palabras, podemos decir que un «token de criptomoneda» es una cantidad virtual de «algo» que es propiedad de alguien. Se reclama la posesión de un token publicando una «dirección de cartera» (básicamente, una clave de verificación pública) y «haciendo algo» que haga que el protocolo de consenso acepte vincular el token a esa dirección. La prueba de la posesión de ese token y la posibilidad de transferirlo a algún otro monedero la proporciona una firma digital, generada por la clave de firma secreta del monedero. Emitir una «transacción» significa simplemente incrustar algunos metadatos y una firma válida relacionada en la cadena de bloques, que autoriza la transferencia de fondos del monedero A al monedero B. En un libro de contabilidad totalmente distribuido, el mecanismo de consenso hace que sea en el «mejor interés» de los participantes aceptar una firma válida dentro de la cadena de bloques.

Contratos inteligentes

El siguiente paso es realizar lo siguiente: Como se ha explicado anteriormente, la emisión de una transacción de criptomoneda es básicamente un proceso en el que varios participantes «acuerdan» cambiar el estado de un libro de contabilidad virtual de la misma manera. Y un libro de contabilidad no es otra cosa que una base de datos replicada con entradas de la forma «dirección: cantidad». Sin embargo, no hay nada especial en esta forma particular de entradas: nada de lo descrito hasta ahora prohíbe aplicar el mismo proceso para cambiar el estado de un tipo diferente de base de datos replicada. ¿Y si consideramos como nuestra «base de datos» la memoria de una máquina virtual?

Aquí es donde entra en juego la idea de los «contratos inteligentes», adoptada inicialmente en la aplicación Ethereum. En pocas palabras, un contrato inteligente es una pieza de software que está diseñada para ejecutarse en una máquina virtual distribuida. En los ordenadores tradicionales, el software se «ejecuta» cambiando, paso a paso, el estado de la memoria interna del ordenador según un conjunto de reglas predefinidas. Un contrato inteligente funciona de forma similar: el estado interno inicial de la máquina se incorpora al libro de contabilidad de la cadena de bloques mediante la emisión de una transacción (desde la cartera de un creador) que contiene una versión compilada («comprimida» y legible por la máquina) del código. El código se ejecuta cambiando el estado de la blockchain, y el protocolo de consenso hace que siempre sea en el mejor interés de los participantes ejecutar el código (es decir, aplicar las reglas que rigen el cambio de estado al siguiente bloque) de forma correcta. Al igual que en las transacciones de criptomonedas, la ejecución de un contrato inteligente está autorizada por una firma digital emitida por el «propietario» del contrato inteligente (normalmente, pero no siempre, su creador), pero debe ser validada por el consenso.

Esto trae a colación el importante concepto de propiedad e interacción entre contratos. ¿Cómo interactúan los contratos inteligentes? En un entorno de código típico, puede haber diferentes subrutinas que llamen a diferentes funciones, a menudo recogidas en bibliotecas, y es necesario gestionar la entrada y la salida de éstas. Los contratos inteligentes funcionan de la misma manera, pero la diferencia es que, dada la naturaleza distribuida de la computación, hay que establecer salvaguardias (y hacerlas cumplir por el mecanismo de consenso) para asegurarse de que, por ejemplo, si los contratos A y B llaman ambos a una función F, F devolverá a cada uno de ellos la salida correcta, o que si se crea y ejecuta un contrato inteligente, yo no puedo cambiar arbitrariamente su estado, ya que no soy el propietario.

En la práctica, los contratos inteligentes (o sus plataformas de ejecución) suelen incorporar salvaguardas del tipo «Si el que llama a este programa no está autorizado por una determinada clave, entonces aborta». La autorización no siempre viene en forma de firma directa: un contrato A puede ser emitido especificando otro contrato B como su «propietario», y se pueden dar diferentes permisos a diferentes conjuntos de usuarios.

Redes de cadenas cruzadas

Ya hay demasiadas cadenas de bloques como para seguirlas todas. Un problema común que surge en el escenario de las criptomonedas es cuando se posee un determinado tipo de token (por ejemplo, Bitcoin) y se quiere convertir en otro que utiliza un tipo diferente de tecnología de cadena de bloques o consenso (por ejemplo, Ripple). Esto suele implicar el intercambio de tokens en una bolsa, con las correspondientes comisiones. En el panorama de los contratos inteligentes, te encuentras con problemas similares: ¿qué pasa si estás ejecutando un contrato inteligente en Ethereum pero quieres que interactúe con otro, digamos, en Cardano?

Ahora, si lo piensas, puedes ver un intercambio de criptodivisas como una especie de contrato: yo te doy X Bitcoin y tú me devuelves Y Ripple. De manera análoga, los servicios centralizados pueden actuar como «intérpretes de cadenas cruzadas», por ejemplo, permitiendo que un programa A que se ejecuta en Ethereum interactúe con otro programa B que se ejecuta en Cardano de la siguiente manera:

Leer la consulta (salida intermedia) de A desde la blockchain de Ethereum
Introducir ese valor en B emitiendo una transacción dentro de la blockchain de Cardano
Esperar a que B termine su ejecución, y luego leer su salida de la capa de Cardano
Devolver ese valor a A emitiendo una transacción de Ethereum de entrada.
Los servicios de este tipo se denominan a veces «oráculos» y, al igual que los intercambios, requieren una tarifa para operar. También, como los intercambios, requieren que el usuario confíe plenamente en una sola entidad. Sin embargo, la siguiente observación es: ya que estos nodos están ejecutando básicamente un contrato o script, ¿por qué no descentralizamos éste al igual que hacemos con los contratos inteligentes?

Entra el concepto de «red de cadena cruzada», que es básicamente una capa de abstracción adicional sobre el concepto de máquina virtual distribuida. Una red de cadena cruzada permite que dos cadenas de bloques diferentes se «comuniquen entre sí». O, para ser más precisos, permite a los usuarios de una determinada blockchain realizar operaciones en otra blockchain de forma distribuida y autónoma. Sin embargo, precisamente porque operan a través de diferentes blockchains, estas redes suelen requerir su propia «blockchain virtual» para ejecutar los contratos inteligentes que rigen las reglas de comunicación dentro de las redes subyacentes.

Poly Network es una red de cadena cruzada que se asienta sobre diferentes blockchains, incluyendo Bitcoin, Ethereum y Elrond. Para simplificar en exceso su arquitectura, Poly puede describirse con los siguientes componentes

Un monedero maestro para cada una de las redes subyacentes de la capa 1 (por ejemplo, uno para Bitcoin, otro para Ethereum, …) cada uno de los cuales contiene una determinada cantidad de fondos.
Un conjunto de contratos inteligentes que interpretan y ejecutan las instrucciones de los usuarios (por ejemplo, «por favor, intercambia esta cantidad de Bitcoin que te estoy enviando en tokens de Ether») llamando a funciones en los monederos anteriores relacionados.
Una capa de blockchain (la red Poly) donde se ejecutan los contratos inteligentes anteriores.
Es común que las redes de cadena cruzada, incluyendo Poly, almacenen en cualquier momento gran cantidad de liquidez en sus monederos subyacentes, porque hay muchos usuarios realizando operaciones de cadena cruzada al mismo tiempo. Por lo tanto, es crucial asegurar adecuadamente los privilegiados contratos inteligentes de cadena cruzada que administran estos monederos. Lamentablemente, esto es exactamente lo que no ocurrió aquí.

El hackeo

El 10 de agosto, Poly Network informó de que un atacante no revelado hackeó un contrato inteligente de la red, transfiriendo el equivalente a unos 610 millones de dólares (principalmente en Ether, Binance Coin y USDC) y trasladándolos a direcciones de monederos externos.

Según la empresa de ciberseguridad SlowMist y el investigador de seguridad Kelvin Fichter, el hackeo fue posible gracias a una mala gestión de los derechos de acceso entre dos importantes contratos inteligentes de Poly. El primero es EthCrossChainManager y el segundo es EthCrossChainData.

Hablemos primero de EthCrossChainData. Este es un contrato muy privilegiado que se supone que no puede ser invocado por nadie dentro de la red, excepto por sus propietarios. La razón es que este contrato es responsable de establecer y gestionar una lista de claves públicas de «nodos autentificadores» (Keepers) que gestionan los monederos en las cadenas de liquidez subyacentes. En otras palabras, EthCrossChainData puede decidir quién tiene el privilegio de mover la gran cantidad de fondos contenidos en la cartera Binance de Poly, la cartera Ethereum, etc. Si un atacante pudiera llamar a la función correcta (putCurEpochConPubKeyBytes) dentro de EthCrossChainData, no habría ni siquiera necesidad de atacar la clave secreta de un Keeper: el atacante podría simplemente establecer su propia clave pública para reemplazar la de un Keeper, y entonces tendría el derecho de ejecutar una transacción de alto volumen dentro de la red Poly para exfiltrar una gran cantidad de fondos a otras billeteras. Claramente no es algo que quieras que ocurra.

Ahora, sobre EthCrossChainManager. Este es otro contrato de alto privilegio que tiene el derecho de lanzar mensajes desde otra cadena a la cadena Poly. Lo que significa: cualquiera puede llamar a un evento de cadena cruzada emitiendo una transacción en la cadena de origen que invoca la función verifyHeaderAndExecuteTx dentro de EthCrossChainManager, y especificando un contrato Poly de destino para ejecutar. Sin embargo, debido a su uso particular, EthCrossChainManager no llamaría a cualquier función dentro del contrato de destino, sino sólo a la que tiene un «Solidity function ID» muy específico. Es decir, sólo llamaría a una función cuyo ID de 32 bits se calcule de esta manera:

En otras palabras, el ID de la función llamada se calcula como el truncamiento de 32 bits de un hash Keccak de 256 bits de la cadena _method y un sufijo.

El atacante explotó dos problemas aquí.

El primero es que resulta que EthCrossChainManager es propietario de EthCrossChainData, ¡y por tanto puede ejecutar funciones privilegiadas dentro de éste! El segundo es que el campo _method en el fragmento de código anterior es en realidad definido por el usuario, y por lo tanto puede ser establecido a voluntad. En particular, es posible para el atacante forzar un campo _method que se convierte en un valor de 32 bits que es exactamente el ID de putCurEpochConPubKeyBytes.

Este es el ataque en detalle:

El atacante calculó el ID de 32 bits de putCurEpochConPubKeyBytes:
ethers.utils.id (‘putCurEpochConPubKeyBytes(bytes)’).slice(0, 10)’0x41973cd9′
El atacante forzó una cadena que, si se establece como _method en el fragmento de código anterior, da el mismo valor de 32 bits. En este caso el atacante utilizó la cadena «f1121318093»:
ethers.utils.id (‘f1121318093(bytes,bytes,uint64)’).slice(0, 10)’0x41973cd9′
El atacante llamó a una transacción de cadena cruzada desde la red Ethereum a la red Poly activando EthCrossChainManager y apuntando a EthCrossChainData, y pasando la cadena f1121318093 como _method, y la clave pública de su propia cartera Ethereum como parámetro.
Esto provocó que EthCrossChainManager llamara a la función putCurEpochConPubKeyBytes dentro de EthCrossChainData, y exigiera que la clave pública del atacante se registrara como la de un Keeper. EthCrossChainData ejecutó dicha orden, ya que EthCrossChainManager es su propietario.
Una vez ejecutada la transacción y concedido al atacante el estatus de Keeper para la blockchain de Ethereum, el atacante procedió a utilizar la correspondiente clave secreta en su poder para canalizar tokens fuera del monedero de Ethereum de Poly hacia su propio monedero.
El atacante repitió lo anterior para otros monederos de liquidez de Poly: Binance, Neo, Tether, etc.
Un valor total de más de 610 millones de USD fue robado de esta manera. Eso es mucho.

Consecuencias

Las cosas se mueven rápidamente y es difícil seguir en tiempo real la evolución de la situación.

Tras el hackeo, el siguiente paso razonable habría sido que el atacante transfiriera los fondos robados al fondo de liquidez de una bolsa anónima descentralizada como Curve. Por esta razón, Poly emitió inmediatamente una solicitud para que los mineros de criptomonedas y los intercambios hicieran una «lista negra» de los fondos robados, haciéndolos de facto inaccesibles para el atacante. En teoría, esto es posible, pero es engorroso para los intercambios por muchas razones, y no está claro si todas las plataformas de intercambio respondieron a la petición.

Sin embargo, los administradores de la stablecoin Tether (que tienen un mayor control sobre su blockchain en comparación con otras aplicaciones DeFi) consiguieron congelar los fondos Tether-USDC robados a tiempo, sólo 9 bloques antes de que el atacante intentara blanquearlos en el pool de liquidez Curve. Un usuario anónimo alertó al hacker emitiendo una transacción de Ethereum a la dirección del hacker con el mensaje de advertencia de congelación en los metadatos, y el hacker recompensó a este usuario con parte del Ethereum robado. Después de eso, la dirección del hacker se vio inundada de transacciones de cientos de personas pidiendo dinero.

Poly Network pidió al hacker que devolviera los fondos. La empresa de seguridad Slowmist publicó las conclusiones sobre el presunto hacker, afirmando que la identidad del hacker había quedado expuesta y que el grupo tenía acceso al correo electrónico y a la dirección IP del hacker. Según Slowmist, el hacker pudo aprovechar un intercambio de criptomonedas relativamente desconocido en Asia y afirmaron tener mucha información sobre el atacante.

Sea cierto o no, el hacker comenzó a devolver los fondos a Poly el miércoles. Para el 11 de agosto a las 15:00 UTC se había devuelto casi la mitad de los tokens, y el hacker afirma estar dispuesto a devolver más a cambio de la descongelación de los tokens de Tether. Un segundo mensaje incrustado en una transacción dice: «YA ES UNA LEYENDA GANAR TANTA FORTUNA. SERÁ UNA LEYENDA ETERNA SALVAR EL MUNDO. HE TOMADO LA DECISIÓN, NO MÁS DAO».

Mientras se desarrolla esta historia, no está de más recordar que «blockchain» no es sinónimo de «seguridad». Es muy importante auditar la seguridad de sus aplicaciones, incluidos los contratos inteligentes.

Leave a Reply

Your email address will not be published. Required fields are makes.