This NFT will steal your IP

Thumbnail containing the NFT

Here’s a link to an NFT on opensea.io. Do you dare to click on it? If you do, I’ll know your IP Address and whether you are using crypto wallet browser add-ons. This post will show you how it’s done

I wanted to learn how NFTs work, so I studied them by messing around with its security. Inspired by the following tweet, I built an NFT that allows me to collect your IP and further metadata if you view it on opensea. The whole NFT I created is built on Polygon using the Mumbai testnetwork, but it can also be applied to the Ethereum network directly.


Fundamentals

On the Blockchain all transactions are public, so let’s investigate the IP stealing NFT on Polygonscan. The input data of the smart contract contains the jsonURI field. This is the link containing the NFT metadata in json format.

Thumbnail containing the NFT

The metadata json format is defined by the ERC721 Metadata JSON Schema and contains the actual image URL as well as name and description. Often the metadata.json and the actual image are stored on IPS which provides tampering and censorship storage.

{
    "title": "Asset Metadata",
    "type": "object",
    "properties": {
        "name": {
            "type": "string",
            "description": "Identifies the asset to which this NFT represents"
        },
        "description": {
            "type": "string",
            "description": "Describes the asset to which this NFT represents"
        },
        "image": {
            "type": "string",
            "description": "A URI pointing to a resource with mime type image/* representing the asset to which this NFT represents. Consider making any images at a width between 320 and 1080 pixels and aspect ratio between 1.91:1 and 4:5 inclusive."
        }
    }
}

Additional to the ERC-721 metadata standard, opensea support additional fields. One of them is the animation_url. It contains a multimedia attachment which is displayed when viewing the image on opensea. Various media types are supported, HTML is one of them.

{
  "name": "IP-NFT",
  "description": "This NFT will (hopefully) display your IP",
  "animation_url": "https://www.doge-to-the-moon.xyz:5005",
  "image": "ipfs://bafybeigo73mvfuszx5gfl4ljqqsod4azumqaitorgy6suyx3r3j6wenfee/nft.png"
}


Stealing the IP

We can use as animation URL a self hosted server. This way, the person viewing the NFT on opensea will automatically request the ‘animation’ from our server and we catch metadata like the IP-Address and the user-agent.

As the animation can be a HTML web page, we can execute (to some limited extent) javascript code. This allows us to get additional data such as:

  • Whether crypto wallets such as metamask or phantom are installed in your browser
  • If the user ‘accidentally’ clicks on the image, we can collect all keyboard input


Implementation

Flask is a micro web framework written in Python, which allows us to implement such a functionality in a couple of lines of code. The following (shortened) HTML code-snippet shows the actual ‘animation’ displayed on opensea.

<!DOCTYPE html>
<html>

<body>
    <img width="100%"
        src="https://ipfs.io/ipfs/bafybeigo73mvfuszx5gfl4ljqqsod4azumqaitorgy6suyx3r3j6wenfee/nft.png">
    <div class="txt">
        <h3>Your IP: request.remote_addr</h3>

        <h3 id="wallet-sol">SOL Wallet: ❌</h3>
        <h3 id="wallet-eth">ETH Wallet: ❌</h3>
    </div>

    <script>
        if (typeof window.ethereum !== 'undefined') {
            document.getElementById("wallet-sol").textContent = "SOL Wallet: ✅";
        }

        if (typeof window.solana !== 'undefined') {
            document.getElementById("wallet-eth").textContent = "ETH Wallet: ✅";
        }
    </script>
</body>

</html>

The body consists of the original image, that is stored on IPFS. So far, the ‘animation’ looks like the real NFT and the user does not notice any difference.

For demonstration, I overlay on top of the image the viewer’s actual IP address, which we receive from the web request data as well as the connected crypto wallets, which can be determined with the following javascript code.

if (typeof window.ethereum !== 'undefined') {
    console.log("Ethereum wallet connected")
}

For the Flask webserver, first generate valid SSL/TLS certificates. This can be done using Let’s encrypt. Last, fill the IP address of the webpage with the actual one of the request and host the site. Use this domain and webpage as your animation URL in the NFT metadata

from flask import Flask, request


app = Flask(__name__)


@app.route('/')
def animation():
    return webpage.replace("request.remote_addr", request.remote_addr)


if __name__ == '__main__':
    app.run(host="0.0.0.0", port=5005, ssl_context=(
        'fullchain.pem', 'privkey.pem'))


Is this a problem?

Screenshot of the NFT on opensea

Not really. For centralized websites it is normal to collect data, such as user agent or IP for analytics and you usually cannot identify the natural person behind the IP-Address directly. In this case you could use it as analytics for the NFT creator.

But does the person looking at the NFT on opensea know each individual NFT creator can collect data and track you? Do you know whether to trust the NFT creator on handling your data responsibly?

References