上溢(Overflow)和下溢(Underflow)
Solidity能处理256位的整数。所以 2²⁵⁶-1 加1就会为0.这个就是Overflow
0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + 0x000000000000000000000000000000000001 ---------------------------------------- = 0x000000000000000000000000000000000000
在Solidity中如果使用无符号整数,那么0减1就会得到最大的整数
0x000000000000000000000000000000000000 - 0x000000000000000000000000000000000001 ---------------------------------------- = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
对策是使用SafeMath库来做数学运算
Solidity可见性修饰符之差别
Public函数可以被任意调用(本合约的方法,被继承的合约方法,以及其他合约方法)
External函数不能被本合约方法调用
Private函数只能在本合约中被调用
Internal函数,稍微宽松一点,可以被本合约和继承合约的函数调用
Delegate Call是一个消息调用,需注意的是目标地址上的代码是运行在调用合约上下文当中的。这意味着调用的合约,可以在运行时动态的引入其他地址上的代码(模块化设计,对吧?),但是运行在调用合约的上下文中,以为着Storage, Balance, 和Current Address都是用的当前合约的
在下例中,攻击者可以通过在Delegation的上下文中调用Delegate合约中Public的PWN函数,从而获得合约的控制权
pragma solidity ^0.4.11;
// Credits to OpenZeppelin for this contract taken from the Ethernaut CTF
// https://ethernaut.zeppelin.solutions/level/0x68756ad5e1039e4f3b895cfaa16a3a79a5a73c59
contract Delegate {
address public owner;
function Delegate(address _owner) {
owner = _owner;
}
function pwn() {
owner = msg.sender;
}
}
contract Delegation {
address public owner;
Delegate delegate;
function Delegation(address _delegateAddress) {
delegate = Delegate(_delegateAddress);
owner = msg.sender;
}
// an attacker can call Delegate.pwn() in the context of Delegation
// this means that pwn() will modify the state of **Delegation** and not Delegate
// the result is that the attacker takes unauthorized ownership of the contract
function() {
if(delegate.delegatecall(msg.data)) {
this;
}
}
}
Parity Hack同时错用了修饰符和Delegate Call。一个可以修改合约控制权的函数被设为Public。这就给黑客开了后门:定制虚假的msg.data来获得合约控制权。
msg.data里是要调用函数的签名=sha3 (alias for keccak256)的头8个字节。
web3.sha3("pwn()").slice(0, 10) --> 0xdd365b8b
如果函数有一个参数, pwn(uint256 x):
web3.sha3("pwn(uint256)").slice(0,10) --> 0x35f4581b
重入/Reentrancy问题(THEDAO Hack)
在以下的代码中,Call函数会等待直到所有的Gas都是用掉了。但是Sender和真正扣钱是有时间差的。
这就好比,你走进银行,找柜员取现金1000RMB,你的账户正好有1000RMB. 所以第一次,柜员会给你1000RMB。在柜员还没有来的及操作从你的账户中扣1000RMB,你再问柜员要1000RMB,柜员一查,发现你的账户上有1000RMB(因为还没有来得及扣掉),柜员会又给你1000RMB
function withdraw(uint _amount) public {
if(balances[msg.sender] >= _amount) {
if(msg.sender.call.value(_amount)())
{ _amount; }
balances[msg.sender] -= _amount;
}
}
对策是:先扣钱,再送钱。另外一种方法是使用Mutex。
现在最好的方法是msg.sender.transfer(_value)
. 如果确实要使用send
,用require(msg.sender.send(_value));
以太坊智能合约 —— 最佳安全开发指南
参考: