Started at 18:37:24 PM +UTC, Nov-21–2020, Pickle Finance was attacked by exploiting two bugs in the
ControllerV4 smart contract.
The hack results in draining all invested 19.76M DAIs under the StrategyCmpdDaiV2 management. Here we elaborate the technical details of these two bugs in this blog post.
Pickle is a yield-generating YFI-related DeFi protocol on Ethereum that allows users to deposit assets and earn yields. However, it has two bugs in the controller logic: The first one is input validation bug that fails to validate whether a given jar is supported or not; and the second one is arbitrary code execution that allows for external (untrusted) code execution in the context of the controller. The exploitation leads to drain all invested 19.76M DAIs under the StrategyCmpdDaiV2 management.
The Pickle Hack Walk-through
There are five steps involved:
- Step 1: Query current asset balance: The query via
StrategyCmpdDaiV2.getSuppliedUnleveraged()returns the asset balance of 19.72M DAIs
- Step 2: Exploit input validation bug to withdraw all DAIs from StrategyCmpdDaiV2 to Pickle-Jar: This bug is present in the
ControllerV4.swapExactJarForJar()with two given fake Jars. Without validating the given Jars, this step withdraws all invested DAIs back to Pickle-Jar for next-round of deployment.
- Step 3: Call
earn()to deploy withdrawn DAIs back to StrategyCmpdDaiV2. With the internal buffer mangement, the hacker calls
earn()three times, resulting in total 950,818,864.82119677 cDAIs minted to StrategyCmpdDaiV2.
earn() call invests 19.76M DAI and gets 903,390,845.43581639 cDAI minted
earn() call invests 988K DAI and gets 45,169,542.27179081 cDAI minted
earn() call invests 49K DAI and gets 2,258,477.11358954 cDAI minted
- Step 4: Exploit arbitrary code execution to withdraw all cDAIs from StrategyCmpdDaiV2 to Hacker: This step calls
ControllerV4.swapExactJarForJar()but with a different set of crafted arguments to trigger external code execution in the context of
ControllerV4. Specifically, the
_execute()call at line 316 is triggered to
delegatecallthe code located at
CurveProxyLogic.add_liquidity(), but with the following inputs:
underlying=0x8739c55df8ca529dce060ed43279ea2f2e122122. The first arguemnt to
CurveProxyLogic.add_liquidity()is typically the
curveaddress. However, it is miused to withdraw cDAI from
StrategyCmpdDaiV2.withdraw(). Since it is a
StrategyCmpdDaiV2.withdraw()is executed with
msg.sender == controller, which is unfortunate. In addition,
want != _assetin line 142 (
wantis DAI here) which enables the bad actor to
withdraw()cDAI. Now, the execution goes back to line 323 in
ControllerV4.swapExactJarForJar(), the withdrawn cDAI is then deposited into the malicious
_toJar.deposit(), all 950,818,864.8211968 cDAI are immediately transferred to the hacker address.
- Step 5: Redeem cDAIs and walk away with all 19.759M DAIs
The Stolen Funds:
The stolen funds from the above exploitations are currently held in this wallet: 0x2b0b. We are actively monitoring this wallet for any movement.