智能合约交易顺序依赖漏洞,欢迎一起学习!
这些文章的主要目的不是教育别人,而是检验我自己的理解深度。但是,我确实希望我的努力能为您亲爱的读者提供一点价值。如果有任何问题或错误,请告诉我,以便我们一起学习和成长。
文章开头分享一个区块链安全和智能合约审计的交流群:741631068!
交易顺序依赖 (TOD) 漏洞是一种可能存在于智能合约中的竞争条件缺陷。这些也称为前端运行漏洞。由于在以太坊区块链上处理交易的方式,可能会存在此类漏洞。
当用户创建交易时,它不会立即添加到分类帐中。交易在整个网络中广播并存储在临时保存的节点内存池中。Mempool可以被认为是未处理/待处理交易的等待区,即尚未添加到块中的交易。Mempool 是以太坊中必不可少的机制,因为一旦用户交易被添加到账本中,就无法更改。 内存池如何工作:
通常,节点为内存池指定大约 300 MB 的空间。在高网络活动期间,此存储可能会达到其限制,并且可能会删除事务以清除一些内存。为了保证一笔交易不掉线,用户可以支付更高的gas费来保证更高的优先级。更高优先级的事务也被放置在队列中更高的位置以进行处理。这可能会带来安全风险。
由于区块链的开放和去中心化特性,任何人(拥有全节点客户端)都可以跟踪交易并探索内存池以进行恶意提议。交易订单也可以通过提供更高的汽油费来操纵。矿工还能够重新安排他们开采的区块中的交易顺序。 如果关键逻辑依赖于事务的顺序,它可能会导致意外的输出和结果的颠覆。
让我们看一下以下智能合约,以更好地了解该漏洞。
这是一个简单的智能合约,如果玩家猜对了数字,他们将被视为赢家。但是,该合约容易受到 TOD 的影响。攻击过程如下:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract numGuess {
address public winner;
function guess(uint _guess) external {
require (_guess == 9841984198);
winner = msg.sender;
}
}
顾名思义,在插入 TOD 中,将恶意行为者的交易插入到合约和用户的交易之间。例如,如果 Alice 下单以最优价格购买 NFT,Bob 可以在 Alice 之前进行交易并购买 NFT,然后立即以更高的价格出售以获利。
最后,抑制 TOD 只是意味着用许多交易淹没合约,以延迟其他用户对功能的访问。这也称为块填充攻击。
现实世界的例子包括Bancor (BNT),这是一个在几分钟内筹集了超过 1.5 亿美元的 ICO。安全研究发现 BNT 容易受到 TOD 攻击。幸运的是,该漏洞尚未被利用,Bancor 团队声称已修复该漏洞。
Mythril、Oyente 和 Securify 等自动化工具能够发现此类漏洞,等等。要在审核过程中手动发现 TOD,应考虑以下因素:
在我们的示例合同中,两个条件都是明显符合的,因此我们有漏洞。
TOD 漏洞可能难以预防和修复,因为它们需要对代码逻辑进行重大更改。
一种解决方案是使用commit reveal hash 方案。不是直接传输答案,而是传输盐,地址和答案的哈希。这里的盐是用户选择的随机数。此哈希与用户地址一起由合同存储。现在,当用户想要提交他们的答案时,他们将创建一个包含答案和盐的交易。合约将重新计算哈希值,如果与之前的哈希值匹配,则发布答案。 对我们的示例合约进行以下修改以修复该漏洞。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract numGuess {
address public winner;
mapping (address => bytes32) public playerHashes;
function storeHash(bytes32 _hash) external {
playerHashes[msg.sender] = _hash;
}
function guess(uint _answer, uint _salt) external {
require (keccak256(abi.encodePacked(_answer, _salt, msg.sender)) == playerHashes[msg.sender]);
require (_answer == 9841984198);
winner = msg.sender;
}
}
在上面,映射的代码存储与特定地址相关联的哈希。storeHash函数用于执行此操作。之后,玩家将调用guess函数并将真实答案与盐一起传入。
虽然,作弊者仍然能够拦截这些消息并将它们转发给玩家之前的合约,但这将是无用的,因为检查将在第 13 行(require (keccak256(abi.encodePacked(\_answer, \_salt, msg.sender)) == playerHashes\[msg.sender]);
)失败。试图捕获答案和计算哈希以提交它们都不起作用,因为为时已晚(还可以实现基于时间的锁定机制来强制执行这一点)。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!