The MarginCallPrimaryFacet::flagShort()
is vulnerable to hijacking attacks. This vulnerability enables an attacker to hijack the right (flaggerId
) to liquidate Short positions flagged by a victim (hijacked flagger).
The attacker takes no risks (losing gas) for flagging any Short themselves, and no paying any gas fee or waiting delay (firstLiquidationTime
) is required.
The victim will no longer be eligible to liquidate their flagged Short positions, losing gas, time, and the opportunity to get rewards for free.
When executing the flagShort()
, a flagger can provide the flaggerHint
variable containing an inactive flaggerId
that the flagger wants to replace. This is a recycling mechanism for inactive flaggerIds
of the Ditto
protocol. The flagShort()
will execute the LibShortRecord::setFlagger()
to handle tasks regarding the setting of the flagger and re-use of the inactive flaggerId
(flaggerHint
).
However, the recycling mechanism of inactive flaggerIds
is vulnerable to hijacking attacks.
An attacker can create a brand new account (with no cost) or use the existing account if its g_flaggerId
state is 0 in order to force the setFlagger()
to initiate the recycling mechanism on the target flaggerId
(flaggerHint
) they want to hijack. Assuming that the victim (target flagger) has been flagged 10 Short positions and is waiting for the firstLiquidationTime
delay for liquidating them.
The attacker can front-run the victim to hijack the target flaggerId
due to the condition: "timeDiff > LibAsset.firstLiquidationTime(cusd)
" because immediately after the firstLiquidationTime
delay has passed, the setFlagger()
will reset the g_flaggerId
of the victim to 0. Then, the target flaggerId
(flaggerHint
) will be re-assigned to the attacker's g_flaggerId
instead.
Finally, the attacker can replace themselves to become a possessor of the hijacked flaggerId
(now the short.flaggerId == flaggerHint)
.
The flagShort() executes LibShortRecord::setFlagger()
: https://github.com/Cyfrin/2023-09-ditto/blob/a93b4276420a092913f43169a353a6198d3c21b9/contracts/facets/MarginCallPrimaryFacet.sol#L72
If the attacker's g_flaggerId == 0, the setFlagger() will either assign an inactive flaggerId (flaggerHint) or a brand new flaggerId to the attacker
: https://github.com/Cyfrin/2023-09-ditto/blob/a93b4276420a092913f43169a353a6198d3c21b9/contracts/libraries/LibShortRecord.sol#L386
The attacker can hijack the flaggerId (i.e., flaggerHint) of the target flagger due to the condition: "timeDiff > LibAsset.firstLiquidationTime(cusd)"
: https://github.com/Cyfrin/2023-09-ditto/blob/a93b4276420a092913f43169a353a6198d3c21b9/contracts/libraries/LibShortRecord.sol#L394-L396
The attacker replaces themselves to become a possessor of the stolen flaggerId (short.flaggerId == flaggerHint)
: https://github.com/Cyfrin/2023-09-ditto/blob/a93b4276420a092913f43169a353a6198d3c21b9/contracts/libraries/LibShortRecord.sol#L404
After successfully hijacking the target flaggerId
, the attacker can call the MarginCallPrimaryFacet::liquidate()
to liquidate all eligible Short positions previously flagged by the victim (hijacked flagger).
The attacker requires no waiting period for the firstLiquidationTime
delay or even paying any gas fee for flagging those Short positions. In this step, the MarginCallPrimaryFacet::_canLiquidate()
will be triggered to verify the liquidation eligibility of the attacker.
Since the attacker would already become the possessor of the hijacked flaggerId
, they are now eligible to liquidate all eligible Short positions previously flagged by the victim for free.
After successfully hijacking the target flaggerId, the attacker can call the liquidate() to liquidate all eligible Short positions that have previously been flagged by the victim (the hijacked flagger) without the need to wait for the firstLiquidationTime delay or even pay any gas fee for flagging them. In this step, the _canLiquidate() is triggered to verify the liquidation eligibility of the attacker
: https://github.com/Cyfrin/2023-09-ditto/blob/a93b4276420a092913f43169a353a6198d3c21b9/contracts/facets/MarginCallPrimaryFacet.sol#L118
Since the attacker would become the possessor of the stolen flaggerId, the attacker is now eligible to liquidate all eligible Short positions flagged by the victim
: https://github.com/Cyfrin/2023-09-ditto/blob/a93b4276420a092913f43169a353a6198d3c21b9/contracts/facets/MarginCallPrimaryFacet.sol#L393
This vulnerability enables the attacker to hijack the right (flaggerId
) to liquidate Short positions flagged by the victim. The attacker takes no risks (losing gas) for flagging any Short themselves, and no paying any gas fee or waiting delay (firstLiquidationTime
) is required.
The victim (hijacked flagger) will no longer be eligible to liquidate their flagged Short positions, losing gas, time, and the opportunity to get rewards for free.
This vulnerability can affect any flagger in the protocol, possibly influencing the disruption of the Ditto
protocol and its minted stable assets (e.g., cUSD).
Manual Review
To fix the vulnerability, adopt the secondLiquidationTime
delay instead of the firstLiquidationTime
, as shown in the snippet below.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.