现在是时候分享今年恶意 Solidity 竞赛!
的获奖者了。
在我们深入研究获奖作品之前,让我们回顾一下 USC 的最重要特征。
简而言之,USC 是关于寻找 Solidity 语言中的漏洞或“隐藏点”,并利用这些漏洞来编写看似无害且直观的 Solidity 代码,其中包含恶意行为或后门。
- 恶意 Solidity 竞赛旨在...
- 提高对智能合约安全性的认识。
- 发现语言设计缺陷。
- 对最近引入的语言特性和限制进行实战测试。
- 突出智能合约开发中的反模式。
建立安全智能合约开发的新最佳实践。每次竞赛都有不同的主题或话题。
今年,任务是构建一个看起来公平的去中心化交易所,但可以“操纵”。
我们总共收到了 19 份作品,其中 18 份符合“合格作品”恶意 Solidity POAP NFT 的标准。您可以在 这个仓库中找到所有 18 份合格作品。
非常感谢所有参与者的参与,并向他们致敬!
与往常一样,评委们收到了匿名作品,只有在评审过程结束后才公布参与者的身份。
现在,事不宜迟,让我们从第三名开始,看看获奖者吧!
2022 年恶意 Solidity 竞赛获奖者
🥉 第三名:Michael Zhu
评论来自 samczsun
此作品是一个简单而优雅的提醒,并非所有事物都如表面所示。合约实现了一个非常简单的 NFT 销售机制,买家可以出价,卖家可以接受出价。为了优化目的,两个资产(正在购买的 NFT 和用于购买的代币)的地址被异或在一起以形成一个唯一的键。注释帮助我们记住,找到另一个地址对(合约地址),它们碰撞到同一个键的概率在统计学上是不可能的。当出价被接受时,
safeTransferFrom 用于将付款从出价者转移到发送者,而 transferFrom 用于将 NFT 从发送者转移到出价者。但是,ERC20 和 ERC721 都定义了一个具有相同参数的 transferFrom 函数,这导致它们具有相同的选择器。此外,即使代币不完全符合 ERC20 规范且不返回布尔值,safeTransferFrom 也会成功。最后,虽然对有些人来说可能很明显,但异或是一个可交换的操作。因此,可以通过在接受出价时交换 ERC20 和 ERC721 地址来接受虚假出价。这会导致 NFT 而不是付款代币从出价者那里转移。显然,这是一个不可取的结果。
虽然以简化的形式,这种优化的恶意本质可能很容易被发现(尤其是在恶意竞赛的背景下),但我们认为它可以很好地作为教学用户如何超越代码和注释的暗示,并阅读字里行间的代理。类似的思考方式导致发现了 Anyswap 漏洞,在较小程度上也导致了各种“假合约”漏洞。
🥈 第二名:Santiago Palladino
评论来自 Hari Mulackal
该作品展示了如果在合约中使用非标准签名方案可能会出现什么问题。合约巧妙地设计了 Order 结构体,使其能够与 approve 事务的 RLP 编码发生冲突。这允许攻击者重用 approve 事务的签名来执行交易所中的订单!
该实现中存在许多其他问题,我们留给读者去发现所有问题!
🥇 第一名:Tynan Richards
评论来自 Duncan Townsend
此作品非常微妙。非常微妙。我们评审团中没有人能够在没有提示文件帮助的情况下发现这个技巧。虽然这里利用的行为在 Solidity 文档中明确记录,但它绝对不是程序员或安全专家在日常工作中考虑的事情。
从表面上看,此作品是一个实施得相当不错的恒定乘积 AMM。对于熟悉这种 DEX 的人来说,您可能认识添加和删除流动性的公式,以及对交换收取费用的公式。此 AMM 对帮助fully 包含了在没有路由器帮助的情况下计算输入和输出金额所需的逻辑。审计师可能会在这个合约中发现一些安全问题,这些问题不是我们在这里寻找的核心灾难性漏洞。
- 按照从轻到重的顺序,评委们发现的一些漏洞是
- 此 AMM 无法提供时间加权平均价格预言机(或其他形式的防操纵价格预言机)。
- 流动性不是代币化,只能由提供流动性的地址撤回。
- 对 sqrt 的实现没有使用更常见的 巴比伦方法,而是使用 牛顿-拉夫森方法 收敛到正确的值。
- transferFundsIn 和 transferFundsOut 不支持从它们的 transferFrom 和 transfer 函数中不返回任何内容的 ERC20 代币。这是对 标准 的常见偏差,该标准要求这些函数返回 bool。SafeERC20 的各种迭代通常会处理这种情况。
- 没有用于将合约的内部余额/储备与其实际余额同步的功能。如果代币在 transferFundsIn 或 transferFundsOut 之外(如回购或直接调用 transfer)进入或离开合约,那么余额的变化将不会被记录。
- 在 addLiquidity 中没有针对精度丢失漏洞的保护,这会降低 totalSupply 与流动性之比。但是,这实际上被前一个缺陷所缓解。
在 trade、addLiquidity 或 removeLiquidity 中没有滑点参数,所有这些都可能导致 DEX 交互被 夹层攻击,交易者获得的收益减少。
真正的技巧比这些都更具灾难性。漏洞存在于管理员费用索取的方式中。此合约的作者帮助fully 添加了一种方法,允许流动性提供者在 DEX 的管理员将管理员费用设置得过高时退出 DEX。可以安排新的管理员费用,但它不会在 7 天内生效,从而允许流动性提供者退出。但是,这种保护是以一种微妙且灾难性的方式错误实现的。
在 Solidity 中,子表达式的求值顺序是 未指定的。这意味着在 f(g(), h()) 中,g() 可能会在 h() 之前求值,或者 h() 可能会在 g() 之前求值。实际上,这个顺序是可以预测的,但 Solidity 代码不应该依赖于编译器版本之间的这种行为。在 大多数情况下,g() 在 h() 之前求值(从左到右顺序),这也是大多数语言在其标准中指定的行为。但是,在使用索引参数发出事件的情况下,参数是从右到左求值的。
因此,当管理员调用 changeAdminFees 时,setNewAdminFee 在 retireOldAdminFee 之前求值。由于 retireOldAdminFee 调用 _claimAdminFees,因此新费用在代币被记录和转移之前就生效了。第 67 行的 require 实际上是无用的,因为 nextFeeClaimTimestamp 的 7 天延迟尚未设置。此外,newAdminFee 没有上限。它可以设置为高于 ONE(10**18),因此可以设置为足够高,以至于管理员可以耗尽交易对的 全部余额/储备。
虽然此作品很长,并且包含许多烟雾弹,但评委们选择它作为我们 2022 年 USC 的获胜者,是因为缺陷如何隐藏在显眼的地方。良好的单元测试或模糊测试肯定会发现此漏洞,但手动代码审查无法发现它。我们采用了这样的标准:成功的 USC 作品是可以欺骗谨慎的智能合约开发人员相信它,并将其资金委托给它,然后窃取这些资金。在这方面,此合约是绝对的赢家。此外,此作品突出了 Solidity 的一个方面,大多数开发人员和审计师可能没有意识到。子表达式的求值顺序通常无关紧要,但在这种情况下,它却关系重大。
这里是我承诺的 文档。
荣誉提名
评论来自 Anton Permenev
使用 Solidity v0.8,开发人员不必考虑算术运算中的溢出。
此作品是一个很好的提醒,位移 << 和 >> 运算符不属于算术运算符,因此不会在溢出时回滚。
但是,这种运算符很少见,它们的用法很可疑,会引起人们的注意。
在下一届恶意 Solidity 竞赛中,欺骗我们吧,匿名者!
感谢ChainSecurity、ConsenSys Diligence、Immunefi、Solidified、Trail of Bits、Paradigm以及Ethereum Foundation的鼎力支持!
最后但同样重要的是,我们也要向我们出色的评委们表示衷心的感谢,他们帮助我们成功举办了这次比赛:Alex Beregszaszi、Anton Permenev、Duncan Townsend、Gonçalo Sá、Hari Mulackal、Josselin Feist、samczsun 和 Stefan Beyer。👏
您是否想为下一届 Underhanded Solidity 竞赛提议主题、提供反馈或帮助评审? 欢迎您通过sol_underhanded@ethereum.org与我们联系!
我们很快将与所有参赛者联系,详细介绍 NFT 和主要奖品的领取流程。