Vuln合约漏洞攻击

135 阅读3分钟

合约内容

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函数拥有极大的自由度:

  1. 对于一个指定合约地址的call调用,可以调用该合约下的任意函数
  2. 如果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

image.png

target为合约地址 data为构造的数据

执行allowance发现成功了

image.png

执行complete

执行solved

image.png

问题到这里就解决啦

2.利用整数溢出攻击

image.png

溢出成功

image.png