SWC-133/ 变长参数导致的哈希冲突
abi.encodePacked()在某些情况下,使用多个可变长度参数可能会导致哈希冲突。 由于abi.encodePacked()将所有元素按顺序打包,而不管它们是否属于数组的一部分, 因此可以在数组之间移动元素,并且只要所有元素的顺序相同,它将返回相同的编码。 在签名验证情况下,攻击者可以通过修改先前函数调用中元素的位置来有效利用绕过授权, 从而利用此漏洞。
CWE漏洞分类
整改方案
使用abi.encodePacked()时,至关重要的是要确保使用不同的参数无法获得匹配的签名。为此, 要么不允许用户访问在abi.encodePacked()中使用的参数,要么使用固定长度的数组。另外, 你也可以简单地使用abi.encode()。
此外,还建议使用重放保护(见SWC-121),虽然攻击者仍然可以利用提前交易绕过。
参考文献
示例合约
access_control.sol
/*
* @author: Steve Marx
*/
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]) {
// 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;
}
}
}
access_control.yaml
description: Access control contract without protection against multiple variable length argument hash collisions
issues:
- id: SWC-133
count: 1
locations:
- bytecode_offsets: {}
line_numbers:
access_control.sol: [23]
access_control_fixed_1.sol
/*
* @author: Steve Marx
* Modified by Kaden Zipfel
*/
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.
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;
}
}
}
access_control_fixed_1.yaml
description: Access control contract without protection against multiple variable length argument hash collisions
issues:
- id: SWC-133
count: 0
locations: []
access_control_fixed_2.sol
/*
* @author: Steve Marx
* Modified by Kaden Zipfel
*/
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;
}
}
}
access_control_fixed_2.yaml
description: Access control contract without protection against multiple variable length argument hash collisions
issues:
- id: SWC-133
count: 0
locations: []