委托调用给不受信任的被调用者(Delegatecall to Untrusted Callee)
当用户A通过合约B来call合约C的时候,执行的是合约C的函数,上下文(Context,可以理解为包含变量和状态的环境)也是合约C的:msg.sender是B的地址,并且如果函数改变一些状态变量,产生的效果会作用于合约C的变量上。
delegatecall是一种特殊的调用,当用户A通过合约B来delegatecall合约C的时候,执行的是合约C的函数,但是上下文仍是合约B的:msg,sender是A的地址,并且如果韩式改变一些状态变量,产生的效果会作用于合约B的变量上。可以这样理解:一个投资者A(用户A)把他的资产(B合约的状态变量)都交给一个风险投资代理(C合约)来打理。执行的是风险投资代理的函数,但改变的是资产的状态。(以上转载自:https://blog.csdn.net/djklsajdklsajdlk/article/details/140522665)
本质上,delegatecall委托其他合约来修改调用合约的存储。由于delegatecall对合约提供了如此多的控制,因此仅对受信任的合约(如自己的合约)使用此功能。如果目标地址来自用户输入,请确保它是一个受信任的合约。
考虑以下合约,其中delegatecall被误用,导致了一个漏洞:
contract Proxy {
address public owner;
constructor() {
owner = msg.sender;
}
function forward(address callee, bytes calldata _data) public {
require(callee.delegatecall(_data), "Delegatecall failed");
}
}
contract Target {
address public owner;
function pwn() public {
owner = msg.sender;
}
}
contract Attack {
address public proxy;
constructor(address _proxy) {
proxy = _proxy;
}
function attack(address target) public {
Proxy(proxy).forward(target, abi.encodeWithSignature("pwn()"));
}
}
当Attack合约的attack函数被调用时,它会调用Proxy合约的forward函数,传入target合约的地址以及pwn函数的ABI编码。Proxy合约接收到调用后,通过delegatecall调用target合约的pwn函数。由于delegatecall保留了调用者的环境(包括msg.sender),所以pwn函数实际上会将Attack合约的调用者作为新的拥有者。
保护措施:
为了降低delegatcall相关的风险,请考虑以下策略:
(1)白名单可信合约:确保delegatecall的目标地址是您控制的合约,或者是经过验证和可信列表的一部分。
(2)限制delegatecall的作用域:只对特定的、受控的操作使用delegatecall。除非绝对必要,否则避免将其公开为通用函数。