By shedding light on various vulnerabilities and risks, the OpenZeppelin Security Report aims to dissect some of the key incidents and lessons learned for blockchain security researchers. In this edition, we will explore exploits from critical logic errors to governance hacks from April through June of 2023. This is a part of a continued effort to track, document, and share novel and recently uncovered vulnerabilities to elevate the security posture of the ecosystem.
Silo Finance's Logic Error Vulnerability
A bug disclosure took place within Silo Finance, which faced a critical logic error vulnerability. This flaw, if successfully exploited, could have allowed a malicious actor to manipulate the interest rate within the protocol. The exploit would have enabled the attacker to borrow much more than their collateral’s value, threatening the stability of the system.
Silo Finance creates permissionless and risk-isolated lending markets that use an isolated pool approach. This means every token asset has its own lending market, and is paired against ETH and XAI.
The vulnerability stemmed from the Base Silo contract, which is responsible for the core logic of the protocol. This contract is inherited by the Silo contract, which functions as a loan protocol, enabling users to submit collateral asset tokens through its deposit() function. In exchange, the contract creates a proportionate amount of tokens for the depositor, this being determined by the amount they deposited and the overall supply of the share. Following this, the contract refreshes the _assetStorage[_asset] storage state, reflecting the newly added deposit.
Users who deposit collateral into the contract can borrow other assets with the borrow() function. This function initially calculates the accrued interest rate of the asset borrowed, and then verifies whether there are enough tokens in the current contract for the user to borrow. Subsequently, it moves the tokens to the user’s account and reviews the loan-to-value ratio. We can see that the Base Silo contract is inherited (liquidity() function).
In essence, an attacker was able to manipulate the utilization rate of ERC-20 assets that had zero total deposits to the contract (or pools where just one of the tokens had zero deposits). The attack would work as follows:
- Deposit a small amount of asset (token A) that has 0 total deposits. This will lead to the attacker becoming the majority stakeholder for that particular asset, and make its totalDeposits() non-zero.
- Donate larger amounts of token A to the market, allowing other users to borrow more than the totalDeposits() for token A (total liquidity will exceed total deposits).
- Use another address to deposit an asset (token B), and borrow the donated token A.
- If accrueInterest is called in the next block, the interest will be artificially high due to the large borrowed amount against the much lower totalDeposits.
- This will cause the user’s initial deposit to have an artificially high value, enabling them to borrow much more than they are supposed to. This calculation is as follows:
This situation was swiftly and successfully managed. Thanks to the rapid response from a vigilant white-hat hacker (@kankodu) and the Silo team, no user funds were lost. The initial fix involved depositing assets to markets with 0 total deposits for one or more assets, but the protocol later implemented a permanent fix that involves ensuring the utilization rate never exceeds 100% and the maximum compounded interest rate does not surpass 10%. The fixes were then tested through formal verification.
It is important to always monitor statistically unusual values for variables and/or parameters, as these usually indicate that a potential exploit or market manipulation is taking place. Although patching this was a quick preventative fix for the issue, the final solution involved placing limits around certain values. As such, it is crucial to keep an eye out for outlier values in transactions.Sources:
Tornado Cash's Governance Hijack
Funds at risk: $275 Million
Amount of funds stolen: ~$750 thousand
In a rather shocking incident, Tornado Cash's governance system was hijacked. An attacker cleverly inserted a Trojan horse proposal that effectively transferred control of the Decentralized Autonomous Organization (DAO) to a single address. This intrusion demonstrated the potency of metamorphic smart contracts and highlighted the potential vulnerabilities in DAO governance models.
The attack involved passing a seemingly-benign proposal, similar to another one that had previously passed.
However, this proposal contained an extra function, which would later cause the entire system to be held hostage:
The proposal contract was initially rolled out through a deployer contract. The attacker could then exploit the deterministic deployment nature of the CREATE and CREATE2 opcodes to introduce new code into the address that had been approved by the governance. The newly launched malicious proposal drained the governance’s tokens and gave the exploiter 1.2M votes (granting them control over the DAO).
Unexpectedly, the attacker later decided to give control back to the DAO. They published a proposal to give control back. Perhaps the exploiter was more interested in selling off ~$750K worth of TORN rather than actually holding the governance hostage for an extended period.
This event serves as a stark reminder of the need for careful and thorough auditing of all smart contract components, especially those related to governance. It underlines the importance of keeping a watchful eye for certain functions in seemingly benign proposals, particularly whenever selfdestruct() is being used in conjunction with deployer contracts.
Yearn Finance Exploit
Funds stolen: $11 Million
On April 13, 2023, a deprecated version of the DeFi protocol iEarn Finance (a predecessor to Yearn Finance) suffered a Flash Loan Attack that resulted in losses exceeding $11M across various stablecoins. The attacker exploited a flaw in the yUSDT contract, which mistakenly depended on the iUSDC token instead of the iUSDT token. This bug had been lying dormant for over one thousand days before an attacker found a way to exploit it.
In the invocation flow below, we can see that the exploiter took a flash loan of 5M DAI, 5M USDC and 2M USDT (line 2). They then deposited the funds into the yUSDT contract (lines 5, 10 and 15).
The yUSDT contract is used to mint yUSDT tokens that represent USDT deposits in Yearn Finance. After redeeming yUSDT, the attacker withdrew all assets from the Aave V1 vault. They then minted bZxUSDC (line 5492) and sent it to the yUSDT contract, which increased the price of each share. This triggered a rebalance (line 5612), which led to the redemption of bZxUSDC into USDC, thus reducing the value per yUSDT to zero. They then transferred 1 wei of USDT (line 5669) to the yUSDT contract, enabling them to mint very large amounts of yUSDT.
These minted tokens were exchanged for profit through the Curve pool (lines 5691-5786) before repaying the borrowed flash loans (lines 6125-6130). The proceeds of the attack were then transferred to the attacker's addresses (lines 6133-6143).
As of this blog post’s writing, it seems that most of the funds were withdrawn from the attacker’s EOAs using Tornado Cash, mostly in increments of 100 ETH.
A stark reminder that code which has been live for years unexploited does not ensure safety and security to a protocol or its users. Continuous security audits and monitoring solutions are key to both deploying and maintaining codebases.
How to Almost Take Over any DNSSEC Name on ENS
The Ethereum Name Service is a decentralized domain name system built on the Ethereum blockchain. ENS aims to provide a similar mapping functionality as DNS but for Ethereum addresses, smart contracts, and decentralized applications on the Ethereum network.
With ENS, users can register and manage human-readable domain names ending in .eth that can be associated with their Ethereum addresses or other resources on the blockchain. This makes it easier for users to interact with decentralized services, send funds, and access decentralized applications using user-friendly names.
An interesting feature of ENS is that it allows users to integrate DNS names. The smart contracts of ENS can take in a set of signed records, verify them, and then allow the user to claim a DNS name on ENS. However, a critical vulnerability was discovered in the ENS smart contracts that would allow users to potentially claim someone else’s DNS names.
To claim a DNS name on ENS, users must call the proveAndClaim() function:
Following the trail of function calls, we can see that the _claim function is called and actually performs the claim of the DNS name (and logically, its validation).
The _claim() function checks that the DNS owner is the one claiming the name on ENS in line 158, by calling the getOwnerAddress() function. This verification process is conducted by checking that the DNS zone has a TXT record that indicates the Ethereum address that the owner wants to grant control of the ENS record to.
Looking into the getOwnerAddress() function, there is a buffer that is supposed to contain the name that is found by looking through the records. However, this buffer is left unused. This results in anyone who has a legitimate DNS record being able to claim any DNS record (the claimed name would not necessarily need to be the one associated with the proof).
This bug was patched before hitting the mainnet, so no ENS records were erroneously assigned. For more information on this bug’s discovery and disclosure, see this gist by @tinchoabbte.
Stay Up To Date
If you are interested in bug breakdowns like these and ecosystem news, follow OpenZeppelin on Twitter.
Learn from our recent Security Reports that have been published on our blog: