Back to all stories
Blogs
Incident Analysis
Pike Finance Incident Analysis
5/22/2024
Pike Finance Incident Analysis

Incident Summary


On 26 April Pike Finance was exploited for $299k. The project announced via their X account that the root cause was due to a forged CCTP message which allowed the attacker to drain funds. As a result of the exploit they paused their contract which required it to be upgraded first.

Screenshot 2024-05-23 at 14.12.25

The upgrade caused a knock on effect and on 30 April 2024 Pike Finance was exploited for a second time with approximate losses of $1.68m. Due to the way in which delegatecall works with upgradeable contracts, the addition of the pause() and unpause() function caused a collision in storage. The contract was then able to be initialized for a second time by the attacker, giving them elevated access.

Exploit Transactions


Incident 1

https://arbiscan.io/tx/0x979ad9b7f5331ea8034305a83b5cd50aea88adec395fff8298dd90eb1b87667f

https://etherscan.io/tx/0x167c97b5897d11b23a6694f30acb71e8deead771d22fe88aee04ca8696a2bffa

https://optimistic.etherscan.io/tx/0x12770a7284c8c89f19489e125376dda48afa4d7662a1c5b32872c83adda890e2

Incident 2

https://arbiscan.io/tx/0xdac6af5695ba00b3d229574dbf7fcc326d16b9f8a52ad2620637d3022956d157

https://etherscan.io/tx/0xe2912b8bf34d561983f2ae95f34e33ecc7792a2905a3e317fcc98052bce

https://optimistic.etherscan.io/tx/0x6baa6332f9a3ed75e727311d6317fb636844d61d9df5e199f9f68711eb632d6f

Attack Flow


The Build Up

26 April: Pike Finance was exploited for ~$299k. The project paused the contract while they investigated and addressed the vulnerability. As the project didn't contain a pause function it needed to be upgraded first.

Pike2

When comparing the proxy contracts initial implementation (0x634683d7079af2EBeC84637BBC29dbD6FE817564) against the new upgrade (0xD167A1893e8F108572826dAbAe19663A9131b0c2), we can see the newly added Unpaused() and Pause() functions.

Screenshot 2024-05-23 at 14.12.56

The Second Exploit

30 April: Though the contract had remained paused, Pike Finance was exploited for ~$1.68m.

The attack flow is based on the following transaction. https://etherscan.io/tx/0xe2912b8bf34d561983f2ae95f34e33ecc7792a2905a3e317fcc98052bce66431

  1. The attacker called the initialize() function from proxy contract. Under normal circumstances this function is not callable more than once. The initialize() function set the isActive variable to the attacker’s address.

Pike4

  1. With the isActive role, the attacker could call the upgradeToAndCall() function and upgrade the proxy contract to their own malicious implementation.

Pike5

  1. The malicious upgrade allowed the attacker to withdraw 479.39 ETH from the the vulnerable contract.

Pike6

  1. The attacker repeated the same process on Arbitrum and Optimism chains.

Vulnerability


The vulnerability is due to complexities with using delegateCall and upgradeable proxy contracts. Delegatecall allows a contract to load code from a different address at runtime.

"Storage, current address and balance still refer to the calling contract, only the code is taken from the called address."

https://docs.soliditylang.org/en/v0.8.25/introduction-to-smart-contracts.html#delegatecall-and-libraries

Any change to the state by the called contract affects the calling contract state. If the state of the called contract isn’t identical to the calling contract, it can lead to incorrect behaviour. In the case of Pike Finance the state variable showing the contract was initialized returned false instead of true.

The Paused() and Unpaused() functions that were added in an upgrade after the initial incident return a boolean flag (true of false) which only occupies one bit of storage. This means they can fit and be included in a storage slot that isn’t using up its allocated space, creating a difference between the two contracts. This difference then causes the collision. In Pike Finance, the upgrade assigned 0 bytes to the initialization flag, erasing its state. Below is the state change from txn 0xf3f where the newly added unpause() function was called.

Pike7

The attacker could therefore call initialize() again as the contract believed it had never been initialized.

Pike8

Money Flow


Summary of Losses

(Approximate at time of exploit)

Incident 1

Arbitrum: $299,107

Incident 2

Arbitrum: $100,070.00

Ethereum: $1,433,977.23

Optimism: $150,458.95

Total: $1,983,633.18

The attack wallet from the first incident withdrew 1 BNB withdrawal from Tornado Cash on BSC before bridging a small amount to Arbitrum via Orbiter bridge to cover fees. After the exploit $299k was bridged from Arbitrum to Ethereum via Stargate bridge and deposited into Tornado Cash from the attacker’s wallet, 0xAdaF1626aEC26A7937aE7d1Fa0664e6E0904C1d0. After the Tornado deposits a small amount of left over ETH 0.043, too small to deposit to Tornado Cash, was sent to a Binance account.

In the second incident the attacker funded their Arbitrum wallet via Railgun which then bridged to Optimism. The exploited funds from both Arb and OP were then bridged to Ethereum via Li.Fi bridge before the exploit transaction on Ethereum was executed. 562 ETH was then transferred back to a Railgun wallet, at which point the funds are shielded, hiding their trail.

Conclusion


This incident is a rare case of a reaction to one exploit leading to the cause of a second exploit and highlights the cutthroat nature of web3. The issue exploited in the second incident is a widely known one but easy to overlook, especially when dealing with the pressure of having been exploited for $299k. The $1.68 million loss from the second exploit was the 5th largest incident we recorded in April.

To find out more about CertiK’s audit process and how we can help protect your smart contracts visit certik.com/products/smart-contract-audit or contact us via the social media listed on certik.com.