APP
中国版App下载 Android & iPhone
金色专栏
  • 发布文章
  • 发布活动

一文了解以太坊中智能合约攻击和漏洞

以太坊上的应用程序管理财务价值,使安全性变得绝对重要。作为一种新兴的、实验性的技术,智能合约当然也受到了相当多的攻击。

为了防止进一步的攻击,我列出了几乎所有已知的攻击和漏洞的列表。尽管此列表可能包含已知的攻击,但新的漏洞仍在定期发现,因此,这应该只是您作为工程师研究智能合约安全性的开始。

攻击

在本节中,我们将介绍可用于利用智能合约漏洞的已知攻击。

前端运行又称为事务排序依赖性

康科迪亚大学(University of Concordia)认为,“先行是一种行动,在此过程中,用户可以从预先访问有关即将发生的交易和交易的特权市场信息中受益”,对市场中未来事件的了解会导致剥削。

例如如果知道某个特定代币将要进行非常大的购买,那么坏的参与者可以提前购买该代币,并在超大的购买订单提高价格时出售该代币以获取利润。

前端攻击在金融市场长期以来一直是一个问题,由于区块链的透明性,这个问题在加密货币市场再次出现。

由于此问题的解决方案因合约而异,因此很难避免。可能的解决方案包括批量交易和使用预提交方案(即允许用户稍后提交详细信息)。

限制区块气体的DoS

在以太坊区块链中,所有区块都有气体限制。气体限制的好处之一是,它可以防止攻击者创建无限的事务循环,但是如果事务的气体使用量超过此限制,则事务将失败。 这可能以几种不同的方式导致DoS攻击。

无限操作

在这种情况下,区块气限额可能是一个问题,即向一系列地址发送资金。即使没有任何恶意,这也很容易出错。仅仅是因为有太多的用户需要付费,就可以最大限度地超出气体的限额,并阻止交易的成功。

这种情况也可能导致攻击。假设一个坏的参与者决定创建大量的地址,每个地址从智能合约中支付少量资金。如果有效地执行,则可以无限期地阻止事务,甚至可能阻止进一步的事务处理。

解决此问题的有效方法是在当前的推付式支付系统上使用预付式支付系统。为此,请将每笔付款分成自己的交易,然后让收款人调用该功能。

如果出于某种原因,您真的需要遍历一个未指定长度的数组,至少希望它可能占用多个区块,并允许它在多个事务中执行,如本例所示:

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;
}

区块填充

在某些情况下,即使您未遍历未指定长度的数组,您的智能合约也可能受到气体限制的攻击。攻击者可以通过使用足够高的气体价格来填充交易之前的几个区块。

这种攻击是通过以很高的气体价格发行几笔交易来完成的。如果气体价格足够高,并且交易消耗了足够的气体,它们就可以填满整个区块并阻止其他交易被处理。

以太坊交易要求发送者支付费以抑制垃圾交易攻击,但是在某些情况下,可以有足够的动机来进行此类攻击。例如在Dapp Fomo3D上使用了区块填充攻击。该应用程序具有倒数计时器,通过最后一次购买密钥,用户可以赢得大奖-除非用户每次购买钥密钥,计时器都会延长。攻击者购买了一把密钥,然后连续塞满了接下来的13个区块,这样他们才能赢得大奖。

为了防止此类攻击的发生,必须仔细考虑在应用程序中合并基于时间的操作是否安全。

撤回Dos

DoS(拒绝服务)攻击可能发生在函数中,当您尝试向用户发送资金时,该函数依赖于该资金转移是否成功。

如果资金被发送到一个由坏的参与者创建的智能合约中,这可能会有问题,因为他们可以简单地创建一个回退函数来还原所有付款。

例如:

// INSECURE
contract Auction {
    address currentLeader;
    uint highestBid;
    function bid() payable {
        require(msg.value > highestBid);
        require(currentLeader.send(highestBid)); 

// Refund the old leader, if it fails then revert
        currentLeader = msg.sender;
        highestBid = msg.value;
    }
}

如本例所示,如果攻击者通过具有回退函数的智能合约出价来还原所有付款,则它们将永远无法退款,因此,没有人可以提出更高的出价。

在没有攻击者在场的情况下,这也可能会带来问题。 例如您可能希望通过遍历数组来向用户支付费用,当然,您要确保为每个用户都支付了适当的费用。 这里的问题是,如果一次付款失败,该功能将被还原并且而没有人得到付款。

address[] private refundAddresses;
mapping (address => uint) public refunds;
// bad
function refundAll() public {
    for(uint x; x < refundAddresses.length; x++) { 

// arbitrary length iteration based on how many addresses participated
        require(refundAddresses[x].send(refunds[refundAddresses[x]])) 

// doubly bad, now a single failure on send will hold up all funds
    }
}

解决此问题的有效方法是在当前的推付式支付系统上使用预付式支付系统。 为此,请将每笔付款分成自己的交易,然后让收款人调用该功能。

contract auction {
    address highestBidder;
    uint highestBid;
    mapping(address => uint) refunds;
    function bid() payable external {
        require(msg.value >= highestBid);
        if (highestBidder != address(0)) {
            refunds[highestBidder] += highestBid; // record the refund that this user can claim
        }
        highestBidder = msg.sender;
        highestBid = msg.value;
    }
    function withdrawRefund() external {
        uint refund = refunds[msg.sender];
        refunds[msg.sender] = 0;
        (bool success, ) = msg.sender.call.value(refund)("");
        require(success);
    }
}

强制将以太坊发送给智能合约

有时候,用户不需要将以太坊发送到智能合约。不幸的是,在这种情况下,可以绕过智能合约回退函数并强行发送以太坊。

contract Vulnerable {
    function () payable {
        revert();
    }
    function somethingBad() {
        require(this.balance > 0);
        // Do something bad
    }
}

尽管似乎应该撤消与Vulnerable智能合约的任何交易,但实际上有两种方法可以强制发送Ether。

第一种方法是在以“易受攻击的合同”地址设置为受益人的合同上调用“selfdestruct”方法。 这是可行的,因为selfdestruct不会触发回退函数。

另一种方法是预先计算智能合约的地址,并在部署智能合约之前将以太坊发送到该地址。令人惊讶的是,这是可能实现的。

Griefing是一种经常在视频游戏中执行的攻击,恶意用户以一种意外的方式玩游戏,以打扰其他玩家,也就是trolling。此类攻击还用于阻止事务按预期执行。

可以对接受数据并在另一个智能合约的子调用中使用它的智能合约进行此攻击。此方法通常用于多签名钱包以及交易中继器中。如果子调用失败,则将还原整个事务或继续执行。

让我们以一个简单的中继智能合约为例。 如下所示,中继智能合约允许某人进行交易并签署交易,而不必执行交易。当用户无法支付与交易相关的气体时,通常会使用此函数。

contract Relayer {
    mapping (bytes => bool) executed;
    function relay(bytes _datapublic {
        // replay protection; do not call the same transaction twice
        require(executed[_data] == 0"Duplicate call");
        executed[_data] = true;
        innerContract.call(bytes4(keccak256("execute(bytes)")), _data);
    }
}

执行事务的用户(转发器)可以通过仅使用足以执行事务的气体而不是足够使子调用成功的气体来有效地审查事务。

有两种方法可以防止这种情况发生。第一种解决方案是仅允许受信任的用户中继事务。另一种解决方案是要求转运商提供足够的气体,如下所示。

// contract called by Relayer
contract Executor {
    function execute(bytes _data, uint _gasLimit{
        require(gasleft() >= _gasLimit);
        ...
    }
}

#### 可重入攻击

可重入性是一种攻击,当契约函数中的错误允许函数在本应禁止的情况下多次执行时,可能会发生这种攻击。如果恶意使用,这可以用来从智能合约中抽走资金。实际上,可重入性是DAO攻击中使用的攻击向量。

单函数可重入(Single-function reentrancy)

当易受攻击的函数与攻击者试图递归调用的函数相同时,就会发生单函数重入攻击。

// INSECURE
function withdraw() external {
    uint256 amount = balances[msg.sender];
    require(msg.sender.call.value(amount)());
    balances[msg.sender] = 0;
}

在这里,我们可以看到余额只有在资金转移后才被修改。这可以让黑客在余额设置为0之前多次调用该函数,有效地耗尽智能合约。

跨函数重入攻击

跨函数重入攻击是同一过程的更复杂版本。当易受攻击的功能与攻击者可以利用的功能共享状态时,就会发生跨函数重入攻击。

// INSECURE
function transfer(address to, uint amountexternal {
  if (balances[msg.sender] >= amount) {
    balances[to] += amount;
    balances[msg.sender] -= amount;
  }
}
function withdraw() external {
  uint256 amount = balances[msg.sender];
  require(msg.sender.call.value(amount)());
  balances[msg.sender] = 0;
}

在此示例中,黑客可以通过在fallout()函数中将余额设置为0之前具有回退函数调用transfer()来转移已用资金来利用此智能合约。

防止可重入攻击

在智能合约中转移资金时,请使用发送或转移而不是调用。使用调用的问题与其他函数不同,它没有2300的限制。这意味着可以在外部函数调用中使用该调用,该函数可用于执行重入攻击。

另一种可靠的预防方法是标记不受信任的功能。

function untrustedWithdraw() public {
  uint256 amount = balances[msg.sender];
  require(msg.sender.call.value(amount)());
  balances[msg.sender] = 0;
}

此外,为了获得最佳安全性,请使用“checks-effects-interactions”模式。这是智能合约函数的简单经验法则。

该函数应从checks开始-例如require和assert语句。

接下来,应执行智能合约的效力-例如状态修改。

最后,我们可以与其他智能合约进行交互-例如外部函数调用。

这种结构可有效防止重入,因为智能合约的修改状态将防止不良行为者执行恶意交互。

function withdraw() external {
  uint256 amount = balances[msg.sender];
  balances[msg.sender] = 0;
  require(msg.sender.call.value(amount)());
}

由于在执行任何交互操作之前将余额设置为0,因此如果递归调用智能合约,则在第一个事务之后将没有任何要发送的内容。

脆弱性

在本节中,我们将介绍已知的智能合约漏洞以及如何避免这些漏洞。此处列出的几乎所有漏洞都可以在智能合约弱点分类中找到。

整数上溢和下溢

实际上,整数类型具有最大值。 例如:

uint8 => 255

uint16 => 65535

uint24 => 16777215

uint256 =>(2 ^ 256)-1

当您超过最大值(溢出)或低于最小值(下溢)时,可能会发生溢出和下溢错误。当您超过最大值时,您将返回到零,而当您低于最小值时,它将使您返回到最大值。

由于较小的整数类型(如uint8,uint16等)具有较小的最大值,因此更容易引起溢出; 因此应谨慎使用它们。

可能的最佳解决溢出和下溢错误的方法是在执行数学运算时使用OpenZeppelin SafeMath库。

时间戳依赖性

现在或block.timestamp访问的块的时间戳可由矿工操作。 使用时间戳执行智能合约函数时,应考虑三个因素。

时间戳操纵

如果使用时间戳来尝试产生随机性,则矿工可以在区块验证后的15秒钟内发布时间戳,从而使他们能够将时间戳设置为一个值,从而增加使用该功能的几率。

例如彩票应用可以使用区块时间戳来选择组中的随机投标人。矿工可以进入彩票,然后将时间戳修改为一个值,使他们有更大的几率赢得彩票。

因此,不应将时间戳用于创建随机性。

5秒规则

以太坊的参考规范“ Yellow Paper”(黄皮书)没有规定可以改变多少区块的时间限制,它必须大于其父级的时间戳。话虽这么说,流行的协议实现会拒绝将来时间戳大于15秒的区块,因此只要您的时间相关事件可以安全地相差15秒,就可以安全地使用区块时间戳。

不要使用block.number作为时间戳

您可以使用block.number和平均区块时间来估计事件之间的时间差。 但是阻止时间可能会更改并破坏函数,因此最好避免这种用法。

通过tx.origin授权

tx.origin是Solidity中的全局变量,它返回发送事务的地址。重要的是您切勿使用tx.origin进行授权,因为另一个智能合约可以使用回退函数来调用您的智能合约并获得授权,因为授权地址存储在tx.origin中。 考虑以下示例:

pragma solidity >=0.5.0 <0.7.0;
// THIS CONTRACT CONTAINS A BUG - DO NOT USE
contract TxUserWallet {
    address owner;
    constructor() public {
        owner = msg.sender;
    }
    function transferTo(address payable dest, uint amountpublic {
        require(tx.origin == owner);
        dest.transfer(amount);
    }
}

在这里我们可以看到TxUserWallet智能合约使用tx.origin授权transferTo()函数。

pragma solidity >=0.5.0 <0.7.0;
interface TxUserWallet {
    function transferTo(address payable dest, uint amount) external;
}
contract TxAttackWallet {
    address payable owner;
    constructor() public {
        owner = msg.sender;
    }
    function() external {
        TxUserWallet(msg.sender).transferTo(owner, msg.sender.balance);
    }
}

现在如果有人诱骗您将以太坊发送至TxAttackWallet智能合约地址,他们可以通过检查tx.origin来查找发送交易的地址来窃取您的资金。

为了防止这种攻击,请使用msg.sender进行授权。

浮动编译器

最好选择一个编译器版本并坚持使用它。使用浮动编译器时,可能会使用过时或有问题的编译器版本意外地部署智能合约,这可能会导致错误,从而使智能合约的安全性受到威胁。对于开源项目,该实用程序还会告诉开发人员在部署您的智能合约时要使用哪个版本。所选的编译器版本应经过全面测试,并考虑是否存在已知错误。

对于库和包,可以使用浮动编译指示例外。 否则开发人员将需要手动更新编译指示以在本地编译。

函数默认可见性

可以将功能可见性指定为public,private,internal或external。重要的是要考虑哪种可视性最适合您的智能合约函数。

许多智能合约攻击是由开发人员忘记或放弃使用可见性修饰符引起的。 然后默认情况下将该函数设置为public,这可能导致意外状态更改。

过时的编译器版本

开发人员通常会在现有软件中发现错误和漏洞并进行修补。 因此尽可能使用最新的编译器版本很重要。

未检查的调用返回值

如果未检查低级调用的返回值,则即使函数调用抛出错误,也可能继续执行。这可能导致意外行为并破坏程序逻辑。失败的调用甚至可能是由攻击者引起的,攻击者可以进一步利用应用程序进行攻击。

在Solidity中,您可以使用低级调用,例如address.call(),address.callcode(),address.delegatecall()和address.send(),也可以使用智能合约调用,例如ExternalContract.doSomething( )。低级调用永远不会引发异常-相反,如果遇到异常,它们将返回false,而智能合约调用将自动引发。

如果您使用低级调用,请确保检查返回值以处理可能的失败调用。

无保护的以太坊提款

如果没有足够的访问控制,不良行为者可能能够从智能合约中撤出部分或全部以太坊。这可能是由于错误地命名了要用作构造函数的函数,从而使任何人都可以重新初始化智能合约。为了避免此漏洞,请仅允许授权或按预期的方式触发提款,并适当命名您的构造函数。

无保护的自毁指令

在具有自毁方法的智能合约中,如果缺少访问控制或访问控制不足,恶意行为者可以自毁智能合约。重要的是要考虑自毁功能是否绝对必要。如有必要,请考虑使用多重签名授权来防止攻击。

 在Parity攻击中使用了此攻击。一位匿名用户定位并利用了“库”智能合约中的漏洞,从而使自己成为智能合约的所有者。 然后攻击者开始自毁智能合约。这导致资金被冻结在587个唯一的钱包中,总共持有513,774.16个以太坊。

状态变量默认可见性

开发人员通常会明确声明函数可见性,而声明变量可见性并不常见。状态变量可以具有三个可见性标识符之一:public,private或internal。幸运的是,变量的默认可见性是内部的,而不是public的,但是即使您打算将变量声明为internal的,也必须明确,因此对于谁可以访问该变量没有错误的假设。

未初始化的存储指针

数据作为存储,内存或调用数据存储在EVM中。 理解并正确初始化这两者很重要。 错误地初始化数据存储指针,或者只是不进行初始化就可能导致智能合约漏洞。

断言assert

从Solidity 0.5.0开始,未初始化的存储指针不再是问题,因为与未初始化的存储指针的协定将不再编译。 话虽如此,了解在某些情况下应该使用哪些存储指针仍然很重要。

在Solidity 0.4.10中,创建了以下函数:assert(),require()和revert()。 在这里,我们将讨论assert函数以及如何使用它。

正式地说,assert()函数用于声明不变量;非正式地说,assert()是一个过分自信的保镖,可以保护您的智能合约,但会在过程中窃取您的气体。正常运行的智能合约永远不应到达失败的assert声明。如果到达了失败的assert语句,则说明您使用了assert()的方式不正确,或者智能合约中存在将其置于无效状态的错误。

如果在assert()中检查的条件实际上不是不变的,则建议您将其替换为require()语句。

使用过时的函数

随着时间的流逝,Solidity中的函数已被弃用,并经常被更好的函数所取代。不要使用过时的函数,这很重要,因为它可能导致意外的效果和编译错误。

下面是一个不推荐使用的函数和替代项的列表。许多替代品都是简单的别名,如果用作不推荐使用的替代品,则不会破坏当前行为。

委托给不受信任的调用者

Delegatecall是消息调用的一种特殊变体。它几乎与常规消息调用相同,只是目标地址在调用协定的上下文中执行,msg.sender和msg.value保持不变。实际上,delegatecall委托其他智能合约修改调用智能合约的存储。

由于delegatecall提供了对智能合约的如此多的控制权,因此只将其用于可信的智能合约(比如您自己的智能合约)是非常重要的。如果目标地址来自用户输入,请确保它是受信任的协定。

签名延展性

人们通常会假设在智能合约中使用加密签名系统会验证签名是否唯一; 但是事实并非如此。在以太坊中的签名可以在没有私钥的情况下进行更改并保持有效。 例如椭圆密钥密码术由三个变量v,r和s组成,如果以正确的方式修改了这些值,则可以获得带有无效私钥的有效签名。

为避免签名可延展性的问题,切勿在签名消息哈希中使用签名来检查智能合约是否已处理了先前签名的消息,因为恶意用户可以找到并重新创建您的签名。

构造函数名称不正确

在Solidity 0.4.22之前,定义构造函数的唯一方法是使用智能合约名称创建函数。在某些情况下,这是有问题的。 例如如果智能合约以不同的名称重复使用,但构造函数也未更改,则它将变成常规的可调用函数。

现在,使用Solidity的现代版本,您可以使用Constructor关键字定义构造函数,从而有效弃用此漏洞。 因此,解决此问题的方法只是使用现代的Solidity编译器版本。

隐藏状态变量

在Solidity中可以两次使用相同的变量,但可能会导致意外的副作用。 对于使用多个智能合约,这尤其困难。 请看以下示例:

contract SuperContract {
  uint a = 1;
}
contract SubContract is SuperContract {
  uint a = 2;
}

在这里,我们可以看到SubContract继承了SuperContract,并且变量a被两次定义为不同的值。 现在,假设我们使用a在SubContract中执行某些功能。 由于已修改a的值,因此从SuperContract继承的功能将不再起作用。

为避免此漏洞,重要的是我们检查整个智能合约系统是否存在歧义。检查编译器警告也很重要,因为只要它们在智能合约中,它们就可以标记这些歧义。

区块链属性的随机性来源较弱

在以太坊中,某些应用程序依赖于随机数的生成来保持公平。但是在以太坊中,随机数的生成非常困难,并且有一些陷阱值得考虑。

使用诸如block.timestamp,blockhash和block.difficulty之类的链属性似乎是个好主意,因为它们通常会产生伪随机值。然而,问题在于矿工修改这些值的能力。 例如在具有数百万美元大奖的赌博应用中,矿工有足够的动力去生成许多替代区块,只选择会导致矿工中奖的区块。当然,要像这样控制区块链会付出巨大的代价,但是如果赌注足够高,那肯定可以做到。

为了避免在随机数生成中操纵矿工,有一些解决方案:

1. 承诺方案,例如RANDAO,DAO,其中随机数由DAO中的所有参与者生成。

2. 通过oracle的外部来源-例如Oraclize。

3. 使用比特币区块哈希,因为网络更加分散,区块的开采成本更高。

缺少针对签名重放攻击的保护

有时在智能合约中,有必要执行签名验证以提高可用性和气体的成本。但是在实施签名验证时需要考虑。

为了防止签名重放攻击,智能合约应仅允许处理新的哈希。这样可以防止恶意用户多次重播另一个用户的签名。

为了更加安全地进行签名验证,请遵循以下建议:

  • 存储智能合约处理的每个消息哈希,然后在执行功能之前对照现有哈希检查消息哈希。

  • 在哈希中包括合同的地址,以确保消息仅在单个合同中使用。

  • 切勿生成包含签名的消息哈希。

违反条例

equire()方法用于验证条件,例如输入或智能合约状态变量,或验证来自外部智能合约调用的返回值。 为了验证外部调用,可以由调用者提供输入,也可以由被调用返回输入。如果被调用方的返回值发生输入冲突,则可能是以下两种情况之一出了问题:

  • 提供输入的合同中有一个bug。

  • 要求条件太强。

要解决此问题,首先要考虑需求条件是否太强。如有必要,请减弱它以允许任何有效的外部输入。如果问题不是必需条件,则智能合约中必须有提供外部输入的错误。确保此智能合约未提供无效输入。

写入任意存储位置

只有授权地址才能访问敏感存储位置。如果整个智能合约中没有适当的授权检查,则恶意用户可能会覆盖敏感数据。但是即使存在用于写入敏感数据的授权检查,攻击者仍可能能够通过不敏感数据覆盖敏感数据。 这可能使攻击者可以覆盖重要的变量,例如智能合约所有者。

为了防止这种情况的发生,我们不仅要保护具有授权要求的敏感数据存储,而且还要确保对一个数据结构的写入不会无意间覆盖另一数据结构的条目。

继承顺序不正确

在Solidity中,可以从多个来源继承,如果不能正确理解,则可能会引起歧义。这种歧义被称为钻石问题:如果两个基本智能合约具有相同的函数,那么哪个优先? 幸运的是,只要开发人员了解解决方案,Solidity就可以很好地处理此问题。

Solidity为钻石问题提供的解决方案是使用反向C3线性化。这意味着它将使继承从右到左线性化,因此继承的顺序很重要。建议从更一般的智能合约开始,再到更具体的智能合约结束,以避免出现问题。

具有函数类型变量的任意跳转

Solidity支持函数类型。这意味着可以将类型为function的变量分配给具有匹配签名的函数。然后可以像其他任何函数一样从变量中调用该函数。用户不应更改函数变量,但是在某些情况下,这是可能的。

如果智能合约使用某些汇编指令(例如mstore),则攻击者可能能够将函数变量指向任何其他函数。这可能使攻击者能够破坏智能合约的函数,甚至可能耗尽智能合约资金。

由于内联汇编是从底层访问EVM的一种方式,因此它绕过了许多重要的安全功能。 因此,只有在必要且正确理解的情况下,才使用汇编程序。

存在未使用的变量

尽管允许,但最好的做法是避免使用未使用的变量。 未使用的变量会导致一些不同的问题:

  • 计算量增加(不必要的气体消耗)

  • 错误或数据结构错误的指示

  • 代码可读性降低

强烈建议从代码库中删除所有未使用的变量。

意外的以太坊余额

由于始终可以将以太坊发送到智能合约中(请参阅“强行将以太币发送到智能合约”)-如果智能合约具有特定的余额,则很容易受到攻击。

假设我们有一个智能合约,如果智能合约中存储了任何以太坊,则该智能合约将阻止所有函数执行。如果恶意用户决定通过强行发送Ether来利用此漏洞,则将引发DoS,使智能合约无法使用。 因此请勿对智能合约中的以太坊余额使用严格的平等检查,这一点很重要。

以太坊智能合约代码始终可以被读取。即使您的代码未在Etherscan上进行验证,攻击者仍然可以反编译甚至检查与它之间的事务以进行分析。

这里的一个问题示例是猜谜游戏,用户必须猜测所存储的私有变量才能赢得合同中的以太坊。当然这是极其琐碎的利用(要点是您不应该尝试,因为它几乎可以肯定是蜜罐合约,要复杂得多)。

这里的另一个常见问题是在Oracle调用中使用未加密的链下机密(例如API密钥)。如果可以确定您的API密钥,恶意行为者可以简单地自己使用它或利用其他媒介,例如用尽您允许的API调用并强迫Oracle返回错误页面,这可能会或可能不会导致问题,具体取决于智能合约的结构。

检测智能合约中错误

有些智能合约不希望其他智能合约与之交互。防止这种情况的常见方法是检查主叫帐户中是否存储了任何代码。但是智能合约帐户在构建过程中发起调用仍不会显示它们存储代码,从而有效地绕过了智能合约检测。

非封闭区块链依赖

许多智能合约依赖于在一定时间内发生的调用,但以太坊可以在相当长的时间内以相对便宜的价格通过非常高的Gwei交易进行垃圾邮件发送。

如Fomo3D(倒数游戏,最后一位投资者赢得了头奖,但每项投资都增加了倒计时的时间)是由一个用户赢得的,该用户在短时间内完全阻塞了区块链,不允许其他人在定时器运行之前进行投资出局,他赢了得了比赛。

如今有许多经纪人赌博合同依靠过去的哈希来提供RNG。在大多数情况下,这不是可怕的RNG来源,甚至可以解释256个区块后发生的哈希删除。但是到那时,他们中的许多人根本就没有下注。这将使某人可以对许多这些功能相似的智能合约下注,并以一定的结果作为所有人的赢家,在主持人仍未决的情况下检查主持人的提交,并且如果不利,只需阻塞区块链,直到进行修剪即可,得到他们的赌注。

不遵守标准

在智能合约开发方面,遵循标准很重要。设置标准是为了防止漏洞,而忽略这些漏洞可能会导致意想不到的后果。

以Binance的原始BNB令牌为例。它以ERC20代币的形式销售,但后来指出它实际上不符合ERC-20的原因有以下几个:

  • 它阻止发送到0x0

  • 它阻止了0值的传输

  • 它没有为成功或失败返回true或false

实施这种不当行为的主要原因是,如果将其与期望使用ERC-20令牌的智能合约一起使用,则会表现出意想不到的效果。它甚至可能永远被锁定在智能合约中。

尽管标准并不总是很完美,有一天可能会过时,但它们可以培育出最安全的智能合约。

结论

如您所见,可以通过多种方式利用智能合约。 在构建之前,您必须充分了解每种攻击媒介和漏洞,这一点至关重要。

文章作者: / 责任编辑: 我要纠错

声明:本文由入驻金色财经的作者撰写,观点仅代表作者本人,绝不代表金色财经赞同其观点或证实其描述。

提示:投资有风险,入市须谨慎。本资讯不作为投资理财建议。

金色财经 > 区块链 > 一文了解以太坊中智能合约攻击和漏洞