Pickle Incident: Root Cause Analysis
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.
Summary
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.
Details
The Pickle Hack Walk-through
We started the analysis from the transaction behind the hack: 0xe72d…87b0. This hack is initialized from a malicious contract (located at 0x2b0b).
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 callsearn()
three times, resulting in total 950,818,864.82119677 cDAIs minted to StrategyCmpdDaiV2.
- 1st earn()
call invests 19.76M DAI and gets 903,390,845.43581639 cDAI minted
- 2nd earn()
call invests 988K DAI and gets 45,169,542.27179081 cDAI minted
- 3rd 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 ofControllerV4
. Specifically, the_execute()
call at line 316 is triggered todelegatecall
the code located atCurveProxyLogic.add_liquidity()
, but with the following inputs:curve=StrategyCmpdDaiV2
,curveFunctionSig=51cff8d9
,curvePoolSize=1
,curveUnderlyingIndex=0
,underlying=0x8739c55df8ca529dce060ed43279ea2f2e122122
. The first arguemnt toCurveProxyLogic.add_liquidity()
is typically thecurve
address. However, it is miused to withdraw cDAI fromStrategyCmpdDaiV2
back toControllerV4
by callingStrategyCmpdDaiV2.withdraw()
. Since it is adelegatecall
, theStrategyCmpdDaiV2.withdraw()
is executed withmsg.sender == controller
, which is unfortunate. In addition,StrategyCmpdDaiV2.withdraw()
only checkswant != _asset
in line 142 (want
is DAI here) which enables the bad actor towithdraw()
cDAI. Now, the execution goes back to line 323 inControllerV4.swapExactJarForJar()
, the withdrawn cDAI is then deposited into the malicious_toJar
. Inside_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.
About Us
PeckShield Inc. is an industry leading blockchain security company with the goal of elevating the security, privacy, and usability of the current blockchain ecosystem. For any business or media inquiries (including the need for smart contract auditing), please contact us at telegram, twitter, or email.