New Smart Contract Weakness: Hash Collisions With Multiple Variable Length Arguments
Introduction
A new smart contract weakness was recently discovered by Steve Marx. As per the title of this article, the bug can result in a hash collision on functions with multiple variable-length arguments. Let’s take a look at an example to see the vulnerability in action.
As we can see in the contract, if the addUsers
function is called by an admin, arrays of admins and regularUsers
are added to mappings of isAdmin
and isRegularUser
. If the function is not called by an admin, it can be relayed with an admins signature.
Problem
The vulnerability can be found on line 15 with the use of abi.encodePacked()
. The problem lies in the way that abi.encodePacked()
manages its parameters. The following two statements return the same value, even though the parameters are unique.
Given that different parameters can return the same value, an attacker could exploit this by modifying the position of elements in a previous function call to effectively bypass authorization. For example, if an attacker saw addUser([addr1, addr2], [addr3, <attacker’s address>, addr4], sig)
, they could call addUser([addr1, addr2, addr3, <attacker’s address>], [addr4], sig)
. Since the return values are the same, the signature will still match, making the attacker an admin. Though the contract should have proper replay protection, an attacker can still bypass this by front-running.
Remediation
There are a few different remediation's we can take to prevent this vulnerability. The first option is to not allow arrays as parameters, instead of passing a single value. Another option would be to only allow arrays of fixed lengths, so the positions cannot be modified. Finally, we can avoid this vulnerability by simply opting for the use of abi.encode()
instead of abi.encodePacked()
.