本次,我们来讲一讲运用delegatecall函数时更复杂的合约漏洞案例。
目标合约
漏洞分析
这次的攻击目标依然是获得HackMe 合约中的 owner 权限,我们可以看到两个合约中除了 HackMe 合约中的构造函数可以修改合约的 owner 其他地方并没有修改 owner 的函数,但是却可以修改位置slot0的值,而HackMe 合约中插槽slot0表示的便是Lib的地址,那么我们就先修改Lib的地址为我们的地址,再次调用HackMe 合约时就会运行我们合约中的逻辑,那么想改哪个位置插槽的值不就都由我们控制了吗?
攻击合约
下面是我们本次的攻击合约:
接下来我们来看看攻击的整个逻辑:
1. Attack.attack() 函数先将自己的地址转换为 uint256 类型(这一步是为了兼容目标合约中的数据类型)第一次调用 HackMe.doSomething() 函数;
2. HackMe.doSomething() 函数使用 delegatecall 函数带着传入的 Attack 合约的地址调用了 Lib.doSomething() 函数;
3. 可以看到 Lib.doSomething() 函数将合约中存储位置为 slot0 的参数改为传入的值,这样当 HackMe 合约使用 delegatecall 调用 Lib.doSomething() 函数时也将改变自己在 slot0 位置存储的变量的值,也就是将 lib 参数(这里存储的是 Lib 合约的地址)改为我们传入的 Attack 合约的地址。此时之前在 HackMe.lib 参数中存储的 Lib 合约的地址就被修改成我们传入的 Attack 合约的地址了;
4. Attack.attack() 函数再次调用 HackMe.doSomething() 函数,由于在上一步我们已经将 HackMe.lib 变量修改为 Attack 合约的地址了,这时 HackMe.doSomething() 函数将不再调用之前的 Lib 合约而是用 delegatecall 去调用 Attack.doSomething() 函数。此时我们再来观察 Attack 合约的写法,发现其变量的存储位置故意和 HackMe 合约保持一致,并且不难发现 Attack.doSomething() 函数的内容也被攻击者写为 owner = msg.sender,这个操作修改了合约中存储位置为 slot1 的变量。所以 HackMe 合约使用 delegatecall 调用 Attack.doSomething() 函数就会将合约中存储位置为 slot1 的变量 owner 修改为 msg.sender 也就是攻击者的地址,至此攻击者完成了他的攻击。
修复建议
我们在合约的开发中使用delegatecall要时刻注意其被调用的合约地址要始终在我们设计的逻辑内运行,不能让其有可能超出我们设计时的适用范围,一旦出现了超出我们预期设计的情况,那么合约就有可能被不法之徒利用。
如果想了解更多的智能合约和区块链知识,欢迎到区块链交流社区CHAINPIP社区,一起交流学习~
社区地址:https://www.chainpip.com/