Zokyo Security Research
JSON Injection in NFT Metadata
Exploring how Web2 injection vulnerabilities cross into the Web3 ecosystem through NFT metadata, and how to defend against them.
{ } !

The intersection of Web2 vulnerabilities and the Web3 ecosystem is a growing concern. As blockchain technology and NFTs continue to evolve, traditional web security issues find new attack surfaces. One such vulnerability - JSON Injection - can have particularly severe consequences when it targets NFT metadata.

This article explores the mechanics of JSON Injection, examines how it specifically impacts NFT metadata stored on-chain and off-chain, and provides actionable strategies for developers and auditors to defend against these attacks.

What is JSON?

JSON (JavaScript Object Notation) is a lightweight data-interchange format designed for transmitting structured data between applications. Originally derived from JavaScript, JSON has become the universal standard for APIs, configuration files, and - crucially for our discussion - NFT metadata.

JSON organizes data into key-value pairs using a simple and readable syntax. A basic JSON object looks like this:

JSON example.json
{
  "name": "Sunset Bliss",
  "description": "A beautiful painting of a sunset",
  "image": "ipfs://QmXyz.../sunset.png",
  "attributes": [
    { "trait_type": "Artist", "value": "Alice" },
    { "trait_type": "Style", "value": "Impressionist" }
  ]
}

The simplicity and human readability of JSON makes it the natural choice for encoding NFT metadata. But this same accessibility creates an attack surface when user-supplied data is concatenated into JSON strings without proper sanitization.

What is JSON Injection?

JSON Injection is a vulnerability that occurs when untrusted user input is incorporated into a JSON data structure without adequate validation or sanitization. An attacker exploits this by injecting special characters - such as quotation marks ("), backslashes (\), or colons (:) - to alter the intended structure of the JSON output.

There are two primary categories of JSON Injection:

  • Server-side injection: Unvalidated data infiltrates JSON streams on the server, potentially leading to privilege escalation, data manipulation, or unauthorized access to backend systems.
  • Client-side injection: Unsanitized data is parsed by front-end applications, paving the way for Cross-Site Scripting (XSS) attacks when the malicious JSON payload reaches a user's browser.

In the context of NFTs, both forms of injection create serious risks because metadata is consumed by marketplaces, wallets, and indexers that render the data directly in user interfaces.

NFT Metadata and Its Importance

NFT metadata describes the unique attributes and properties of a token. It typically includes the token's name, description, image URI, animation URL, and an array of trait attributes. This metadata is the backbone of how NFTs are displayed across platforms like OpenSea, Blur, and other marketplaces.

The metadata is usually returned by the tokenURI function in ERC-721 contracts or the uri function in ERC-1155 contracts. There are two common approaches to storing this metadata:

  • Off-chain storage: The tokenURI returns a URL pointing to an external JSON file hosted on IPFS, Arweave, or a centralized server.
  • On-chain storage: The metadata JSON is constructed directly within the smart contract using abi.encodePacked and returned as a base64-encoded data URI.

The on-chain approach is where JSON Injection becomes especially dangerous. When smart contracts dynamically build JSON strings by concatenating user-provided inputs, any unsanitized field becomes an injection point.

How JSON Injection Works in NFT Metadata

Consider a smart contract that allows users to mint NFTs with custom descriptions. The tokenURI function constructs the metadata JSON on-chain:

Solidity VulnerableNFT.sol
function tokenURI(uint256 tokenId) public view returns (string memory) {
    string memory json = string(
        abi.encodePacked(
            '{"name":"',
            tokenNames[tokenId],
            '", "description":"',
            tokenDescriptions[tokenId],
            '", "image": "',
            tokenImages[tokenId],
            '"}'
        )
    );
    return string(
        abi.encodePacked(
            "data:application/json;base64,",
            Base64.encode(bytes(json))
        )
    );
}

This pattern is extremely common in NFT contracts. The problem is that if a user supplies a description containing special JSON characters, they can break out of the intended field and inject arbitrary key-value pairs.

A Practical Injection Example

Imagine an artist named Alice mints an NFT called "Sunset Bliss." The application lets Alice input a description, which the contract concatenates into the JSON string. If the contract does not sanitize Alice's input, an attacker who gains control of the description field (or a malicious minter) could submit the following value:

Text Malicious input
A beautiful sunset", "name": "CryptoPunk #9999", "image": "ipfs://QmMalicious...

The resulting JSON, after concatenation, would look like this:

JSON Injected metadata output
{
  "name": "Sunset Bliss",
  "description": "A beautiful sunset",
  "name": "CryptoPunk #9999",
  "image": "ipfs://QmMalicious...",
  "image": "ipfs://QmOriginal.../sunset.png"
}

When a JSON parser encounters duplicate keys, most implementations (including JavaScript's JSON.parse) use the last occurrence. This means the injected "name" and "image" values overwrite the legitimate ones. The NFT now appears as "CryptoPunk #9999" with the attacker's chosen image on any marketplace that consumes this metadata.

Critical

Because most JSON parsers resolve duplicate keys by keeping the last value, the original metadata fields are silently overwritten. The contract's on-chain data looks normal to anyone inspecting the raw storage, but the rendered metadata tells a completely different story.

What Can Attackers Do with JSON Injection?

The consequences of JSON Injection in NFT metadata extend well beyond simple visual trickery. An attacker with the ability to manipulate metadata JSON can execute several attack vectors:

Mimicking or Cloning Legitimate NFTs

By injecting duplicate name fields, an attacker can craft an NFT that appears identical to a high-value token from a legitimate collection. When displayed on marketplaces, the fake token would show the same name, description, and potentially the same image as the original. Users may be tricked into believing they own or are purchasing an NFT they do not actually possess, leading to fraud.

Altering NFT Metadata

An attacker could manipulate metadata fields such as attributes, descriptions, or trait values to mislead users and third-party services that rely on token metadata. This is particularly dangerous in protocols where NFT metadata drives governance decisions, access control, or financial mechanics.

Redirecting to External URLs

By injecting a malicious URL into the image or animation_url field, the attacker can point the NFT's visual representation to arbitrary external content. This could redirect users to phishing sites, display inappropriate content, or undermine the integrity of the NFT's associated digital asset.

Executing Cross-Site Scripting (XSS) Attacks

If the injected JSON data contains executable JavaScript and the consuming application (a marketplace, wallet, or gallery) renders it without proper escaping, the attacker can trigger XSS attacks. When the front-end application fetches and processes the malicious JSON data, the injected script executes in the context of the user's session. This can lead to:

  • Session hijacking: Stealing authentication tokens or cookies
  • Wallet draining: Triggering malicious transaction approvals
  • Data exfiltration: Sending sensitive user data to attacker-controlled servers
  • UI manipulation: Altering what the user sees to facilitate social engineering
Note

The XSS risk is amplified when NFT metadata includes SVG images stored on-chain. SVGs can contain embedded <script> tags, making them a potent vector for JavaScript injection alongside JSON manipulation.

Manipulating On-Chain Governance

In protocols where NFTs serve as governance tokens and metadata drives voting displays, JSON Injection could alter how proposals or voting options appear to users. An attacker could replace artwork displayed during a voting phase with different content after the vote concludes, effectively performing a bait-and-switch at the metadata level.

Real-World Vulnerability Pattern

A well-documented example of this vulnerability pattern appeared in the Revolution Protocol, where the createPiece() function did not sanitize user input for the metadata.image and metadata.animationUrl fields. The vulnerable tokenURI() function used abi.encodePacked to construct JSON metadata without any character escaping:

Solidity Descriptor.sol (vulnerable)
function constructTokenURI(
    TokenURIParams memory params
) public pure returns (string memory) {
    string memory json = string(
        abi.encodePacked(
            '{"name":"',
            params.name,
            '", "description":"',
            params.description,
            '", "image": "',
            params.image,
            '", "animation_url": "',
            params.animation_url,
            '"}'
        )
    );
    return string(
        abi.encodePacked(
            "data:application/json;base64,",
            Base64.encode(bytes(json))
        )
    );
}

An attacker could submit a malicious animation_url value like:

Text Injection payload
", "image": "ipfs://QmFakeMonaLisa

During the voting phase, the front-end displayed the legitimate image field. However, after the NFT was minted, querying tokenURI() returned base64-encoded JSON containing both image references. When decoded with JSON.parse(), the injected image overwrote the original, meaning the final NFT displayed entirely different art than what the community voted on.

Mitigation Strategies

Defending against JSON Injection in NFT metadata requires a defense-in-depth approach. No single measure is sufficient on its own - the following strategies should be implemented together.

Input Sanitization and Character Escaping

The most fundamental defense is sanitizing all user input before it is incorporated into JSON metadata. This means escaping special characters that could alter the JSON structure:

Solidity JSONSanitizer.sol
function escapeJSON(string memory input)
    internal pure
    returns (string memory)
{
    bytes memory inputBytes = bytes(input);
    uint256 extraChars = 0;

    // First pass: count characters that need escaping
    for (uint256 i = 0; i < inputBytes.length; i++) {
        if (
            inputBytes[i] == '"' ||
            inputBytes[i] == '\\' ||
            inputBytes[i] == '/'
        ) {
            extraChars++;
        }
    }

    // Second pass: build escaped string
    bytes memory result = new bytes(inputBytes.length + extraChars);
    uint256 j = 0;
    for (uint256 i = 0; i < inputBytes.length; i++) {
        if (
            inputBytes[i] == '"' ||
            inputBytes[i] == '\\' ||
            inputBytes[i] == '/'
        ) {
            result[j++] = '\\';
        }
        result[j++] = inputBytes[i];
    }
    return string(result);
}

The key characters to escape include double quotes ("), backslashes (\), forward slashes (/), and control characters like newlines and tabs. These characters can all be used to break the JSON structure.

Strict Input Validation

Beyond escaping, contracts should validate that user inputs conform to expected patterns. For NFT metadata fields, this means:

  • Length limits: Enforce maximum character counts for name, description, and URI fields
  • Character whitelisting: Reject or strip characters outside an allowed set (alphanumeric, spaces, and a limited set of punctuation)
  • URI validation: Ensure image and animation URLs match expected patterns (e.g., ipfs:// or https:// prefix only)
Solidity InputValidator.sol
function validateMetadataInput(string memory input)
    internal pure
{
    bytes memory b = bytes(input);
    require(b.length > 0 && b.length <= 256, "Invalid length");
    for (uint256 i = 0; i < b.length; i++) {
        require(
            b[i] != '"' && b[i] != '\\',
            "Forbidden character"
        );
    }
}

JSON Schema Validation

For off-chain metadata or hybrid approaches, implement strict JSON schema validation. Ensure that the output JSON matches the expected structure exactly and that no unexpected keys or duplicate fields are present. Libraries like the OWASP JSON Sanitizer can help ensure that JSON outputs are well-formed and free from injection artifacts.

Front-End Escaping and Content Security

Marketplaces, wallets, and other front-end applications that display NFT metadata must escape all user-generated content during rendering. This means:

  • HTML-encoding metadata values before inserting them into the DOM
  • Sanitizing SVG content to strip embedded scripts before rendering
  • Implementing Content Security Policies (CSP) to limit what scripts can execute
  • Using safe rendering methods like textContent instead of innerHTML

Regular Security Audits and Testing

Smart contracts that construct metadata on-chain should undergo thorough security audits with specific attention to injection vectors. Automated testing should include:

  • Fuzz testing with inputs containing special JSON characters, escape sequences, and boundary-length strings
  • Integration testing that verifies the complete pipeline from minting through metadata retrieval and front-end rendering
  • Penetration testing of the front-end applications that consume and display NFT metadata
Best Practice

When constructing JSON metadata on-chain, use a dedicated JSON encoding library rather than raw string concatenation with abi.encodePacked. If a library is not available, always apply character escaping as the first step before any concatenation.

Defense-in-Depth Summary

Protecting against JSON Injection in NFT metadata requires coordinated defenses across multiple layers:

  1. Smart contract level: Escape special characters in all user inputs before JSON concatenation. Validate inputs against strict character and length constraints.
  2. Protocol level: Use structured JSON encoding libraries instead of raw string concatenation. Implement schema validation for metadata outputs.
  3. Front-end level: Sanitize all metadata before rendering. Use CSP headers. Never trust on-chain metadata to be safe for direct DOM insertion.
  4. Audit level: Include JSON injection testing in every smart contract audit. Fuzz metadata construction functions with adversarial inputs.

Conclusion

JSON Injection in NFT metadata represents a critical intersection of Web2 vulnerabilities and Web3 infrastructure. As NFTs continue to underpin digital ownership, governance, and financial systems, the integrity of their metadata is non-negotiable.

The vulnerability is deceptively simple - it relies on the same injection techniques that have plagued web applications for decades. But in the Web3 context, the consequences are amplified: impersonated tokens can defraud buyers, redirected images can undermine trust in entire collections, and XSS payloads can drain wallets.

Developers building NFT contracts must treat metadata construction with the same rigor they apply to financial logic. Input sanitization, character escaping, schema validation, and front-end security are not optional - they are fundamental requirements for any protocol that constructs or renders NFT metadata.

At Zokyo, our audit methodology includes dedicated testing for JSON injection and metadata manipulation across all NFT-related engagements. If you are building a protocol that handles NFT metadata on-chain, reach out to our team for a comprehensive security review.