关卡链接: Ethernaut Level 23 - Dex Two
攻击类型: 价格操纵 / 缺少输入验证
难度: ⭐⭐⭐☆☆
与上一关类似,你需要与一个 Dex
合约交互。但这次的目标更具挑战性:你需要同时耗尽 Dex
合约中 Token1
和 Token2
的全部流动性。
DexTwo
合约的代码与上一关的 Dex
几乎完全相同,但有一个微小却致命的改动。在 swap
函数中,一行关键的验证代码被移除了:
1 | // This line was present in Dex, but is missing in DexTwo |
这行代码原本用于确保交易只在 token1
和 token2
之间进行。由于它被移除了,DexTwo
的 swap
函数现在可以接受任何符合ERC20标准的代币作为交易对的一方。
这就为我们打开了攻击的大门。我们可以创建一个我们自己控制的、毫无价值的“攻击代币”(我们称之为 Token3
),并用它来操纵与 Token1
和 Token2
的交易价格。
我们的策略是利用我们自己创建的 Token3
作为媒介,以极低的价格换取 Dex
池中所有的 Token1
和 Token2
。
创建并分发攻击代币: 我们创建一个新的ERC20代币 Token3
,并给自己铸造大量的 Token3
。
为 Token3
提供“流动性”: 我们向 DexTwo
合约发送极少量的 Token3
(例如,1个)。现在 DexTwo
合约中 Token3
的余额为1。
第一次交换 (Token3 -> Token1):
Token3
交换 Token1
。池中 Token1
的余额是100,Token3
的余额是1。价格比为 100:1。Token3
,就可以根据价格公式换取 (1 * 100) / 1 = 100
个 Token1
。Dex
池中的 Token1
被全部换走。第二次交换 (Token3 -> Token2):
Token2
的余额是100,Token3
的余额是2(我们第一次交换时转入了1个,现在又转入了1个)。价格比为 100:2,即 50:1。Token3
,就可以换取 (2 * 100) / 2 = 100
个 Token2
。Dex
池中的 Token2
也被全部换走。通过引入一个我们完全控制的第三方代币,我们成功地操纵了价格,并用极小的代价(总共3个我们自己随意铸造的 Token3
)清空了整个 Dex
池。
测试代码将模拟上述的攻击流程:创建新代币,并用它来耗尽 DexTwo
的流动性。
1 | // SPDX-License-Identifier: Unlicense |
swap
函数缺少对交易代币的白名单验证。DexTwo
合约发送极少量的攻击代币,以建立一个极不平衡的交易对。DexTwo
中所有的 Token1
。DexTwo
中所有的 Token2
。严格的输入验证: 这是最关键的防御措施。合约必须严格验证所有外部输入,特别是那些决定核心逻辑的参数,如本例中的代币地址。
1 | // 修复建议:加回被移除的验证 |
使用白名单: 对于允许哪些代币参与交互的系统,应维护一个可信代币的白名单,并对所有传入的代币地址进行检查。
核心概念:
攻击向量:
防御策略:
在智能合约的世界中,最简单的漏洞往往隐藏着最深刻的安全教训。 🎓