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):
- A contract accepts price updates via
updatePrice(uint256 price, uint256 timestamp)from any caller. - No cryptographic signature verifies that the price came from an authorized oracle.
- An attacker calls
updatePrice(1000000e18, block.timestamp)— a 1,000,000x inflated price. - The contract’s collateral calculations use the injected price.
- The attacker borrows the protocol’s entire treasury against a small amount of collateral.
Replay attack (no freshness check):
- An authorized oracle signed a price of
$100(a historical low) 1 hour ago. - The contract checks the signer address but not the timestamp.
- An attacker replays the old
$100signed price data. - Current market price is $2000; the replayed price allows under-priced borrowing.
- The attacker borrows against collateral valued at 20% of its true value.
Single-source DoS:
- A contract depends on a single API endpoint (e.g.,
https://price-api.example.com/eth). - An attacker DDoSes the endpoint or compromises the server to return zero.
- All protocol functions that require the price revert.
- 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):
- Identify the external API endpoint used by the contract.
- Craft malicious API response without proper signature (if lacks_verification).
- Use MITM or compromised endpoint to inject data.
- Replay old API responses with favorable values (if lacks_freshness).
- Exploit time-sensitive operations (liquidations, trades).
- Target single point of failure in data pipeline (if single_source).
- DoS the API endpoint to force contract failures.
- Execute high-value transactions using manipulated data.
- 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
- Detector: API Off-Chain Data Detector
- Remediation Guide: API Off-Chain Data 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
ecrecoverto 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.timestampto 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 (Chainlink, Band Protocol, 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 Chainlink, Band Protocol, or Pyth 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");