Closed c4-submissions closed 1 year ago
141345 marked the issue as low quality report
141345 marked the issue as remove high or low quality report
141345 marked the issue as low quality report
141345 marked the issue as remove high or low quality report
141345 marked the issue as duplicate of #48
The vulnerability lies in the _reimburse function, where there is no source check, and msg.sender is not necessarily the initial owner of the delegate tokens
1155 has the record
141345 marked the issue as sufficient quality report
141345 marked the issue as not a duplicate
141345 marked the issue as duplicate of #314
hansfriese changed the severity to QA (Quality Assurance)
hansfriese marked the issue as grade-c
Lines of code
https://github.com/code-423n4/2023-10-ens/blob/ed25379c06e42c8218eb1e80e141412496950685/contracts/ERC20MultiDelegate.sol#L144-L149
Vulnerability details
Impact
The impact of this critical vulnerability is significant. The vulnerable code in the ERC20MultiDelegate contract allows malicious users to steal delegate tokens stored in all ERC20ProxyDelegator contracts, potentially leading to unauthorized token transfers and governance privilege escalation.
Following nickjohson message on Discord :
In a scenario involving a non-malicious token contract that interacts with the receiver contract on transfer, a user could exploit a vulnerability to use any $ENS tokens stored in ERC20ProxyDelegator contracts. In the worst-case scenario, if a sufficient number of tokens are using the ERC20MultiDelegate contract, malicious users could gain control of all governance privileges associated with these tokens.
Proof of Concept
The vulnerability lies in the
_reimburse
function, where there is no source check, andmsg.sender
is not necessarily the initial owner of the delegate tokens. Here is the vulnerable code snippet:In a standard case, this function reverts when tokens are burned, as
msg.sender
cannot burn tokens if he is not the owner of it. However, for example, a non-malicious ERC20Votes tokens can call the receiver after transfer to execute an "onReceive" function. For example, let's take the ENSToken.sol contract, and modifiy this function:The token implements the initial interface, and it is not malicious.
In this case, a malicious receiver can call the ERC20MultiDelegate contract on receive, minting tokens with the received ones to the initial target address. Upon returning to the
_reimburse
function, it will not revert becausemsg.sender
possesses tokens, allowing the malicious user to receive tokens, before giving them back. It looks like an undesired flash loan.Here is a PoC, done with the modification of ENSToken.sol above:
Without a malicious receiver, all tests are ok. Now let's create a malicious contract.
At the end of the
hack
function, the contract is the owner of tokens. Depending on the governance contract, the hacker can perform different attacks.Here is a modification of the delegatemulti.js test that can be used as a PoC.
It does not revert, showing that any user can perform a "flash loan" on $ens tokens using this MultiDelegate contract.
Tools Used
Manual, Hardhat tests
Recommended Mitigation Steps
To mitigate this vulnerability, it is recommended to implement a reentrancy guard within the delegateMulti function. This guard will prevent any other contract from calling the delegateMulti function before it completes its execution, thus protecting against reentrancy attacks.
I recommend using OpenZeppelin ReentrancyGuard.sol library.
Note for judge
This vulnerability has significant implications as the audit for this contract extends to "any non-malicious token contract that implements the interface". The provided Proof of Concept (PoC) serves as an illustrative example of a token that could be vulnerable. It's important to emphasize that any token implementing external contract calls can potentially be exposed to this vulnerability.
Assessed type
Reentrancy