33.SWC-133_encodePacked
2023-07-13 15:56:54 # 09.SWC

SWC-133_encodePacked

  • vulnerability: Using abi.encodePacked() with multiple variable length arguments can, in certain situations, lead to a hash collision. Since abi.encodePacked() packs all elements in order regardless of whether they’re part of an array, you can move elements between arrays and, so long as all elements are in the same order, it will return the same encoding. In a signature verification situation, an attacker could exploit this by modifying the position of elements in a previous function call to effectively bypass authorization.

  • remediation: When using abi.encodePacked(), it’s crucial to ensure that a matching signature cannot be achieved using different parameters. To do so, either do not allow users access to parameters used in abi.encodePacked(), or use fixed length arrays. Alternatively, you can simply use abi.encode() instead.

vulnerable contract

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
pragma solidity ^0.5.0;

import "./ECDSA.sol";

contract AccessControl {
using ECDSA for bytes32;
mapping(address => bool) isAdmin;
mapping(address => bool) isRegularUser;
// Add admins and regular users.
function addUsers(
address[] calldata admins,
address[] calldata regularUsers,
bytes calldata signature
)
external
{
if (!isAdmin[msg.sender]) { // something problem
// Allow calls to be relayed with an admin's signature.
// 情景:项目方给了我admins和regularUsers的数组,admins和
// regularUsers中各2个数据,那么我就可以把regularUsers中的两
// 个数据放到admins中,这样也是可以通过的,因为拼接出来的hash是相同的
bytes32 hash = keccak256(abi.encodePacked(admins, regularUsers));
address signer = hash.toEthSignedMessageHash().recover(signature);
require(isAdmin[signer], "Only admins can add users.");
}
for (uint256 i = 0; i < admins.length; i++) {
isAdmin[admins[i]] = true;
}
for (uint256 i = 0; i < regularUsers.length; i++) {
isRegularUser[regularUsers[i]] = true;
}
}
}

fixed_1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
pragma solidity ^0.5.0;

import "./ECDSA.sol";

contract AccessControl {
using ECDSA for bytes32;
mapping(address => bool) isAdmin;
mapping(address => bool) isRegularUser;
// Add a single user, either an admin or regular user.
function addUser(
address user,
bool admin,
bytes calldata signature
)
external
{
if (!isAdmin[msg.sender]) {
// Allow calls to be relayed with an admin's signature.
// dynamic array increases the posibility of hash collisions,
// but this example is bad since hash collisions is not possible
// with 32 bytes.
bytes32 hash = keccak256(abi.encodePacked(user));
address signer = hash.toEthSignedMessageHash().recover(signature);
require(isAdmin[signer], "Only admins can add users.");
}
if (admin) {
isAdmin[user] = true;
} else {
isRegularUser[user] = true;
}
}
}

fixed_2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
pragma solidity ^0.5.0;

import "./ECDSA.sol";

contract AccessControl {
using ECDSA for bytes32;
mapping(address => bool) isAdmin;
mapping(address => bool) isRegularUser;
// Add admins and regular users.
function addUsers(
// Use fixed length arrays.
address[3] calldata admins,
address[3] calldata regularUsers,
bytes calldata signature
)
external
{
if (!isAdmin[msg.sender]) {
// Allow calls to be relayed with an admin's signature.
bytes32 hash = keccak256(abi.encodePacked(admins, regularUsers));
address signer = hash.toEthSignedMessageHash().recover(signature);
require(isAdmin[signer], "Only admins can add users.");
}
for (uint256 i = 0; i < admins.length; i++) {
isAdmin[admins[i]] = true;
}
for (uint256 i = 0; i < regularUsers.length; i++) {
isRegularUser[regularUsers[i]] = true;
}
}
}
Prev
2023-07-13 15:56:54 # 09.SWC
Next