How I Saved a Company $300k by Responsibly Disclosing an IDOR Vulnerability
In April of 2025, Sherlock invited me to audit a client's codebase, within four hours I found the first critical vulnerability.
TLDR
In April of 2025, Sherlock invited me to audit a client's codebase, within four hours I found the first critical vulnerability. The next day, I found something worse one.
The issue allowed an attacker to abuse a fee-claiming function to access and transfer collateral without permission. In simple terms: instead of only withdrawing small amounts of earned fees, an attacker could make the protocol transfer the entire collateral position.
At the time of disclosure, the affected contract held over $300,000 in funds. Fortunately, the team reacted quickly and deployed a fix on the same day, resulting in no lost funds.
Table of Contents
- Start of the engagement
- Client Codebase
- The vulnerability
- How the exploit worked
- Why this was critical
- Conclusion
- About Us
- FAQ
Start of the engagement
In April of 2025, Sherlock invited me to audit a client's codebase.
Upon starting the review, it took me no more than four hours to find the first critical vulnerability. The issue was serious enough that the team started working on a fix at around 1 a.m during the night.
The next day, I found another vulnerability, which is the one I want to write about here.
The following gets fairly technical, but I’ll try to keep the terminology as high level as possible.
Without revealing the client's name, even though everything is public, the general product was a blockchain-based lending protocol. In simple terms, it allowed users to take out loans against a tokenized position they held on an exchange.
The interesting part was how that position was represented.
Client Codebase
The exchange position was represented using an NFT, or non-fungible token.
For non-crypto readers, an NFT in this context is basically just a unique data object on the blockchain. It allows to globaly track the ownership of something, this is done by mapping a unique ID to an owner. Read more: https://ethereum.org/de/developers/docs/standards/tokens/erc-721/
This matters because normal cryptocurrency tokens and NFTs are usually transferred differently.
Standard tokens such as USDC generally use one set of transfer functions, while NFTs use another. NFTs commonly require a function called transferFrom, which specifies who currently owns the asset and who it should be transferred to.
The exchange position in this case was represented as an NFT, but for some reason its implementation also supported normal transfer function, in with the destination and tokenID.
That unusual behavior became important later.
The client then used these NFT positions as collateral for loans.
The general flow was:
A user deposits their position into the lending protocol, gives up control over it while the loan is active, and receives USDC tokens in return.
As long as the loan exists, the protocol is supposed to control and protect that collateral.
The vulnerability
The protocol also implemented a function that allowed users to claim fees generated by their deposited position.
The intended behavior was relatively straightforward.
The position claim generate trading fees over time. The protocol would withdraw those fees, swap them into a dollar-denominated token such as USDC, and use the result to repay part of the user's loan.
The problem was that the swap logic trusted user-provided information about which tokens and pools should be used.
More importantly, it did not properly verify that the asset being transferred was actually a fee token rather than the collateral object itself.
This resulted in what can essentially be described as an IDOR: an Insecure Direct Object Reference.
The collateral was represented as an object with a specific ID, and the contract allowed an attacker to reference and transfer that object without verifying that they had permission to access it.
Instead of only allowing the function to transfer the small amount of fees generated by a position, it was possible to make it transfer the entire position.
In other words: instead of taking the interest, you could take the collateral.
How the exploit worked
There were several layers to making this exploitable.
The attacker could create their own trading pool containing two assets:
- the NFT contract representing the collateral positions
- USDC
Normally, putting NFTs into a pool designed for normal tokens would not make much sense.
However, because of the unusual transfer implementation of the NFT, the pool could interact with the NFT contract in a way that looked similar to interacting with a normal token.
The attacker could then manipulate the pool's balances so that its calculated exchange rate returned a very specific number.
This number could be made equal to the ID of an NFT currently held as collateral by the lending protocol.
For example, if the target collateral position had NFT ID 274, the attacker could construct the pool so that a swap involving an amount of 274 resulted in an output value of 274.
The original exploit required the surrounding balances to be prepared accordingly. The attacker could mint worthless NFTs, deposit them into the attacker-controlled pool, and add a tiny amount of USDC in order to manipulate the pool's calculation.
Once the pool returned the required values, the attacker could call the lending protocol's fee-claiming function.
Instead of providing normal fee tokens, the attacker supplied the address of the NFT contract itself.
Because the lending protocol did not sufficiently restrict what could be passed into the swap, it attempted to "swap" the NFT in the same way it would normally swap a token representing fees.
Due to the unusual NFT implementation, the amount passed into the transfer became the NFT's token ID.
The result was that the lending protocol transferred the actual collateral NFT into the attacker's pool.
The attacker could then recover the NFT from the pool.
At that point, the loan contract was left in a state where the collateral was simply missing.
Why this was critical
The final steps of the attack could be repeated.
An attacker could potentially remove collateral from the lending contract and then use that same collateral again elsewhere to borrow additional funds.
By executing the process repeatedly, it was possible to drain collateral from the vault.
At the time I disclosed the vulnerability, the affected contract held more than $300,000 worth of funds.
Fortunately, the team reacted extremely quickly.
The issue was disclosed, and a fix was deployed on the same day. No funds were lost.
Conclusion
This is also a good example of why getting a third-party opinion on critical code is so important.
The vulnerability wasn't necessarily hidden inside one obviously dangerous line of code. It appeared through the interaction between several different systems, assumptions, and implementation details.
Each component behaved in a way that was technically correct, but the combination created a critical exploit.
Four hours into the audit, we already had one critical vulnerability being fixed.
The next day, we found a way that could have drained more than $300,000 in collateral.
Fortunately, responsible disclosure and fast work by the team meant that no funds were lost.
About Us
At SC Audit Studio, we specialize in protocols security assessments. Our team of experts has worked with companies like Aave, 1Inch and several more to conduct security assessments. Partner with us to enhance your project's security and gain peace of mind.
Reach out to us for queries and security assessments!