During our journey in SphereX, we encountered the Chainswap hack and discovered an unexpected behavior. Chainswap, the Alameda backed “cross-chain hub for all ecosystems,” was hacked twice, resulting in a loss of over $5M in less than a week.
This post, regarding the second hack, highlights that what is visible on Etherscan does not necessarily reflect what is happening on-chain. While the source code for the verified implementation is correct, the call was delegated to a different, unverified contract. The supposed exploit is quite different from what was originally reported.
What are the odds of lightning striking twice? And what are the chances of a DeFi protocol being hacked twice within a week? This article delves into an untold story of the second Chainswap protocol hack, in which $4.4 million was stolen on July 11th, 2021, just a week after an $800k theft on July 2nd.
The Chainswap hack incident has been covered by numerous blog posts and articles. Rekt.news mentioned a “sloppy auth check,” while others referred to a critical bug with the same root cause, along with code snippets. In their official post mortem, Chainswap describe the second hack as follows:
“a bug in the token cross-chain quota code… due to a logical flaw in code, this led to an exploit by allowing invalid addresses which weren’t whitelisted to automatically increase the amount.”
Let’s try to locate the bug by examining one of the attacker’s transactions. The attacker sends the transaction to an Etherscan-verified proxy contract, invoking the receive() function. As per Etherscan:
“ABI for the implementation contract at 0x3c894caf21f18f42d8d06daf26983c4b6a32fc1c, likely using a custom proxy implementation.”
We did not identify any issues with the signature verification process. The verification process is correct, and the signing address is then passed to _decreaseAuthQuota(). It must have adequate quota; otherwise, it would revert when the quota is deducted:
The definition of authQuotaOf mapping:
Any function that increases the quota is protected by a permissioned modifier, either onlyFactory or onlyAuthorty [sic], code is audited, storage defaults to zero, quotas can only be increased by onlyFactory or onlyAuthorty, and a signer with no quota shouldn’t be able to withdraw anything. It appears to be safe.
However, when you try to query authQuotaOf() with any random address, you get a surprising answer. Rather than the default zero, you get 10²² for every random EOA address. (practically) Unlimited quota for everyone?! Let’s try to find out where this is coming from.
Tracing the transaction, we reach the point where the function productImplementations(bytes32) is invoked in the factory contract with the string “TokenMapped”. It is then delegated to the factory implementation, which returns the address of a closed-source contract.
The call is then delegated to that address with the original input parameters.
To summarize: Etherscan directs us to a verified and supposedly correct implementation, though eventually we’re directed to a closed-source contract.
Since we don’t have access to the source code from this point, we are navigating without a map. Our next step is to investigate the closed-source contract to find out what’s going on.
Based on the trace, the closed source contract calls the factory contract’s getConfig() five times using the input strings: “fee”, “feeTo”, “minSignatures”, “autoQuotaRatio” and “autoQuotaPeriod”. The first three strings appear in the original implementation contract code, whereas the latter two do not.
Looking for those two missing strings, we were able to find another “TokenMapped” verified contract, containing those strings. Here, authQuotaOf()is not a simple mapping anymore but a function that calls getConfig() in the factory contract with the config string “autoQuotaRatio”, and that’s where the 10²² value comes from.
This behavior corresponds with what we have observed on-chain, but we are unable to confirm if it is exactly the code we are looking for. decompiling the closed-source contract, indicated that the contracts are not identical.
This post is based on joint work with Yoav Weiss. We thank him for the fruitful collaboration!
Oren is a graduate of the Talpiot academic excellence program, and ex-8200 senior leadership. Oren has more than 20 years of experience in the cyber security domain, from R&D to leadership.