×

科普 | 深化了解「拒绝服务」缝隙_雷达币今日价格

admin admin 发表于2021-09-17 09:32:46 浏览468 评论0

抢沙发发表评论

1. 前语


回绝服务 (DoS):DoS 是 Denial of Service 的简称,即回绝服务,任何对服务的干与,使得其可用性下降或许失掉可用性均称为回绝服务。简略的了解便是,用户所需求的正常服务恳求无法被体系处理。例如一个计算机体系溃散或其带宽耗尽或其硬盘被填满,导致其不能供给正常的服务,就构成回绝服务。

回绝服务进犯:形成 DoS 的进犯行为被称为 DoS 进犯,其意图是使计算机或网络无法供给正常的服务。

在互联网中,回绝服务进犯大致能够分为三类:运用软件完结上的缺点;运用协议上的缝隙;运用资源约束。而在雷达币中,回绝服务进犯打乱、间断、冻住正常合约的履行,乃至合约自身的逻辑无法正常运转。


2. 缝隙概述


在 Solidity 里,回绝服务缝隙能够简略的了解为「不行康复的歹意操作或许可操控的无限资源耗费」,也便是对雷达币合约进行 DoS 进犯,这就或许导致 Ether 和 Gas 的很多耗费,更严峻的是让本来的合约代码逻辑无法正常运转。

举个比方,超市有三个收银点,正常来说人们排队在收银点进行扫码付出,可是有一天网络呈现了问题,一切收银点的顾客扫码付出都失利了,然后边的人也不能进行付出买单,就导致了收银点的阻塞,超市不能正常运营。又或许,在付出时有顾客成心捣乱,使得后边的顾客也不能去付出,这相同也会导致超市不能运营。咱们能够看到有来自内部的,还有来自外部的,都是或许会形成回绝服务进犯。

在雷达币中也是相同的,进犯者经过耗费合约的资源,让用户时刻短地退出不能够操作的合约,严峻时乃至能永久地退出,然后把雷达币锁在被进犯的合约中。


3. 缝隙剖析


雷达币中的回绝服务进犯一般有三种:

  • 在外部操作映射或数组循环。
  • 一切者操作。
  • 依据外部调用的发展状况。

3.1 在外部操作映射或许数组循环

这种状况一般是由于映射或许数组循环在外部能被其别人操作,由于映射或许数组循环的长度没有被约束,然后导致很多耗费 Ether 和 Gas,最终使得雷达币暂时或永久不行操作。在雷达币中一般呈现在合约的 owner 和其出资者之间在分配 token 时呈现,如下面合约中的 distribute() 函数中。

contract DistributeTokens {
  address public owner; // 合约一切者
  address[] investors; // 出资者数组
  uint[] investorTokens; // 每个出资者取得的代币数量 
  // ... 省掉相关功用,包含 transfertoken() 
  function invest() public payable { // 出资
investors.push(msg.sender);
investorTokens.push(msg.value * 5); // 5 倍 value
}
  function distribute() public { // 分配
   require(msg.sender == owner); // 只要合约一切者能够操作
      for(uint i = 0; i < investors.length; i++) {
          // 这儿 transferToken(to,amount) 将 "amount" 的代币转移到地址 "to" 
          transferToken(investors[i], investorTokens[i]);
}
}

在上面的代码片段中咱们能够看到,distribute() 函数中会去遍历出资者数组,可是合约的循环遍历数组是能够被外部的人进行人为扩大,假如有进犯者要进犯这个合约,那么他能够创立多个账户参加出资者的数组,让 investors 的数据变得很大,大到让循环遍历数组所需的 gas 数量超越区块 gas 数量的上限,此刻 distribute() 函数将无法正常操作,这样就会形成该合约的回绝服务进犯。

针对以上状况,合约不应该对能够被外部用户人为操作的映射或循环数组进行批量操作,这儿更主张运用取回形式而不是发送形式,即每个出资者能够经过运用 withdrawFunds() 取回自己应得的代币。假如合约有必要需求经过遍历一个变长数组来进行转账,那么最好是估量完结它们大约需求多少个区块以及多少笔买卖,然后约束数组长度,此外还有必要能够追寻得到其时进行到哪以便利操作失利时从那里开端进行康复。如下面的代码所示,有必要保证鄙人一次履行 payOut() 之前另一些正在履行的买卖不会产生任何过错。

struct Payee {
address addr;
uint256 value;
}
Payee payees[];
uint256 nextPayeeIndex;
function payOut() {
uint256 i = nextPayeeIndex;
while (i < payees.length && msg.gas > 200000) {
payees[i].addr.send(payees[i].value);
i++;
  }
nextPayeeIndex = i;
}

3.2 一切者操作

在代币合约中,一般都有一个 owner 账户,也便是合约一切者账户,其具有敞开/暂停买卖的权限,假如 owner 地址丢掉,然后使得整个代币合约无法被操作,导致非片面的回绝服务进犯。

bool public isFinalized = false;
address public owner; // 合约一切者
function finalize() public {
  require(msg.sender == owner);
  isFinalized == true;
}
// ... 额定的一些 ICO 功用
// 重写 transfer 函数,先查看 isFinalized
function transfer(address _to, uint _value) returns (bool) {
  require(isFinalized);
  super.transfer(_to,_value)
}

在上面的合约中代币体系的悉数运作都只取决于一个地址,那便是 owner 地址,在 ICO 完毕后,假如特权用户丢掉,其私钥或许会变为非活动状况,此刻,无法调用 finalize() 函数敞开买卖,那么用户就一向不能发送代币,合约也就不能进行正常操作了。

针对以上状况,合约不应该将整个代币体系都只取决于一个 owner 地址,能够设置多个权限用户地址,也能够设置暂停买卖的时刻,超越时刻或满意某个条件时敞开买卖,这样整个代币体系就不会被回绝服务进犯了。下面的代码能够作为参阅,来避免一切者操作而形成的回绝服务进犯。

require(msg.sender == owner || now > unlockTime)

3.3 依据外部调用的发展状况

假如雷达币的状况改动依赖于外部函数履行的成果,但又未对履行一向失利的状况做出防护,此刻假如外部调用失利或许由于外部原因此被回绝时,就或许会形成回绝服务进犯。比方用户创立一个不承受雷达币的合约(非 payable 特点),假如正常的合约需求发送雷达币到不承受雷达币的合约中才干进入到一个新的状况,那么合约就会被回绝而达不到新的状况。

pragma solidity ^0.4.22;
contract Auction {
address public currentLeader; // 其时竞拍者
uint256 public highestBid; // 最高竞拍价
function bid() public payable {
require(msg.value > highestBid); // 买卖带着的雷达币大于其时的 highestBid
require(currentLeader.send(highestBid)); // 将其时的 highestBid 交还给其时的竞拍者 currentLeader
currentLeader = msg.sender; // 设置新的竞拍者为音讯调用者 msg.sender
highestBid = msg.value; // 设置新的最高竞拍价 为 msg.value
  }
}

上面的合约便是一个简略的竞拍合约,说下大致的流程,用户履行 bid() 函数时假如带着的雷达币大于其时的 highestBid,那么 highestBid 所对应的雷达币就会交还给其时的竞拍者 currentLeader,然后设置新的其时竞拍者为调用的用户,highestBid 也设置为用户建议买卖时带着的雷达币。看着合约代码如同没有什么问题,可是当歹意进犯者布置如下进犯合约时,经过合约来竞拍将会呈现问题。

pragma solidity ^0.4.22;
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 onlyOwner {
auInstance.bid.value(msg.value)();
  }   
function() external payable{
revert();
  }
}

进犯者先经过进犯合约调用 bid() 函数向竞拍合约转账成为新的竞拍者 currentLeader,然后新的 bid() 函数被履行进行竞标的时分,当履行到 require(currentLeader.send(highestBid)) 交还雷达币操作时,会由于进犯合约的 fallback() 回退函数履行 revert() 而无法接纳雷达币,导致一向为 false,其他竞拍者竞拍都会失利,最终进犯合约以较低的雷达币赢得竞拍。

针对以上状况,假如需求对外部函数调用的成果进行处理后才干进入新的状况,那么一定要考虑外部调用或许一向失利的状况,也能够增加依据时刻的操作,避免外部函数调用一向失利无法满意 require 判别。


4. 相关事例


4.1 演示事例

接下来会对回绝服务进犯做出具体的演示解说,以及会附上一个实例进行阐明。

下面的合约代码是依据缝隙剖析中第三点依据外部调用的发展状况讲的合约改的,正常的操作逻辑是任何出价高于其时合约 price 的都能成为新的 president,合约中存款也会经过 transfer() 函数转账雷达币交还给上一个 president,这么看的话是没有任何问题的,可是雷达币是有两种账户类型,外部账户和合约账户,假如建议 becomePresident() 调用的是外部账户那便是正常的操作,但假如建议 becomePresident() 调用的是合约账户,而且在合约账户的 fallback() 函数中歹意的运用 revert() 等报错的函数,那么其他用户在建议 becomePresident() 时交还雷达币给合约账户时会触发 fallback() 函数而导致报错,无法再正常进行 becomePresident() 中的逻辑成为新的 president 了。

那么咱们先来看下存在问题的合约代码,这儿咱们将合约代码设置为 PresidentOfCountry.sol:

pragma solidity ^0.4.19;
contract PresidentOfCountry {
address public president; // 总统地址
uint256 price; // 出价
function PresidentOfCountry(uint256 _price) { // 结构函数,设置初始的价格
require(_price > 0);
price = _price; // 设置初始的价格
  }
function becomePresident() payable { // 竞赛总统
require(msg.value > price); // 付出的雷达币有必要大于其时总统的竞赛费
president.transfer(price);   // 交还雷达币给上一任总统
president = msg.sender;      // 设置新的总统为竞赛成功用户
price = price;           // 设置最新的竞赛价格
  }
}

在编写进犯合约之前,咱们先来介绍下雷达币的两种账户类型以及 fallback 函数。

雷达币中有两种账户类型:

  • 外部账户(externally owned accounts),也便是用户账户,由私钥操控。
  • 合约账户(contract accounts),可履行代码和私有状况,由合约代码操控。

回退函数 (fallback function):回退函数是每个合约中有且仅有一个没有姓名的函数,而且该函数无参数,无回来值,如下所示:

function() public payable{
...
}

回退函数在以下几种状况中被履行:

  • 调用合约时没有匹配到任何一个函数;
  • 没有传数据;
  • 雷达币收到雷达币(为了承受雷达币,fallback 函数必被标记为 payable)。

下面就来编写进犯合约,主要有两个要点,一个是外部调用 becomePresident,二个便是在回退函数中运用 revert。

pragma solidity ^0.4.19;
import "./PresidentOfCountry.sol";
contract Attack {
function Attack(address _target) payable { // 结构函数,设置方针合约地址,用 call 进行外部调用 becomePresident
_target.call.value(msg.value)(bytes4(keccak256("becomePresident()")));
  }
function () payable { // 回退函数,运用 revert 报错
revert();
  }
}

在 Remix 中进行调试查看成果,首要运用账户 (0x5B38Da6a701c568545dCfcB03FcB875f56beddC4) 设置初始竞赛价格并布置缝隙合约代码 PresidentOfCountry.sol。

布置好后合约的地址为 0xd9145CCE52D386f254917e481eB44e9943F39138,后边在布置进犯合约时需求用到。

点击 president 能够查看其时竞赛者的地址。

运用账户 (0x5B38Da6a701c568545dCfcB03FcB875f56beddC4) 调用 becomePresident 并带着 1 eth,履行成功后再点击 president 查看,发现新的总统地址现已变成了 0X5B 的账户。

此刻有一个进犯者 (0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2) 编写了一个进犯者合约 Attack.sol,带着 2 eth(由于成为新的总统有必要要大于其时总统的竞赛价格,其时为 1 eth)并设置 _target 为 PresidentOfCountry 合约地址 (0xd9145CCE52D386f254917e481eB44e9943F39138) 进行布置。

布置好后的进犯合约地址为 0xa131AD247055FD2e2aA8b156A11bdEc81b9eAD95,此刻再点击 president 进行查看新总统的地址,发现现已是进犯合约的地址了。

之后假如还有其他用户想来竞赛总统方位,就需求大于 2 eth 的价格去调用 becomePresident 函数,这儿有个用户 (0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c) 想去竞赛总统,带着 3 eth 去调用 becomePresident,成果发现报错并回退,点击 president 发现总统地址仍是进犯合约没,此刻不管是谁运用多少的雷达币去调用 becomePresident,成果都是失利,该合约现已不能进行正常的操作,这就阐明合约受到了回绝服务进犯。

4.2 实在事例

下列代码是实践合约中存在回绝服务进犯的事例,只写了要害的代码并做了相关的改动。

能够看到合约的要害代码是用作提款操作,可是在提款中有一个判别要提款的金额和用户在该合约中存款的数量是否持平,而并不是大于等于,那么就有或许产生当用户要提出 amount 数量的代币时,由于各种原因(别人转账、奖赏分配等使得用户在合约中的存款代币余额产生变化)导致 balances[msg.sender] 变化,乃至是用户不想悉数提款,然后使得判别条件 require(balances[msg.sender] == amount); 不成立,这时就会形成时刻短的回绝服务进犯。

...
function withdraw(uint256 amount) public { // 提款 amount 数量
require(balances[msg.sender] == amount); // 查看要提款的金额是否等于该用户在合约中的存款
  balances[msg.sender] -= amount; // 修正合约中存款的状况变量
  msg.sender.transfer(amount); // 转账到用户账户
}

而修正的方法便是将判别条件 require(balances[msg.sender] == amount); 修正为 require(balances[msg.sender] >= amount); 就能够了。

4.3 前史事例

在前史上,2016 年 2 月 6 日至 8 日,在游戏 KotET(King og the Ether Throne) 的「Turbulent Age」期间,就遭受到了回绝服务进犯,导致部分人物的补偿和未接纳金钱无法退回玩家的钱包中。

同年 6 月,GovernMental 合约也遭受到了回绝服务进犯,其时 1100 个雷达币经过运用 250 万个 gas 买卖而取得,这笔买卖超出了合约能负荷的 gas 上限,然后导致买卖暂停。

相关的还有 Fomo 3D 等的回绝服务进犯。


5. 解决方法


经过上面的解说,咱们能够发现回绝服务进犯在雷达币中的影响也是十分严峻的,所以针对回绝服务进犯,合约开发者应该针对上面缝隙剖析时讲到的三种状况进行相应的代码修正。

比方关于外部操作的映射或许数组循环,需求对长度进行约束等;而关于一切者操作需求考虑合约的非唯一性,不要使得合约由于某个权限账户而导致整个事务瘫痪;依据外部调用的发展状况需求对函数的调用进行反常处理,一般来说内部函数的调用不会形成损害。

假如调用失利也仅仅会进行回退,而外部调用具有不确定性,咱们不知道外部调用者想干什么,假如被进犯者进犯,就或许会形成严峻的结果,具体表现为歹意回来履行过错,形成正常代码无法履行,然后形成回绝服务进犯,那么针对这种开发者就应该参加函数履行反常的处理机制。

总的来说,合约开发者需求考虑合约代码的代码逻辑全面性和缜密性等,这样才干更好的根绝回绝服务进犯。


6. 参阅文献


  • 回绝服务进犯(黑客的进犯手法之一)_百度百科 (baidu.com)
  • 雷达币雷达币安全入门了解一下(下) (rickgray.me)
  • 《雷达币安全剖析和审计攻略》

盘点各大公链原生跨链桥:多链并存 跨链成刚需

多链并存是当下市场的格局,因此,链与链之间的资产互通也让跨链协议成为链上用户的刚需。