Conic Finance ETH Omnipool was exploited for $3.35M via a read-only reentrancy vulnerability. The CurveLPOracleV2 contract incorrectly assumed Curve v2 pools use the ETH address (0xeee...eee), but they actually use WETH. This caused the isETH check to fail, bypassing the reentrancy guard on the rETH pool. Attackers called handleDepeggedCurvePool() during the reentrancy callback to manipulate oracle prices.
Vulnerability Detected
Our scanners would have identified the vulnerability that led to this exploit.
Functions depositFor and withdraw are vulnerable to AMM liquidity manipulation. Swaps use spot price from reserves without TWAP oracle protection, enabling price manipulation within a single transaction. This was a contributing factor in the exploit.
// BaseConicPool.sol - Price Manipulation Vulnerability
// Scanner detected: amm-liquidity-manipulation
function depositFor(
address account,
uint256 underlyingAmount,
uint256 minLpReceived,
bool stake
) public override returns (uint256) {
// VULNERABILITY: Uses spot price without TWAP
// Attacker can manipulate price in same transaction
uint256 exchangeRate = _getExchangeRate(); // Spot price!
uint256 lpToMint = underlyingAmount * exchangeRate / 1e18;
// Flash loan attack: deposit at manipulated rate
// then withdraw at normal rate = profit
}Recommendation: Implement Time-Weighted Average Price (TWAP) oracles instead of spot prices. Add price deviation checks and consider using Chainlink oracles as a secondary validation.
Functions unstakeAndWithdraw and withdraw perform critical operations but lack access control modifiers. These functions can be called by anyone, potentially allowing unauthorized fund movements.
// BaseConicPool.sol - Missing Access Control
// Scanner detected: missing-access-modifiers (CWE-284)
function unstakeAndWithdraw(uint256 conicLpAmount, uint256 minUnderlyingReceived)
external // VULNERABILITY: No access modifier
returns (uint256)
{
// Critical fund movement without authorization check
return _unstakeAndWithdraw(conicLpAmount, minUnderlyingReceived, msg.sender);
}
function withdraw(uint256 conicLpAmount, uint256 minUnderlyingReceived)
external // VULNERABILITY: No access modifier
returns (uint256)
{
return _withdraw(conicLpAmount, minUnderlyingReceived, msg.sender);
}Recommendation: Add an access control modifier like onlyOwner or implement role-based access control for these sensitive operations.
Contract uses governance tokens without snapshot protection mechanisms. This enables flash loan attacks where attackers can temporarily acquire tokens to manipulate governance decisions within a single block.
Recommendation: Implement snapshot-based voting that captures token balances at a past block. Add time delays between acquiring tokens and voting power activation.
Multiple functions rely on a single oracle source, creating centralization risk. The oracle can be manipulated during reentrancy callbacks, as demonstrated in the exploit where CurveLPOracleV2 returned stale/manipulated prices.
Recommendation: Use multiple oracle sources with price deviation checks. Implement circuit breakers for large price movements.
Function handleDepeggedCurvePool may be vulnerable to reentrancy attacks due to state changes after external calls. This is the EXACT vulnerability that was exploited in the $3.35M hack. The attacker called this function during a reentrancy callback from Curve's remove_liquidity to manipulate oracle prices.
// BaseConicPool.sol - EXPLOITED FUNCTION
// Scanner detected: classic-reentrancy (CWE-841)
function handleDepeggedCurvePool(address curvePool_) external override {
// External calls to Curve without reentrancy protection
// Attacker exploited this during remove_liquidity callback
IConicPoolWeightManager weightManager = _getWeightManager();
// VULNERABILITY: State read during reentrancy
// Oracle prices are stale during callback execution
uint256 price = oracle.getUSDPrice(lpToken);
// Attacker manipulated this price check
if (price < DEPEG_THRESHOLD) {
_handleDepegging(curvePool_);
}
}Recommendation: Apply checks-effects-interactions pattern or use a reentrancy guard. The vulnerable code made external calls to Curve pools before updating internal state, allowing price manipulation during the callback.
BlockSecOps scans your smart contracts with 580+ security detectors, catching vulnerabilities before attackers do.