从一道智能合约赛题看Poly Network事件

阅读量    224227 |

分享到: QQ空间 新浪微博 微信 QQ facebook twitter

 

前言

​ 前几天参加了一个比赛,上面有一道题目与Poly Network 事件攻击手法类似,写一篇文章来总结一下。简单说一下攻击的点在于函数签名值的爆破,错误的设置合约owner。

 

代码分析

合约的代码文件在Github上,可以自行下载。下面分析漏洞点

 //DVT3.sol
         function changeOwner(address newOwner) public onlyOwner returns(bool) {
        require(newOwner != address(0));
        emit OwnerExchanged(owner, newOwner);
        owner = newOwner;
        return true;
    }

    function payforflag() public onlyOwner {
        emit SendFlag(msg.sender);
    }

}

在这段代码中,我们想要实现触发SendFlag事件必须要有owner权限,而changeOwner函数权限也掌握在owner中,我们无法突破。但是让我们来看另一段代码

//Airdrop.sol
function TransferOrAirDrop(address to, bool isTransfer, bytes calldata _method, uint256 amount) external {
        if (isTransfer) {
            bytes memory returnData;
            bool success;
            (success, returnData) = token.call(abi.encodePacked(bytes4(keccak256(abi.encodePacked(_method, "(address,address,uint256)"))),abi.encode(msg.sender,to,amount)));

            require(success, "executeProposal failed");
        } else {
            bytes memory returnData;
            bool isFristAirDropFlag;
            bool success;
            if(AirDropCount[msg.sender] == 0) {
                isFristAirDropFlag = true;
            } else if (AirDropCount[msg.sender] > 2) {
                return;
            }
            (success, returnData) = token.call(abi.encodePacked(bytes4(keccak256(abi.encodePacked(_method, "(bool,address)"))), abi.encode(isFristAirDropFlag, msg.sender)));
            require(success, "executeProposal failed");
            AirDropCount[msg.sender]++;
        } 
    }
}

在TransferOrAirDrop函数中,使用了call调用,但是未做调用函数名的限制,且_method参数可控,就可以通过爆破函数签名的方式调用token合约上的任意函数。在此处我们依旧可以注意到

对于DVT3合约上的owner被设置为了Airdrop的地址,也就是说我们可以调用前面提到的changeOwner函数变成合约的owner,进而实现触发SendFlag事件。

 

Poc分析

import sha3
from Crypto.Util.number import *
p=sha3.keccak_256()
p.update(b'changeOwner(address)')
print(p.hexdigest()[:8])
#a6f9dae1

再分析下面的两个call调用

(success, returnData) = token.call(abi.encodePacked(bytes4(keccak256(abi.encodePacked(_method, "(address,address,uint256)"))),abi.encode(msg.sender,to,amount)));
(success, returnData) = token.call(abi.encodePacked(bytes4(keccak256(abi.encodePacked(_method, "(bool,address)"))), abi.encode(isFristAirDropFlag, msg.sender)));

对于第一个调用我们需要爆破出满足_method(address,address,uint256)函数签名为0xa6f9dae1的_method,往后传入的第一个参数为msg.sender,恰好等于下面的代码

token.call(abi.encodePacked(bytes4(keccak256(abi.encodePacked("changeOwner(address)"))),abi.encode(msg.sender)));

对于第二个调用我们需要爆破出满足_method((bool,address)函数签名为0xa6f9dae1的_method,往后传入的第一个参数为isFristAirDropFlag,恰好等于下面的代码

token.call(abi.encodePacked(bytes4(keccak256(abi.encodePacked("changeOwner(address)"))),abi.encode(0x0/0x1)));

上述参数传递使用了Solidity语言的参数传递优化自动对齐的性质。

但是对于第二个调用不能是我们变成DVT3合约的owner,不太符合我们的调用。所以我们选择第一个调用。

 

攻击过程

使用 github.com/ethereum/go-ethereum/crypto 的库编写一个Go语言的多线程爆脚本 大致经过十五分钟可以出结果

可以看到两者的签名相同

转化出攻击参数

进行攻击

成功实现攻击

最后实现触发SendFlag事件

 

与Poly Network 事件的联系

Poly Network官方开源的源码中的_executeCrossChainTx函数中,我们可以容易的看到这一行

(success, returnData) = _toContract.call(abi.encodePacked(bytes4(keccak256(abi.encodePacked(_method, "(bytes,bytes,uint64)"))), abi.encode(_args, _fromContractAddr, _fromChainId)));

就可以在_toContract对应的合约上调用任意的函数,同时_toContract对应的合约上没有进行合理的鉴权,攻击者通过爆破_method从而调用 putCurEpochConPubKeyBytes 函数去替换 _toContract合约上的Keeper 的Public Key Bytes。

    function putCurEpochConPubKeyBytes(bytes memory curEpochPkBytes) public whenNotPaused onlyOwner returns (bool) {
        ConKeepersPkBytes = curEpochPkBytes;
        return true;
    }

在用替换后的Keeper的Public Key Bytes对应的私钥进行签名即可通过所有检查执行调用 LockProxy 合约将其管理的资产转出。

可以从函数签名库中找到

 

总结

本次攻击利用的三个点

  • 权限控制错误
  • call调用参数可控
  • 函数签名值的爆破

本次漏洞的发生在本质上还是对于call调用的错误限制,并且和其他的漏洞组合使用导致了Poly Network 6.1亿美金的被盗事件。在智能合约的开发实践中还是需要注意严格控制call调用,不可使其参数可控。同时对于一些关键函数的权限控制在审计时应作为重点审计。将这些函数的使用权掌握在可控的地方,不可被恶意利用。

分享到: QQ空间 新浪微博 微信 QQ facebook twitter
|推荐阅读
|发表评论
|评论列表
加载更多