How Your NFTs Could Have Been Stolen in Just One Click

How it started.

  In October of last year, I had the pleasure of collaborating with a close friend to search for vulnerabilities in an NFT bug bounty program. Drawing from our diverse skill sets and expertise, my friend, who has extensive knowledge in blockchain-related technologies, and I, with a background in web applications, formed a highly effective team that operated seamlessly, leading to one of the biggest bounties of our career.

Our Hunting Methodology.

      In order to ensure a comprehensive and well-informed approach, we meticulously delved into the documentation available to us, taking care to read it in its entirety, in order to gain a deeper understanding of the target. Throughout this process, we happened upon the NFT metadata documentation, where one field in particular, the animation_url, piqued our curiosity and warranted further investigation. By thoroughly examining this aspect, we were able to uncover potential vulnerabilities that could have otherwise gone unnoticed.


animation_url: The animation_url feature now enables the creation of dynamic NFTs with JavaScript canvas, WebGL, and other interactive elements, by supporting HTML pages. This allows for the incorporation of scripts and relative paths within the HTML page. However, the use of browser extensions is not supported.


    We discovered this field allowed users to create animated NFTs with JavaScript code execution. However, we also noted that “the use of browser extensions is not supported” was false. Our deep knowledge of how browser extensions work made us realize that blocking access to browser extensions is impossible.

    As the animated NFT feature was unavailable on the Web UI, my friend created the animated NFT in the testnet blockchain with an HTML file pointing to one of my domains.

     After implementing it, the animated NFT in the website was referenced as the following:

<iframe allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" frameborder="0" height="100%" sandbox="allow-scripts" src="" width="100%" style="min-height: 500px;"></iframe>

   We got an iframe, but with the top window referencing and I just controlling a child iframe, what could we do? That’s where your hacker mindset should pop.

Playing Around with the possibilities.

    We then discovered that we could use the sandbox=”allow-scripts” feature to load our Chrome wallet extensions, such as Metamask and Coinbase, which worked as expected.

Then my head was like:

“what if I can interact with the victim’s wallet, or even better, try to steal their NFTs or takeover the account?”.

We explored the possibility of interacting with the victim wallet and discovered that a JavaScript SDK was used to send postMessages to the extension wallet. The wallet had a postMessage listener that received everything from the SDK and showed the popup prompts to interact with the blockchain. We tested this on different wallets.

For example, sending the following postMessage will result in a prompt window asking you to sign an Ethereum Message.

After sending the postMessage in the Chrome Console.


        Having invested a few minutes in the rigorous investigation and after several unsuccessful trials on Metamask. I arrived at a stunning revelation in Coinbase Wallet: the postMessage dispatched from the child iframe would readily display the domain of the top window. As a consequence, in the event that you chanced upon my evil NFT on that marketplace, I could quickly induce you into performing actions on your wallet, all under the guise of a legitimate signature request from the NFT platform, displaying the logo and domain of the legit NFT site.


  • postMessage sent from the top window will show the top domain in CoinBase wallet (eg
  • postMessage sent from child window/iframe ( will show in the wallet prompt the top domain ( Hijacking the top domain.


We got the jackpot!

Demonstrating Impact, centralized GraphQL API in a decentralized world.

    We then discovered that the NFT site had a centralized GraphQL API to manage collections, profile pictures, etc. The login system worked by sending the connected wallet from the browser to the GraphQL server, which replied with the message to be signed. The sign postMessage prompt was sent, and once the message’s signature was received, it was sent back to the GraphQL API. A valid JWT token was generated to modify the profile and do everything to the user’s account.



    Our exploit involved creating a PHP script that grabbed the wallet address from the browser and sent it to our server. We asked GraphQL to give us a message to be signed and sent it back to the victim. When the victim signed it, they returned it to us, allowing us to generate a valid JWT Authentication Bearer token and take over the account with one click (sign message when visiting our NFT).

    Our final PoC included a MiTM attack in the GraphQL, the use of a feature in Coinbase wallet that showed the top domain when a wallet interaction was called from inside an iframe (which was fixed), and the abuse of a feature – JavaScript allowed NFTs – with a false statement that you couldn’t interact with Chrome extensions.

    To be more clear, we graph it:


    In result, our exploit managed to Hack a visitor to our NFT that clicked into a sign message that was exact the same as the legit login flow.

    In the hype of the crypto boom in 2021, all NFT sites wanted to be one step ahead by adding cool features such as Animated JavaScript-enabled NFTs. However, allowing an attacker to execute their own JavaScript code into a NFT site wasn’t a great idea and should be implemented with care.


      • October 6th 2022: the site has been notified.

      • October 12th 2022: the report was triaged.

      • October 19th 2022: a $XXX,XXX bounty was awarded.

      • December 2022: Coinbase and multiple other affected wallets fixed all features that made able this chain to work. No interaction from child wallets are possible under sandbox, and the top domain display issue has been fixed.

    Leave a Reply

    Your email address will not be published. Required fields are marked *


    © 2023 PermaSecure