“clones-with-immutable-args” is a sleek proxy pattern but…
What would a critical bug involving it look like?
@SpearbitDAO engaged @sudoswap V1 in 2022 for a security assessment and identified a critical bug in its’ contract pair validation process.
Let's break it down ⬇️
This vulnerability arises from the inadequate validation of smart contract clones in Sudoswap's "isPair()" function shown below.
The function performs the inadequate validation of contract clones via calls to the "isETH20PairClone()" and "isERC20PairClone" functions:
In "isETHPairClone()":
The `extcodecopy` function is used to get the bytecode of the queried contract. The first 54 bytes (`0x36` in hex) of the contract at the `query` address are copied to `other` which is located at `ptr + 0x40`:
The `result` is computed as the logical AND of the equality of two parts of the data. If the bytecode and the additional data stored in `ptr` match the corresponding data in `other`, `result` will be true, meaning that the queried contract is a valid clone:
What is wrong with this validation?
The vulnerability arises from the fact that the function is only checking the first `0x36` (or 54) bytes of the contract code. While this is intended to verify the 'signature' of the contract, it opens a potential attack vector.
The parameters provided in `cloneETHPair()` are directly fed into the contract.
If malicious values are supplied, these can bypass the validation process.
The cloned contract may appear legitimate but can possess a malicious payload.
Let's take a look at an example exploit ⬇️
First, we create a malicious pair by making a copy of the source of "cloneETHPair()" with malicious values for `factory`, `bondingCurve`, `nft` and `poolType` using a valid template for the connected contract and the proxy code.
However, the remaining parameters are malicious:
The attacker the initializes the new `Pair` via a copy of "initialize()" which calls "__Ownable_init()" thereby setting a malicious owner:
The malicious owner then calls the "call()" function, with the router as the target and the `calldata` for the "pairTransferERC20From()" function.
Since the checks for `onlyOwner` and `require` pass, "pairTransferERC20From()" is called with the malicious `Pair` as msg.sender:
The above function first gets the factory contract, checks if calling the target is allowed (it is because of insufficient check), and then calls the target. The router as the target contract will check if it is called from a valid pair via "isPair()":
Now, in the `pairTransferERC20From()` function, there is a check if it's being called from a valid pair by calling the `isPair()` function. The malicious pair passes this test due to the insufficient check of the first 54 bytes, allowing transfer of tokens.
Here, "isPair()" checks if the potential pair is an `enumerableETHTemplate` clone by comparing only the first 54 bytes (runtime code including implementation address).
As a result, it does not check for extra parameters `factory`, `bondingCurve`, `nft` or `poolType`:
Why is this critical?
Due to the malicious pair now being considered valid, the `require` statement in "pairTransferERC20From()" has passed and tokens can be transferred to the attacker from anyone who has set an allowance for the router.
This issue is now fixed.
Spearbit recommended Sudoswap to verify more values when checking if a pair is valid - especially the factory value and also recommend to consider the removal of all trust between pairs and routers, as well as the function "call()".
In the "isPair()" function within LSSVMPairFactory.sol the factory value is now passed into the validation functions as shown below in the following commit:
The changes in the validation code reflect an addition of the `factory` address parameter in the `isETHPairClone` and `isERC20PairClone` for functions for legitimacy and a subsequent modification in the assembly code for proper validation beyond 54 bytes of malicious parameters:
Previously, the `extcodecopy` function copied only the first 54 bytes of the contract's bytecode for comparison. Now, it copies the first 74 bytes, checking an additional 20 bytes of the bytecode to prevent an attacker from sneaking in a malicious payload.
As always, fantastic work to all involved. Keep up the great work! 🫡
This is only one of the many interesting vulnerabilities we have outlined in our report. You may view the full report located in our audits repository below:
Here's a thread of content geared towards security researchers that want something more beyond the standard auditor roadmap, top 10 vulns, or basic find-the-bugs.
Let's dive in 🤿🧵
Our approach to security reviews is to leave no stone unturned. We encourage everyone to do the same in this space.
Check out this breakdown of a critical finding based on a dependency our researchers identified:
Let's dive into Spearbit Tip #4 - Verification Patterns w/ @NoahMarconi
🧵⬇️
For blockchain developers, verification is a key aspect in the creation of secure products and can be applied in various ways, such as the verification of:
• Balances
• Transaction History
• State
• Actions
• Attestations
• and much more
Public functions within smart contracts expose all of these verification aspects. However, for advanced applications, developers often employ a set of unique verification patterns.
As part of the tip series, we will also be introducing tips for developers.
We believe education goes beyond just researchers and must extend to the core developer community to foster a security-first culture within our industry.
We will highlight the importance of proper code specification for developers and break down these key areas of focus before implementation:
1. Plain English 2. State Machines 3. Diagramming 4. Interfaces 5. Placeholder Tests 6. Formal Definitions (Optional)
Overview (2/2)
In common practice, stages one through five are iterated numerous times during the system design process. This iterative approach promotes efficiency relative to starting directly with implementation, and also aids in structuring a protocol for an efficient audit.
We will be introducing weekly tips for security researchers in order to support knowledge sharing across the web3 security ecosystem and continue raising the bar in our industry.
Let's begin.
🧵⬇️
When reviewing solidity code, oftentimes it can be helpful to take a peak under the hood to grasp a stronger understanding of what is occurring.
While this can be achieved through bytecode - it's much simpler to parse through optimized Yul assembly.
To illustrate this let's take a look at a simple function:
Spearbit researcher @StErMi, with guidance from @MorphoLabs, identified a 𝐜𝐫𝐢𝐭𝐢𝐜𝐚𝐥 bug in Morpho's implementation of Aave v3 and a lower risk 𝐧𝐨𝐯𝐞𝐥 bug in Aave v3 itself.
Let's break it down ⬇️🧵
𝐂𝐨𝐧𝐭𝐞𝐱𝐭
It is important to understand that:
Atokens where LTV=0 cannot be borrowed against for any amount.
As a result, Aave inherently restricts the usage of certain operations when Atokens with LTV=0 are used as collateral.
Let's say a standard user (A) of Aave has an Atoken called SpearToken (SPT) in their account.
We will also assume the following:
- They have an arbitrary amount of SPT
- SPT has LTV=0
- SPT is set as collateral
🎉 It's with great excitement that we announce our first-ever promotions within our community of security researchers at Spearbit!
Here's a story about how we got here 🧵
🗓️ In September 2022, we proposed a promotion path within our community of security researchers. We outlined four roles to display expertise in web3 security: JSR, ASR, SR, and LSR.
🔍 Junior Security Researcher (JSR): Someone with experience in web3 security and an IT background seeking to upskill through real-time audit experience by being paired with more advanced SRs and LSRs.