Zapper Polygon Bridge post-mortem. Unlimited approvals are bad.
Here is a small story of one vulnerability that could lead to a disaster but luckily didn’t.
It turned out that over 30 contracts of Zapper.fi had a critical bug since November 2020. All of them used similar logic of external calls to exchanges like 0x. As a result, anyone who gave an allowance to spend ERC20 tokens to any of those contracts were in danger of losing tokens from their wallet at any time.
Over $100M of user funds exposed to that attack. Any user who has used Yearn zap or Zapper native functions at least once in the past 7 months could wake up one morning without any ERC20 tokens that were approved earlier for this interaction.
Luckily, all of the vulnerable contracts were fixed in 4 iterations of fixes during the past 3 months.
We independently found that issue; reported it to Zapper team and with their blessing exploited the last remaining contract with the bug to secure user funds.
Story
Recently, we’ve been developing a new project, and we were looking for a solution to zap user funds. During this research, we independently found a significant security breach in one of the Zapper.fi contracts on June 8th 2021. It turned out that this issue exists in the other 35 deployed contracts by Zapper from November 2020. However, most of the contracts were paused in mid-April and the end of May.
On June 10th 2021, white-hat hacker @lukash_dev reported the same issue in the newly deployed contracts that still had been there. Zapper.fi had paused the contracts and took a few days to release a post-mortem. Pausing the contracts was a solution, but sadly not the solution. Later, Zapper.fi released a post-mortem to disclose the vulnerability, so we decided to act.
The vulnerability in those 35 contracts potentially lets anyone call any method of any contract on behalf of Zapper.fi contracts. Therefore all wallets that had any ERC20 approvals for that contract turn out to be vulnerable. And any exploiter could transfer funds from user wallets to their own directly via specially engineered calls to Zapper.fi contracts.
Although Zapper team had paused most of the vulnerable contracts, one contract had been still there. Luckily, it was under the radar for most users. The one (due to an engineering error) lacked a “pausing” modifier for its Zap function. So, in contrast to all other Zapper contracts, this particular contract is unable to be paused.
The contract itself is an outdated Polygon bridge zapper
https://etherscan.io/address/0x1f0d1927498fbd4f9e8558704ce5b658929527ec
It has the modifier stopInEmergency
, however, it’s not used for ZapBridge.
And here is a line contains the vulnerability that is quite similar to all of the rest 35 contracts.
(bool success, ) = swapTarget.call.value(valueToSend)(swapCallData);
It allows anyone to call any contract with any data on behalf of Zapper’s contract. It was intended to be used to integrate with external exchanges like 0x or 1inch.
The problem there is that it is possible to set the address ofswapTarget
to any ERC20 token and encode a call to transferFrom(userWithAllowance, exploiterAddress, amount)
method into swapCallData
and it will go through. As a result, funds will be transferred to exploiterAddress
from the userWithAllowance
wallet, which gave an allowance to the vulnerable contract any time in past.
It was a huge risk to users’ funds, so we’ve decided to step in. The biggest problem with this broken contract is that there is no “right” solution to the vulnerability. Only performing your own attack.
By the time of the @lukash_dev disclosure, we had already developed two smart contracts for the exploit; we were also checking if a similar issue exists in other big projects so that our disclosure won’t potentially damage anyone else. (Luckily, most of the other contracts had been implemented safer, except one minor contract that we’ve reported to another team).
The first smart contract exploited the logic; took all funds from users with non-zero allowance for the vulnerable contract; and deposited it into the second contract: storage.
The second contract, the safe storage, allows to deposit user’s tokens, but it doesn’t allow to claim them until the user removes the ERC20 allowance to the vulnerable Zapper contract.
After development, we’ve reached Zapper, collaborated with their dev team, deployed contracts, and performed an attack. In the end, we’ve transferred ownership of the safe storage contract to the Zapper team.
We hadn’t used any private pool, and as a result, part of our transactions had been frontrunned by some bots. We apologize for that.
Luckily, the bot owners have contacted the Zapper developer team, and most of the funds appear to be restored. The rest will be covered by Zapper. No user will lose funds.
Overall ~$2.5mln worth of tokens were affected by the attack. Around $600k of them were frontrunned by a white-hat bot and should be returned soon.
We really want to thank Zapper for their rapid response and competence.
Next steps
All users who interacted with this particular contract should remove all their approvals to that contract as soon as possible.
Users that got their funds taken should remove approvals and claim their funds from storage at any time.
It is available via Zapper front-end.
It’s recommended for users to remove all approvals from all old Zapper contracts. Being those contracts accidentally unpaused, users are risking losing everything. (check the additional remark)
All currently working Zapper contracts are safe to use and vulnerable free in our opinion.
Additional remark
Right now, the other 34 Zapper contracts still have the same issue but have been paused. (Here’s the list of them)
It means that all users who have approved their tokens to Zapper might lose their funds if contracts become unpaused. Tens of millions of dollars worth of user tokens are at risk and can be taken in 3 minutes, in case Zapper deployer wallet become compromised, or someone unpauses vulnerable contracts by mistake. We urge the Zapper team to renounce the ownership of the vulnerable contracts, thus guaranteeing that those contracts will never be unpaused again.
The bug was there for over 7 months. During that time period, anyone could have take tokens from all users that interacted with those vulnerable contracts. Over $100M worth of tokens were at risk. Yearn.fi had integrated Zapper in March, and most people who used yearn zap function between March and April were susceptible to this exploit.
The bug was partially fixed in April, partially fixed in May and finally resolved in June. However, the time frame for the potential exploit was alarmingly wide. This shows how fragile DeFi is.
We urge all other developer teams that using similar logic (integration with external services like 0x, 1inch, paraswap) to pay close attention to their code and re-review it for potential problems.
Morale
The next big DeFi exploit may come from the fact that we, as users, carelessly leave many approvals for every contract that we have ever used. This time, the ecosystem managed to dodge the bullet. As a user, manage your approvals with more accuracy. Unlimited approvals have been rarely abused in the past. However, they might damage the ecosystem really hard in the future.
Stay tuned for some amazing news onwards.
AndreiKei , VV.
Noeon.finance