Guest Spotlight Article: How to analyze reports and become a great auditor

HatsFinance
9 min read2 days ago

--

The following content has been kindly guest contributed by bogo, as part of the Security Researcher Content Contributor Programme.

One of the best ways to improve your auditing skills and advance to the next level is through feedback from audits you did yourself. Specifically, I’m talking about the reports compiled once an audit concludes, whether it’s private or a competition.

Reviewing the findings you missed provides a valuable opportunity to enhance your auditor mindset by learning from others’ approaches and identifying your weak spots.

However, analyzing reports requires its own technique, much like auditing. To gain real value, it’s crucial to do it properly.

Think of reports as vaults filled with treasures. If you understand the mechanism for opening them, you’ll get to enjoy what’s inside.

This article is dedicated to helping you crack that mechanism. I’ll break down the process, providing examples and in-depth explanations of each step. By the end, you’ll have the tools for effective analysis.

Finding 1

Link

https://github.com/code-423n4/2023-10-wildcat-findings/issues/491

Found by

https://x.com/milotruck

Context

This finding comes from a contest I participated in, which focuses on a lending/borrowing protocol. The protocol uses a factory contract to deploy markets. These markets are smart contracts that facilitate the lending and borrowing process, each offering unique conditions such as fees, liquidation thresholds, token types, and more.

The Bug

It was a quite clever submission by MiloTruck that ended up as unique. The factory contract used Create2 to deploy each market contract and did not allow the same market to be deployed twice. The problem was in the check that the factory used to prevent subsequent deployments of the same contract.

It used .codehash to check if any code exists at the address. According to the protocol logic, if some code is deployed at the address, codehash should return the hash of that code; otherwise, it will be 0, meaning no code was deployed. The issue is that address.codehash is unreliable for determining if code exists at an address. If any ETH (1 wei) is sent to an address with no code, it starts returning keccak256(“”) instead of bytes32(0). This allows an attacker to send ETH to the address before a market is deployed to it, thus causing a denial-of-service (DoS) attack on the deployment.

Analysis

I’ve deliberately skipped giving out too many details so that we can use our brains and figure out how this auditor managed to uncover this exploit. This is a much better way to learn, trust me!

Asking the right questions is essential and will guide us. So, let’s start asking questions.

What is address.codehash ?

After a bit of googling this comes up

So, according to the definition, codehash returns the hash of the code at a particular address — if there is any code present. It also suggests that it can be used to determine if an address corresponds to a smart contract. However, from the bug described above, we already know this is not quite true, which leads us to our first conclusion: do not trust statements blindly; always verify them.

Let’s move on to the next question.

What did the auditor do to get that extra info about address.codehash ?

Obviously, he red the whole EIP till the end. Eventually you’ll end up here:

Conclusion number two — read specs thoroughly, devil is in the detail.

Last question

How did the auditor use that info?

He applied codehash to the check used above and began testing various scenarios. After a few iterations, he realized that sending Ethereum to an empty address would deceive the protocol into assuming that code had already been deployed.

Conclusion

  • Do not trust statements, even in docs — verify that they hold true
  • Research thoroughly, don’t limit yourself to surface-level understanding

Finding 2

Link https://github.com/hats-finance/Paladin-0x1610bfde27e57b068af7f38aec3d2a7b1d146989/issues/5

Found by

https://x.com/p_tsanev

Context

This is from the Paladin contest on Hats Finance platform. Paladin is a governance protocol that includes a contract called Loot. Loot manages user rewards from voting, distribution, and other incentivized activities within the protocol. It’s crucial to note that rewards are vested for a specific duration and can be claimed early, but they incur a penalty based on the remaining vesting period.

The Bug

As mentioned earlier, withdrawing rewards early results in the user receiving fewer rewards due to a portion being slashed. Additionally, there is a function called updateVestingDuration callable by the owner.

The bug occurs when the updateVestingDuration transaction is executed before a user’s full-reward (after the duration has passed) withdrawal transaction. This situation causes the user’s rewards to be slashed if updateVestingDuration extends the duration to a later timestamp.

Analysis

Given the nature of the bug, which requires thinking beyond the immediate protocol level and considering fundamental blockchain principles, let’s pose a question:

How could changes in a contract’s vesting duration affect the penalties incurred by users withdrawing their rewards early, particularly if the update to the vesting duration occurs before the user’s withdrawal transaction?

Indeed, the auditor likely didn’t uncover the bug just by staring at the contracts. He probably invested time in understanding the contract flows and then stepped back to look for potential weak points. The `duration` variable stood out as one such point, leading him to question what would happen if it changed before an ongoing withdrawal transaction was executed. This investigation likely led him to examine functions like `updateVestingDuration` to see if such changes were possible, eventually connecting the dots to discover the vulnerability.

It’s important to note that in this context, the owner is considered trusted, ruling out malicious actions like front-running by a trusted role, which is typically considered invalid in competitions. This leads us to the second question.

How did the auditor argumented a finding that looks invalid at first look ?

This is the other essential part of this finding. The auditor emphasizes that the issue is not due to malicious front-running of withdrawal transactions by the owner.

The problem arises because transactions in the mempool are not guaranteed to be executed in a specific order, causing the bug to occur naturally and outside the owner’s control. This underscores a crucial takeaway: using the right arguments and reasoning can make or break a finding.

Conclusion

  • Invest time in building a solid defense that will safeguard your finding from collapsing.

Finding 3

Link

https://github.com/code-423n4/2024-03-ondo-finance-findings/issues/278

Found by

https://x.com/0xBreeje

Context

This contest was relatively small, encompassing around 800 lines of code. It was an upgrade of the Ondo protocol, which deals with institutional-grade financial products. The protocol acts as a bridge between the web3 world and institutional financial markets, including US treasuries and bonds. The focus of this contest was on a new contract introduced by the protocol called OUSGInstantManager. This contract allows whitelisted users to deposit USDC and receive minted tokens of the protocol’s native token OUSG. The price of OUSG is determined by an oracle.

The Bug

The intriguing aspect of this bug is that it’s not immediately apparent; it requires a bit of abstract or out-of-the-box thinking to uncover. As described earlier, when users deposit USDC, an oracle fetches the OUSG/USDC price and mints the corresponding amount of OUSG tokens.

Explanation of the OUSG mint calculation:

  • price (fetched from the oracle) is the amount of USDC required to mint a single OUSG token
  • we divide the user deposited USDC by the price and get the OUSG tokens to mint

Even though the OUSG price from the oracle was correctly validated, the price of USDC itself was not. The protocol simply assumed that 1 USDC always equals 1 USD, which was not accurate. During USDC depeg events (e.g., when it drops below $1), depositors would still receive the same amount of OUSG tokens, effectively purchasing them at a discounted price.

Analysis

This one is tricky, because it’s not a typical logic error that you can deduce purely by looking at the code. It’s more of a conceptual bug that demands broader knowledge and abstract, outside-the-box thinking to uncover. Which is why it is useful to analyze it.

Let’s pose a question to delve deeper.

Was there a logical error in the code that would give out this bug?

If you look at the contract, there appears to be nothing wrong with it. The oracle had already been audited previously, and all best practices had been applied — staleness checks, acceptable price ranges, and price deviations were all implemented to ensure the validity of the OUSG price. Even if we were to run through the code a thousand times, we wouldn’t find anything wrong. So obviously, that’s not the issue here.

What allowed the auditor to uncover this bug then?

I can speculate a lot about this, but my best guess would be experience or a good habit of constantly staying updated on important events in the blockchain space. There are two ways you would have known that a stablecoin like USDC can depeg on its 1:1 ratio with USD:

  • either you red about it previously
  • or you had a similar experience from past contests, which prompted you to research whether USDC has depegged in the past.

Conclusion

  • Reading web3 resources and keeping yourself updated broadens your mindset and gives you a more abstract perspective
  • Same as with the first finding — never trust assumptions blindly, verify them

Finding 4

Link

https://github.com/code-423n4/2024-04-renzo-findings/issues/612

Found by

LessDupes team

Context

This issue is from the Renzo protocol contest. Understanding the intricate details of the protocol isn’t crucial here. What’s important to know is that users deposit assets and receive minted Renzo tokens. They can later retrieve their assets by returning these Renzo tokens. ETH is among the assets that can be deposited.

The Bug

The bug itself is basic and trivial — a low-level transfer with 2300 gas. However, what’s interesting is how the auditor elevated its severity from QA to a HIGH with only 1 duplicate.

*To clarify — the EVM initially introduced the low-level transfer with 2300 gas to prevent re-entrancy attacks. Over time, due to rising gas costs, this amount became insufficient, leading to potential denial-of-service (DoS) attacks even for simple ETH transfers.

This bug is so common that it typically gets flagged by bots at the start of every contest. In 95% of cases, a judge will mark such submissions as out of scope (OOS) because they were already reported by bots.

Analysis

Let’s analyze how a commonly flagged out-of-scope (OOS) bug was elevated to a nearly unique HIGH severity in a contest with a significant prize pool of 125K 🤑. First, let’s pose the question:

What specific factors or aspects did the auditor focus on to elevate the severity of a typically OOS bug to a HIGH level in this contest?

The following argument from his submission is the key to the answer:

Did you get it?

Let me clarify. Static analyzers aren’t inherently intelligent; they simply flag patterns they detect in a codebase. Especially with low-level transfers, they report instances found in contracts without delving into the specific context or usage details, resulting in a perception of low severity.

However, the auditor in this scenario analyzed the specific circumstances surrounding the transfer’s usage. By leveraging this detailed understanding, he constructed a compelling argument that uncovered the underlying danger:

Let’s connect the dots:

  • Due to the low gas being sent, safes like Gnosis cannot process the receiving of funds.
  • The claim() function requires the caller to be the same address that initiated the original withdrawal request via withdraw().
  • Consequently, funds become forever stuck because only the Gnosis safe that initiated the withdrawal can claim them (which it can’t).

Now, tell me you’re not convinced this qualifies for more than a QA issue.

Conclusion

A bug on its own might be worth nothing, but if you infuse it with solid arguments and proper context, it can shine bright. The takeaway here is to provide proper context to your submissions. As you’ve just seen, this can make the difference between $0.04 and $1,000.

And that’s a wrap! Hope you learned something today!

Follow Bogo on social media:
Twitter: https://x.com/xb0g0

Github: https://github.com/BogoCvetkov/portfolio

--

--

HatsFinance

Hats.Finance a decentralized smart bug bounty marketplace. Permissionless, scalable, and open bug bounty protocol that allows anyone to provide liquidity.