The ScrvusdVerifierV1 and ScrvusdVerifierV2 contracts contain a critical vulnerability in the verifyScrvusdByBlockHash
function. The verification only checks that the provided blockhash matches what the BlockHashOracle returns for a claimed block number, but fails to validate that the claimed block number actually corresponds to that blockhash. This allows an attacker to manipulate prices by using valid blockhashes with arbitrary block numbers, especially in cases where the BlockHashOracle might return incorrect blockhashes.
In ScrvusdVerifierV1.sol, the verifyScrvusdByBlockHash
function attempts to verify the authenticity of price parameters using a block header:
The critical vulnerability lies in this verification logic:
The function only checks that the blockhash provided matches what the oracle returns for the claimed block number, but does not verify that the block number is actually the correct one for that blockhash.
According to the contest documentation:
[The blockhash oracle] can rarely provide an incorrect blockhash, but not an incorrect block number.
This means the blockhash oracle may occasionally return an incorrect blockhash for a given block number. When this happens, an attacker can exploit this by:
Obtaining a valid blockhash (possibly from a historical block)
Providing this valid blockhash with an arbitrary block number of their choice
The verification will pass as long as the BlockHashOracle returns the same blockhash for that number
The same vulnerability exists in ScrvusdVerifierV2.sol's verifyPeriodByBlockHash
function, which inherits from ScrvusdVerifierV1.
This vulnerability has several severe impacts:
Price Manipulation: An attacker can use historical price proofs with newer block numbers, causing the oracle to set incorrect prices. For example, if scrvUSD's price was lower in the past, replaying that proof with a newer block number would cause the oracle to report an artificially low price.
Replay Attacks: Valid proofs can be replayed with different block numbers, potentially causing unnecessary and incorrect price updates. Since the update_price
function in ScrvusdOracleV2.vy uses the last_block_number
to protect against outdated updates, manipulating the block number can bypass this protection.
Timestamp Manipulation: Since the timestamp from the block header is used in price calculations (the function update_price
in ScrvusdOracleV2.vy uses _ts
for price calculations), manipulating block numbers can lead to incorrect timestamps being used.
Cross-Chain Vulnerability: Since this oracle is designed for cross-chain use as stated in the project context, manipulated prices could affect multiple blockchains, potentially leading to systematic exploitation of stableswap pools.
Below is a proof of concept demonstrating the vulnerability:
This PoC demonstrates how an attacker could:
Identify a situation where the BlockHashOracle returns an incorrect blockhash
Use that incorrect blockhash with manipulated block data
Successfully pass the verification check and update the oracle with incorrect price data
The root cause of this vulnerability is:
Incomplete Validation: The contract only verifies the blockhash in one direction (block number → blockhash) but not the reverse (blockhash → block number). This is present in both the verifyScrvusdByBlockHash
function in ScrvusdVerifierV1.sol and the verifyPeriodByBlockHash
function in ScrvusdVerifierV2.sol.
Over-reliance on BlockHashOracle: The contract fully trusts the BlockHashOracle for validating the block number and blockhash relationship, despite the documentation mentioning that the oracle can occasionally provide incorrect blockhashes.
No Historical Verification: There's no mechanism to ensure that prices are only updated with newer blocks than the previous update. Although the update_price
function in ScrvusdOracleV2.vy has a check (assert self.last_block_number <= _block_number, "Outdated"
), this can be bypassed by manipulating the block number.
Manual code review
Logical flow analysis
Pytest for proof of concept development
To address this vulnerability, implement the following measures:
Add Directional Validation: The BlockHashOracle should provide and the verifier should use a function to get the correct block number for a given blockhash:
Add Temporal Checks: Ensure the block number is within a reasonable age:
Enhance Oracle Protection: Modify the update_price
function in ScrvusdOracleV2.vy to include additional checks against replay attacks:
By implementing these mitigations, the contract will be protected against attacks that exploit blockhash verification weaknesses, ensuring the integrity of the price oracle across all integrated systems.
- Anything related to the output by the `BLOCK_HASH_ORACLE` is OOS per \[docs here]\(<https://github.com/CodeHawks-Contests/2025-03-curve?tab=readme-ov-file#blockhash-oracle>). - The PoC utilizes a mock `BLOCK_HASH_ORACLE`which is not representative of the one used by the protocol - Even when block hash returned is incorrect, the assumption is already explicitly made known in the docs, and the contract allows a subsequent update within the same block to update and correct prices - All state roots and proofs must be verified by the OOS `StateProofVerifier` inherited as `Verifier`, so there is no proof that manipulating block timestamp/block number/inputs can affect a price update - There seems to be a lot of confusion on the block hash check. The block hash check is a unique identifier of a block and has nothing to do with the state root. All value verifications is performed by the OOS Verifier contract as mentioned above
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.