合约内容
pragma solidity ^0.7.0;
contract Vuln{
address public owner;
string public name = "Chain";
string public symbol = "CHA";
uint8 public decimals = 18;
uint public totalSupply=10000000000;
bool public isLoan=false;
bool public solved;
event Approval(address indexed from, address indexed to, uint number);
event Transfer(address indexed from, address indexed to, uint number);
event Deposit(address indexed to, uint number);
event Withdrawal(address indexed from, uint number);mapping (address => uint) public balanceOf; mapping (address => mapping (address => uint)) public allowance; constructor() public{ owner=msg.sender; balanceOf[owner]=totalSupply/2; balanceOf[address(this)]=totalSupply/2; } function withdraw(uint number) public { //取钱 require(balanceOf[msg.sender] >= number); balanceOf[msg.sender] -= number; (msg.sender).transfer(number); emit Withdrawal(msg.sender, number); } function approve(address to, uint number) public returns (bool) { //允许别人调用你的余额 allowance[msg.sender][to] = number; emit Approval(msg.sender, to, number); return true; } function transfer(address _to, uint _value) public returns (bool) { require(balanceOf[msg.sender] - _value >= 0); balanceOf[msg.sender] -= _value; balanceOf[_to] += _value; return true; } function fakeflashloan(uint256 value,address target,bytes memory data) public{ require(isLoan==false&&value>=0&&value<=1000); balanceOf[address(this)]-=value; balanceOf[target]+=value; address(target).call(data); isLoan=true; require(balanceOf[target]>=value); balanceOf[address(this)]+=value; balanceOf[target]-=value; isLoan=false; } function transferFrom(address from, address to, uint number) public returns (bool) { require(balanceOf[from] >= number); if (from != msg.sender && allowance[from][msg.sender] != 2**256-1) { require(allowance[from][msg.sender] >= number); allowance[from][msg.sender] -= number; } balanceOf[from] -= number; balanceOf[to] += number; emit Transfer(from, to, number); return true; } function isSolved() public returns(bool){ return solved; } function complete() public { require(balanceOf[msg.sender]>10000); require(allowance[address(this)][msg.sender]>10000); solved=true; }}
要求isSolved为true
我们会发现有问题的是fakeflashloan函数
Call函数
合约之间的调用有2种方式: 底层的call方式和 new 合约的方式
- call:通过合约ContractAddres.call(编码后的方法名和参数),返回调用是否成功,以及返回值data
合约之间的调用建议的方式是:通过new 合约,通过合约的方式去调用,而不是通过call的方式去调用,因为这样会失去控制权。
<address>.call(...) returns (bool)
call函数可以接受任何长度、任何类型的参数,其传入的参数会被填充至 32 字节最后拼接为一个字符串序列,由 EVM 解析执行。
在call函数调用的过程中,Solidity中的内置变量 msg 会随着调用的发起而改变,msg 保存了调用方的信息包括:调用发起的地址,交易金额,被调用函数字符序列等。
使用call函数进行跨合约的函数调用后,内置变量 msg 的值会修改为调用者,执行环境为被调用者的运行环境(合约的storage)。
call函数滥用漏洞说明
利用call函数滥用漏洞,可以执行 call注入 攻击。call注入 是一种新的攻击方式,原因是对call调用处理不当,配合一定的应用场景的一种攻击手段。
通常情况下,跨合约间的函数调用会使用call函数,由于call在相互调用过程中内置变量msg会随着调用方的改变而改变,这就成为了一个安全隐患,在特定的应用场景下将引发安全问题,被恶意攻击者利用,施行 call注入 攻击。
<address>.call(bytes)
call函数拥有极大的自由度:
- 对于一个指定合约地址的call调用,可以调用该合约下的任意函数
- 如果call调用的合约地址由用户指定,那么可以调用任意合约的任意函数
整数溢出漏洞
require(balanceOf[msg.sender] - _value >= 0); 会出现整数下溢 require(balanceOf[msg.sender]>10000); 可以通过整数溢出满足 allowance[from][msg.sender] -= number; 不可控的call调用满足
攻击合约
1.call注入攻击
address(target).call(data);
构造approve数据* 0x095ea7b3000000000000000000000000D5759D7b133E742D8D1C3784666d409385756b800000000000000000000000000000000000000000000000000000000000002711
0中间那串数据是自己的账户地址
执行fakeflashloan
target为合约地址 data为构造的数据
执行allowance发现成功了
执行complete
执行solved
问题到这里就解决啦
2.利用整数溢出攻击
溢出成功