寻找智能合约bug可能是一项高回报的工作,而且它也保护了生态系统免受黑客攻击。我最近有幸采访了一位开发人员,他发现了一个价值 70 亿美元的错误,并因报告该错误而获得了 220 万美元的报酬。
在这篇文章中,我将详细介绍该开发人员发现的 bug 的过程,以及它如何有可能损害 70 亿美元的价值,然后再提供一些可帮助你查找错误的策略和工具。
让我们开始吧。
2020 年 5 月 31 日,Matic 区块链上线(Matic 后来更名为 Polygon)。 Polygon 是一种与 EVM 兼容的区块链,以其低 gas 费用和短块时间而闻名。该链最近开始探索 zk-rollup 技术。
如果你查看 Polygon 的“创世”区块,即区块链的第一个区块,你将看到 10 笔交易。其中一笔交易创建了一个名为 MRC20 的合约。
Polygon 创世区块
这个智能合约是什么?
当我们发送原生区块链通证时,我们必须花费 gas。因此,Polygon 团队部署了一个合约,允许你签名一项交易以向某人发送 ETH,而其他人则可以支付这笔交易的 gas 费。这种功能被称为“元交易”,随着 EIP-712 的推出而普及。
你可以看到,该合约获得了近 100 亿个 MATIC 通证,以帮助促进这些无需 gas 的交易。然而,这个设计精巧的合约却包含一个 bug ,而这个 bug 可能会被利用来盗走全部余额!
2021 年 12 月 3 日,故事的主人公、伪匿名开发者 Leon Spacewalker 向 Immunefi bug 赏金计划提交了一份报告,详细介绍了这个函数的问题。第二位英雄,我们称之为 Whitehat2,也在一天后报告了该 bug 。
在该链于 2021 年 12 月 5 日最终分叉、回滚和修复之前,大约有 800,000 个 MATIC 被盗。
这次事件给我们抛出了很多问题: bug 是什么?它是如何长期未被发现的?它是怎么被发现的?
以下是发送无 gas 交易的函数
function transferWithSig(
bytes calldata sig,
uint256 amount,
bytes32 data,
uint256 expiration,
address to
) external returns (address from) {
require(amount > 0);
require(
expiration == 0 || block.number <= expiration,
"Signature is expired"
);
bytes32 dataHash = getTokenTransferOrderHash(
msg.sender,
amount,
data,
expiration
);
require(disabledHashes[dataHash] == false, "Sig deactivated");
disabledHashes[dataHash] = true;
from = ecrecovery(dataHash, sig);
_transferFrom(from, address(uint160(to)), amount);
}
乍一看,它似乎无害:它需要用户的签名、有多少通证、他们想将通证发送给谁、进一步的数据,以及交易的到期日期。
它也有一些限制,获取数据哈希以发送元交易,确保数据哈希未被使用,并执行这个 ecrecovery
函数。
这个函数本质上是 Solidity ecrecover 函数的 Wrapper。
Solidity ecrecover 函数的 wrapper
Here’s the actual code:
我们这个函数验证签名交易的来源。你会注意到,即使在 Solidity 文档中,它也说它将“错误的返回值为零”。 ecrecovery 函数也一样,如果有问题,它会返回 0。正如许多开发人员所知,这可能是有风险的。如果它在出错时返回零,那意味着我们应该检查以确保返回的地址不为零,对吗?
这是实际的代码:
这是理想中的代码:
我们其实没有对地址执行检查以确保它不会导致错误,好吧。 transferWithSig
函数中的最后一行代码执行实际的转账,我们肯定要在那里执行某种检查,对吧?
function _transfer(address sender, address recipient, uint256 amount)
internal
{
require(recipient != address(this), "can't send to MRC20");
address(uint160(recipient)).transfer(amount); // It just sends the money!
emit Transfer(sender, recipient, amount);
}
_transferFrom
函数刚刚调用了我们的 _transfer
函数,如上所示。你会注意到它不会检查,以确保 from
地址有足够的余额。
这意味着有人可以发送无效签名,这将导致从 ecrecovery 返回零地址,但 MRC20 合约仍会向 to
地址发送一定数量的通证。这就是 9,999,993,000 MATIC 被盗走的方式,因为 MRC20 合约直接从自身发送通证!
如果在合约中检查,以确保发件人地址有足够的余额用于此签名交易,就可以避免此问题。
令我感到奇怪的是,在该 bug 潜伏了将近一年半之后,它在几天之内就被另一位白帽子 Leon 和一名黑客发现了。
好像有猫腻的样子,但 Immunefi 团队告诉我,这种情况经常发生。某些 bug 被利用可能会因一篇文章、文章或挑战而变得流行,然后人们开始寻找该 bug ,导致多个人同时找到它。
但更有可能的是,事实证明 Polygon 在这段时间左右在 Polygonscan 上验证了合约——那也是人们真正开始关注它的时候。
也许还有更多的故事,但也许不是。
无论如何,让我们把这个 bug 作为一个教学案例,看看 Leon 和其他 bug hunter 用来发现 bug 的一些技能,帮助保护 Web3 生态系统。
现在,我们将学习 Leon 和其他 bug hunter 用来发现这些 bug 并申请 bug 赏金的技能。此提示列表假定你已经了解智能合约的基础知识,所以是的,学习 Solidity 是先决条件。
请有道德地去使用这些技巧,请记住负责任地披露你发现的任何 bug 。
许多查找 bug 的工作来自于查看代码和运行诸如 slither 之类的工具。对于这笔 220 万美元的收获,Leon 表示他能够通过逐行查看智能合约代码来找到 bug ,所以请记住,发现 bug 通常需要大量的人工手动操作!
除了下面的实用技巧外,Leon 最大的收获是让智能合约 bug hunter “找到你的优势”,什么意思?通常,这意味着找到让你有别于其他人的东西。作为一个社区,我们需要覆盖智能合约空间的每一个角落,所以找到你特别擅长和擅长的部分。
这里有七个策略和技巧可以帮助你找到优势,让你变成成功的智能合约的 bug hunter。
找到错误的第一种方法是详细地了解协议工作原理。这是每个智能合约 bug hunter 需要学习的首要技能之一:端到端理解协议的能力。
浏览文档,尝试自己重新实现协议,并在区块浏览器上通过该协议查看交易。
Leon 说这个策略对其他 bug hunter 有效,但对他无效。他专注于接下来的三个,但对于每个 bug hunter 来说,能够做到这一点很重要。
寻找 bug 的一种更简单的方法是:找到一个鲜为人知的 bug,然后尝试查看哪些协议的实现中包含这个 bug。
这种策略需要大量研究,因为有很多人致力于向公众公开 bug。
你首先需要了解所有基本的智能合约 bug ,然后是它们的高级版本。你需要了解最佳实践并查看是否有未遵循的协议。
一旦发现智能合约 bug,你认为很多项目可能无法防范,就开始搜索该 bug。直到真正熟悉这个新的 bug 以及如何找到它。一定写博客或某种帖子来帮助遇到此 bug 的其他智能合约开发人员。
希望 bug hunter 查看其智能合约的项目需要注册像 Immunefi 这样的漏洞赏金计划。你会想成为第一批发现新赏金的开发者之一。如果你比其他猎人先开始查看合约,你将有更多时间找到漏洞。
有几种方法可以加快速度——Leon 能够在其他人之前找到智能合约漏洞的方法之一是通过 Immunifi Discord 频道的通知。每当有新项目进入或项目更新时,他都会收到通知。像这样的工具可以帮助你抢在其他人之前深入研究代码。
Leon 获得优势的另一种方式是查看大量的社区论坛,发现他们正在考虑提交 bug。然后他甚至在赏金获得批准之前就开始查看智能合约。这让他比其他开发人员有更多时间查看合约,因为别人会等待项目方 bug 赏金提交成功。
Bug hunter 要使用 VSCode Solidity 的 extension、Hardhat、Foundry、Brownie、Dune、Etherscan 以及许多其他工具。
一种的 bug 查找策略可能是加载 VSCode,使用 Solidity extension 将代码添加到 VSCode,然后逐行查找常见错误或有漏洞的代码实现。
找到潜在漏洞后,设置测试环境以对合约运行测试。你通常可以重用协议开发人员最初使用的大量测试。
这里就不多说了。审计公司会犯错误。 Leon 发现漏洞的许多项目都已经过顶级公司的审计。
使用我们在此博客中讨论的技巧可以帮助你找到这些问题!
你最大优势之一可能就是专注于特定的细分市场。如果你非常了解一个领域,那么你将拥有了解所有函数如何相互调用的知识。相反,如果你是一个厉害的智能合约漏洞专家,但对 DeFi 一无所知,那么很难找到 DeFi 合约中的漏洞。例如,许多开发人员了解代码,但不了解财务术语。
你可能非常擅长理解去中心化交易所、借贷协议,或者只是 NFT!
如果你能成为安全专家和 Web3 中某个垂直领域的专家,你将处于有利地位,在其他寻找漏洞的人面前占据优势。
我希望这篇文章在你的智能合约 bug 搜索之旅中帮助到你。如果你想在编写智能合约时了解更多有关安全的信息,请务必查看十大 DeFi 安全最佳实践。
而且,一如既往,我希望看到你在那里建设并保持生态系统更安全。
相关链接:
这篇文章中表达的观点仅代表作者,并不反映 Chainlink。
欢迎关注 Chainlink 预言机并且私信加入开发者社区,有大量关于智能合约的学习资料以及关于区块链的话题!