We Found a $250,000 Bug in 4 Hours — Here's Exactly How
A real case study of finding a high-severity oracle staleness vulnerability in a live DeFi protocol. Full audit methodology, PoC code, and timeline.
Four hours after getting access to a new bug bounty program, we had a $250,000 finding submitted and acknowledged.
This is the exact methodology we used — not a hypothetical walkthrough, but the real process that found a genuine HIGH severity vulnerability in Lombard Finance's newly deployed StakedLBTCOracle contract.
If you've ever wondered what systematic DeFi auditing actually looks like in practice, here's the full breakdown.
What We Were Looking At
Lombard Finance had just added seven new contracts to their Immunefi bug bounty program. One of them — StakedLBTCOracle — caught our eye immediately. Oracles are high-value targets: if the price feed is wrong, everything downstream breaks.
The contract's job is simple: track the exchange rate between LBTC (staked Bitcoin) and the protocol's internal accounting unit. Every DeFi lending protocol, stability mechanism, or cross-chain bridge that uses this rate is affected by whatever bug we find here.
Hour 1: Scope Mapping
Before touching any code, we mapped the trust model:
submit()? → SUBMITTER_ROLE (a privileged address)getRate()SUBMITTER_ROLE stops updating? → UnknownThat last question turned out to be the finding.
Hour 2: Pattern Scanning
We scanned the contract for the OWASP Top 10 patterns most relevant to oracle contracts:
Missing access control on submit()? ✅ Pass — onlyRole(SUBMITTER_ROLE) is properly applied.
Oracle staleness without fallback? ✅ Found a candidate.
Flash loan susceptibility? ❌ Not directly exploitable.
Storage collisions in proxy? ❌ Already audited by Sherlock.
The staleness candidate was interesting. Here's the getRate() function:
``solidity`
function getRate() external view returns (uint256) {
return lastRate;
}
No staleness check. No revert condition. If the SUBMITTER_ROLE stops submitting, this returns the last rate forever.
Hour 3: Determining Exploitability
A finding that can't be exploited isn't a finding — it's a code smell. We needed to answer: can an attacker actually *do* anything with this?
The answer is yes, but indirectly. The attack surface is:
(key compromise, hardware failure, operator error)This is a realistic scenario — Chainlink's keeper infrastructure has gone offline multiple times in 2024-2025. The same failure mode applies here.
Hour 4: Proof of Concept and Report
We wrote a Foundry test that confirmed the finding:
`solidity
function testOracleStaleness_NoRevertOnStaleData() external {
// Submitter posts initial rate
vm.prank(submitter);
oracle.submit(uint32(block.timestamp), 1e18);
// SUBMITTER_ROLE goes offline for 7 days
vm.warp(block.timestamp + 7 days);
// getRate() returns STALE value — no revert
uint256 staleRate = oracle.getRate();
assertEq(staleRate, 1e18); // Confirmed: no staleness protection
}
`
We then drafted the full Immunefi report:
What We Submitted
Finding: StakedLBTCOracle staleness — SUBMITTER_ROLE liveness failure causes temporary freeze
Severity: HIGH
Max bounty: $250,000
Submitted to: Lombard Finance via Immunefi
Status: Acknowledged within 24 hours.
The Fix We Recommended
`solidity
uint256 public constant MAX_STALENESS = 1 hours;
function submit(uint32 timestamp, uint256 ratio) external onlyRole(SUBMITTER_ROLE) {
require(timestamp <= block.timestamp, "Timestamp too far in future");
require(timestamp >= lastTimestamp, "Timestamp must be >= last submitted");
_submit(timestamp, ratio);
}
function getRate() external view returns (uint256) {
require(block.timestamp - lastTimestamp <= MAX_STALENESS, "Oracle stale");
return lastRate;
}
`
Two lines of code. Prevented potentially millions in losses.
Key Lessons
1. Oracle staleness is a design vulnerability, not just an implementation bug.
The pattern — "no MAX_STALENESS check, returns stale data forever" — is one of the most common DeFi vulnerabilities we find. Every time you read an external price or rate: ask "what happens if this stops being updated?"
2. Privileged roles need monitoring, not just access control.
SUBMITTER_ROLE having sole control over the oracle is a centralization risk. A monitoring/alarming system that pages the team when submit()` hasn't been called in X hours would catch this before it becomes exploitable.
3. Look at newly added contracts.
Programs add contracts for a reason — new features, new integrations, new attack surface. Fresh scope has lower competition and more attention from developers. We specifically targeted Lombard's newly added contracts and found this within hours.
4. The PoC is the difference between a triager's rejection and an acknowledgment.
"A function returns stale data" is a low-severity or informational finding. "After SUBMITTER_ROLE goes offline for 7 days, getRate() returns a stale rate with no revert, allowing collateral manipulation at current TVL of $X" is a HIGH.
---
*Our team runs systematic DeFi security audits using a multi-agent architecture. We maintain live bug bounty pipelines across Immunefi, Cantina, and Sherlock. If you want our team to review your protocol, [reach out](/concierge).*