Skip to main content
Sigvex

API/Off-Chain Data Manipulation Exploit Generator

Sigvex exploit generator that validates off-chain data trust vulnerabilities where contracts accept external API data without signature verification, freshness checks, or redundancy — enabling data injection, replay attacks, and single-point-of-failure exploits.

API/Off-Chain Data Manipulation Exploit Generator

Overview

The API/off-chain data manipulation exploit generator validates findings from the api_offchain_data, external_data, offchain_data, and related detectors by analyzing whether the contract relies on off-chain data without cryptographic signature verification, freshness timestamps, or multiple independent sources. When all three are missing, the generator confirms the vulnerability and produces a proof of concept showing data injection, replay, and single-source DoS attacks. Estimated gas: 150,000 (higher due to external call overhead).

Contracts that consume external data — price feeds, sports results, random numbers, KYC status — must verify that the data is authentic, recent, and from a trusted source. Without these checks, any party that can submit data to the contract (or intercept the data pipeline) can manipulate protocol behavior. Unlike on-chain oracle manipulation, off-chain data attacks do not require flash loans or large capital — a compromised API endpoint or a MITM attack is sufficient.

Note: Exploit generation in Sigvex is for vulnerability validation purposes only.

Attack Scenario

Data injection (no signature verification):

  1. A contract accepts price updates via updatePrice(uint256 price, uint256 timestamp) from any caller.
  2. No cryptographic signature verifies that the price came from an authorized oracle.
  3. An attacker calls updatePrice(1000000e18, block.timestamp) — a 1,000,000x inflated price.
  4. The contract’s collateral calculations use the injected price.
  5. The attacker borrows the protocol’s entire treasury against a small amount of collateral.

Replay attack (no freshness check):

  1. An authorized oracle signed a price of $100 (a historical low) 1 hour ago.
  2. The contract checks the signer address but not the timestamp.
  3. An attacker replays the old $100 signed price data.
  4. Current market price is $2000; the replayed price allows under-priced borrowing.
  5. The attacker borrows against collateral valued at 20% of its true value.

Single-source DoS:

  1. A contract depends on a single API endpoint (e.g., https://price-api.example.com/eth).
  2. An attacker DDoSes the endpoint or compromises the server to return zero.
  3. All protocol functions that require the price revert.
  4. Borrowers cannot repay loans, depositors cannot withdraw — protocol is bricked.

Exploit Mechanics

The generator performs description-based static analysis. Three boolean signals determine exploitability:

Signal Description keywords Risk
has_external_call external, off-chain, api Required for exploit
lacks_verification unchecked, unverified, no signature, without signature, no validation, without validation High: data injection
lacks_freshness stale, no timestamp, no freshness, outdated High: replay attacks
single_source single source, no redundancy, centralized Medium: DoS

Exploitable when: has_external_call AND (lacks_verification OR lacks_freshness OR single_source).

Attack sequence (9 steps):

  1. Identify the external API endpoint used by the contract.
  2. Craft malicious API response without proper signature (if lacks_verification).
  3. Use MITM or compromised endpoint to inject data.
  4. Replay old API responses with favorable values (if lacks_freshness).
  5. Exploit time-sensitive operations (liquidations, trades).
  6. Target single point of failure in data pipeline (if single_source).
  7. DoS the API endpoint to force contract failures.
  8. Execute high-value transactions using manipulated data.
  9. Extract funds before manipulation is detected.
// VULNERABLE: No signature verification, no freshness check
contract VulnerableContract {
    address public dataProvider;

    function executeTradeWithPrice(uint256 amount, uint256 price, uint256 timestamp) external {
        // NO SIGNATURE VERIFICATION — anyone can provide price data
        // NO FRESHNESS CHECK — old data can be replayed
        uint256 value = amount * price / 1e18;
        balances[msg.sender] += value;
    }
}

// Exploit: Inject inflated price
contract ApiDataExploit {
    function attack() external {
        // No authorization needed — inject any price
        uint256 maliciousPrice = 1_000_000 * 1e18; // 1,000,000x actual price
        target.executeTradeWithPrice(1 ether, maliciousPrice, block.timestamp);
    }
}

// SECURE: EIP-712 signed data with freshness check
contract SecureContract {
    address public trustedOracle;
    uint256 public constant MAX_PRICE_AGE = 300; // 5 minutes

    struct SignedPrice {
        uint256 price;
        uint256 timestamp;
        uint8 v;
        bytes32 r;
        bytes32 s;
    }

    function executeTradeWithSignature(uint256 amount, SignedPrice memory signedPrice) external {
        // 1. Freshness check
        require(signedPrice.timestamp > block.timestamp - MAX_PRICE_AGE, "Price data too old");
        require(signedPrice.timestamp <= block.timestamp, "Price data from future");

        // 2. Cryptographic signature verification
        bytes32 messageHash = keccak256(abi.encodePacked(signedPrice.price, signedPrice.timestamp));
        bytes32 ethSignedHash = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", messageHash));
        address signer = ecrecover(ethSignedHash, signedPrice.v, signedPrice.r, signedPrice.s);
        require(signer == trustedOracle, "Invalid signature");

        // 3. Sanity bounds
        require(signedPrice.price > 0, "Invalid price");
        require(signedPrice.price < type(uint128).max, "Price overflow");

        uint256 value = amount * signedPrice.price / 1e18;
        // Execute trade...
    }
}

Remediation

Implement all three layers of off-chain data security:

Layer 1: Cryptographic verification

  • Require an EIP-712 signature from an authorized oracle on every data payload.
  • The signed message must include both the data value and the timestamp.
  • Use ecrecover to verify the signer matches the registered oracle address.
  • Consider multi-oracle threshold signing (e.g., 3-of-5 oracles must sign).

Layer 2: Freshness validation

  • Require timestamp > block.timestamp - MAX_AGE (5 minutes for price data).
  • Require timestamp <= block.timestamp to prevent future timestamps.
  • Bind the timestamp into the signed message — the oracle signs (price, timestamp) together.

Layer 3: Redundancy and circuit breakers

  • Use multiple independent data sources (the off-chain aggregator, a secondary oracle network, custom oracle network).
  • Reject any data point that deviates more than X% from the median of all sources.
  • Implement a circuit breaker: if fewer than M sources are available, pause price-dependent operations.
// Best-practice: Established oracle network (no custom off-chain API needed)
// Use the off-chain aggregator, a secondary oracle network, or a pull-based oracle network
// All provide: cryptographic verification + freshness + redundancy + decentralization
AggregatorV3Interface priceFeed = AggregatorV3Interface(CHAINLINK_ETH_USD);
(, int256 price,, uint256 updatedAt,) = priceFeed.latestRoundData();
require(price > 0 && updatedAt >= block.timestamp - 3600, "Invalid price");

References