上溢(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));
以太坊智能合約 —— 最佳安全開發指南
參考: