cartas sobre una mesa

BadReveal – Explotación de NFT

Una nueva familia de vulnerabilidades

Un nuevo exploit encontrado por los investigadores de Sayfer y @rugpullfinder El equipo permite a los atacantes saber cuál es el NFT más raro antes de la revelación del proyecto. Esto permite que un atacante tenga una ventaja desigual entre los inversores para comprar la pieza más rara y cara. En algunos proyectos, los más raros pueden tener un precio 50 veces mayor que el precio de la pieza estándar.

La vulnerabilidad ocurre debido a malas prácticas de codificación. Encontramos la vulnerabilidad en docenas de proyectos. Posiblemente existan miles que aún no hemos probado manualmente.

No hay forma de probarlo automáticamente, por lo que si desea saber si un proyecto en el que desea invertir o en el que ya ha invertido es vulnerable a BadReveal, etiquétenos en Twitter. @SayferSeguridad con #BadReveal y nuestros investigadores lo comprobarán.

Afectando a Múltiples Proyectos Activos

Para comprender la magnitud del problema, analizamos más de 100 proyectos diferentes y encontramos que 12 eran vulnerables al ataque. Estos son algunos de los proyectos vulnerables: 

CatBloxPUMAcápsula (Tiempo de explotación: 2 días 18 horas, Capitalización de mercado: 628.19 ETH)

Héroes Everai: Dúo (Tiempo de explotación: 4 días 2 horas, Capitalización de mercado: 1,905.37 ETH)

Degen Toonz (Tiempo de explotación: 3 días 13 horas, Capitalización de mercado: 6,221.6 ETH)

Más detalles como tiempos de transacción específicos están en el apéndice.

Detalles Técnicos de la Vulnerabilidad

Antecedentes 

Como son los NFT Tokens ERC721, cada token es único con sus propios metadatos. Por lo general, en un proyecto de NFT con un paso de revelación, los tokens se acuñan a ciegas y, una vez que se ha acuñado todo, se revelan los NFT y sus metadatos se hacen públicos. Los NFT con metadatos raros podrían valer 50 veces más que otros NFT en los proyectos. El más raro de todos ellos se llama la "Leyenda".

Pero, ¿qué sucede si un atacante de alguna manera logra obtener los metadatos antes de que se revelen y usa estos datos para comprar la leyenda? El atacante obtiene un beneficio injusto y obtiene más ganancias que todos los demás compradores al explotar estos datos para comprar la leyenda.

¿Qué es un token URI?

El token URI de un NFT es un identificador único de cómo "se ve" el token. Un URI puede ser una dirección HTTP, un hash IPFS o incluso un blob JSON que se mantiene en la cadena. Apunta a un archivo JSON que contiene los metadatos del token.

Al crear su proyecto NFT, debe asegurarse de que los URI del token no sean accesibles o adivinables antes de que se revele el NFT. De lo contrario, esto podría permitir que los atacantes obtengan el control de los NFT más valiosos del proyecto. De hecho, podrían usar estos URI para verificar algunas propiedades raras en los metadatos antes de que alguien pueda verlos.

La vulnerabilidad 

Descubrimos que muchos proyectos establecen el URI del token en una transacción y luego lo revelan en una transacción diferente. En el tiempo entre esas dos transacciones, que a veces pueden ser horas, un atacante puede escanear todos los NFT en el proyecto y encontrar cuál es el más raro, y luego comprarlo en función de su tokenID.

Ejemplo de explotación

Aquí tenemos un ejemplo de una función que devuelve el token URI si el reveal flag es verdadero (es falso por defecto) o un URI que apunta a una imagen oculta común para cada token si el indicador es falso.

function tokenURI(uint256 tokenId) public view virtual override
       returns (string memory)
   {
       require(_exists(tokenId), "Nonexistent token");
       if (reveal == false) {
           return hideURI;
       }
       string memory URI = baseURI;
       uint256 randomId = ((randomNumber + tokenId) % supplyMax) + 1;
       return
           bytes(URI).length > 0
               ? string(abi.encodePacked(URI, randomId.toString(), ".json"))
               : "";
   }

El URI se crea con cuatro elementos: baseURI, tokenId, supplyMaxy randomNumber. Estos cuatro elementos son públicos en nuestro código, y conocemos fácilmente los primeros tres elementos pero no sabemos cuándo randomNumber se inicializa.

Ahora veamos la función donde reveal se vuelve verdad.

function revealNFT() external onlyOwner {
    require(randomNumber != 0);
       reveal = true;
   }

Entendemos que se puede acceder a cada URI una vez que se revelan los NFT. Antes de eso, solo teníamos una imagen genérica para cada NFT. Pero antes de llamar revealNFT(), el propietario tiene que asegurarse de que randomNumber se ha inicializado.

Así que veamos dónde el código asigna un valor a randomNumber.

function requestRandomNumbers() external onlyOwner {
       requestId = COORDINATOR.requestRandomWords(
           keyHash,
           s_subscriptionId,
           requestConfirmations,
           callbackGasLimit,
           numWords
       );
   }
 
   function fulfillRandomWords(
       uint256, 
       uint256[] memory randomNumbers
   ) internal override {
       randomNumber = randomNumbers[0];
   }

En este ejemplo, utilizan VRF de Chainlink, un generador de números aleatorios fuera de la cadena, para crear una compensación en URI con randomNumber. Estas dos funciones anteriores muestran cómo funciona. El dueño tendrá que llamar requestRandomNumbers() que asignará un número aleatorio a randomNumber con una llamada interna de fulfillRandomWords() por Chainlink.

¿Qué sucede en la vida real cuando el propietario quiere revelar los NFT?

El propietario comenzará llamando requestRandomNumbers() para asignar un valor aleatorio a randomNumber. Porque randomNumber tiene ahora un valor distinto de cero, el propietario puede llamar revealNFT() para convertir el reveal marca en verdadero, y luego todos los URI son públicos con tokenURI().

Pero recuerda que para predecir las URI solo necesitamos cuatro elementos, incluidos tres ya conocidos y randomNumber que también es público y tiene su valor asignado cuando requestRandomNumbers() se llama.

Así que mientras tanto entre requestRandomNumbers() y revealNFT() se llaman, las URI aún no son públicas, pero tenemos todos los elementos para predecirlas.

Existe otra versión de esta vulnerabilidad. Aunque es más común, es más difícil de explotar. Si no hay un paso de revelación, el baseURI a menudo se establece al principio o en medio de la fase de acuñación. Entonces, tan pronto como se acuña un NFT, se revela, pero los tokens restantes permanecen desconocidos. Luego podemos recuperar el URI y usarlo para conocer los NFT más raros entre los restantes. Entonces tienes que intentar acuñar en el momento adecuado para encontrarlos.

Como dijimos anteriormente, el resultado es que los atacantes pueden obtener el control de los NFT más valiosos del proyecto, ya que podrían usar estos URI para verificar algunas propiedades raras en los metadatos antes de que alguien pueda verlos.

Para evitar tener el mismo tipo de vulnerabilidades, puede consultar estos Estrategias de aleatorización para gotas NFT Por William Entrike. Le ayudará a aleatorizar tokens URI de forma segura.

Mitigación

Entonces, ¿qué debe hacer si posee un proyecto NFT y desea mitigar los riesgos y proteger la base de código de BadReveal?

Dado que la esencia de la vulnerabilidad BadReveal se basa en la ventaja que tiene el atacante entre establecer la transacción URI del token en la transacción de revelación (o mint), la mitigación sería combinar las dos en una sola transacción.

Si planea revelar los NFT, configure el URI del token base al mismo tiempo:

 function reveal(string memory baseURI) public onlyOwner {
     revealed = true;
     baseTokenURI = baseURI;
 }

En el ejemplo que vimos anteriormente, la solución también podría ser mover la revelación para que la transacción de Chianlink la establezca automáticamente:

function fulfillRandomness(bytes32, uint256 _randomness) internal override {
     revealed = true;
     randomNumber = randomNumbers[0];
}

Aprovechando este enfoque, es posible que desee considerar descartar el revealed booleano de su código y reemplazándolo con la verificación sobre la marcha de Chainlink randomNumber asignación:

function someMintingFunction(uint256 _quantity) internal {
     require(!randomNumber, "Not revealed yet");
     //…
}

El uso de este mecanismo es la forma más sencilla de mitigar BadReveal, ya que no introduce la brecha entre las transacciones que permite a un atacante comprar antes de que otros compradores obtengan la información de los metadatos.

Resumen

Los riesgos cibernéticos de los proyectos NFT pueden tomar muchas formas y formas, es una creencia común que si su NFT no fue robado, no fue pirateado, pero ¿qué sucede si se suponía que obtendría el NFT más raro en el mazo, pero un atacante fue lo suficientemente inteligente como para comprarlo antes que tú? Te hackearon sin siquiera saberlo.

Es importante establecer requisitos para que los proyectos NFT inviertan en auditorías de alta calidad y utilicen buenas prácticas de seguridad. 

Creemos firmemente que esta vulnerabilidad se explota comúnmente en la naturaleza bajo el radar de los inversionistas y las comunidades de NFT. Comparta esta información con tantos proyectos como sea posible para que garanticen su seguridad.

Nos gustaría dar las gracias buscador de alfombras @rugpullfinder la ayuda en la investigación y verificación de la vulnerabilidad con nosotros. Utilizamos su experiencia y conocimiento en el espacio NFT para profundizar en la madriguera del conejo de BarReveal.

Apéndice: proyecto afectado

CatBloxPUMADetalles de la cápsula

https://etherscan.io/address/0x8b8D1225bB21CA07812FF3c2ee9358f7b5d90EcA

Everai Heroes: Detalles del dúo

https://etherscan.io/address/0x9a38dec0590abc8c883d72e52391090e948ddf12

Detalles de Degen Toonz

https://etherscan.io/address/0x19b86299c21505cdf59ce63740b240a9c822b5e4

Escrito por
AvigdorSason Cohen

Avigdor es investigador de seguridad web3 en Sayfer. Le apasionan las nuevas tecnologías de cadena de bloques y cómo podemos asegurarnos de desarrollarlas de manera segura.

 

Ir al contenido