Analysis of the Transit Finance Exploit
TL;DR
On December 20, 2023, Transit Finance was exploited on the Ethereum Mainnet as well as the BNB chain due to a smart contract vulnerability, which resulted in a loss of funds worth approximately $115,000.
Introduction to Transit Finance
Transit is a cross-chain swap platform that integrates DEXs, aggregates transactions, and provides a one-stop cross-chain solution.
Vulnerability Assessment
The root cause of the exploit is the lack of input validation for the pool.
Steps
Step 1:
We attempt to analyze one of the attack transactions executed by the exploiter.
Step 2:
It can be seen on the vulnerable contract that along the swapping route, the pre-deployed fake token pair would return a false value without actually transferring any tokens to mislead the transit finance route into validating the unexpected swap.
function _executeV3Swap(ExactInputV3SwapParams calldata params) internal nonReentrant whenNotPaused returns (uint256 returnAmount) {
require(params.pools.length > 0, "Empty pools");
require(params.deadline >= block.timestamp, "Expired");
require(_wrapped_allowed[params.wrappedToken], "Invalid wrapped address");
address tokenIn = params.srcToken;
address tokenOut = params.dstToken;
uint256 actualAmountIn = calculateTradeFee(true, params.amount, params.fee, params.signature);
uint256 toBeforeBalance;
bool isToETH;
if (TransferHelper.isETH(params.srcToken)) {
tokenIn = params.wrappedToken;
require(msg.value == params.amount, "Invalid msg.value");
TransferHelper.safeDeposit(params.wrappedToken, actualAmountIn);
} else {
TransferHelper.safeTransferFrom(params.srcToken, msg.sender, address(this), params.amount);
}
if (TransferHelper.isETH(params.dstToken)) {
tokenOut = params.wrappedToken;
toBeforeBalance = IERC20(params.wrappedToken).balanceOf(address(this));
isToETH = true;
} else {
toBeforeBalance = IERC20(params.dstToken).balanceOf(params.dstReceiver);
}
{
uint256 len = params.pools.length;
address recipient = address(this);
bytes memory tokenInAndPoolSalt;
if (len > 1) {
address thisTokenIn = tokenIn;
address thisTokenOut = address(0);
for (uint256 i; i < len; i++) {
uint256 thisPool = params.pools[i];
(thisTokenIn, tokenInAndPoolSalt) = _verifyPool(thisTokenIn, thisTokenOut, thisPool);
if (i == len - 1 && !isToETH) {
recipient = params.dstReceiver;
thisTokenOut = tokenOut;
}
actualAmountIn = _swap(recipient, thisPool, tokenInAndPoolSalt, actualAmountIn);
}
returnAmount = actualAmountIn;
} else {
(, tokenInAndPoolSalt) = _verifyPool(tokenIn, tokenOut, params.pools[0]);
if (!isToETH) {
recipient = params.dstReceiver;
}
returnAmount = _swap(recipient, params.pools[0], tokenInAndPoolSalt, actualAmountIn);
}
}
if (isToETH) {
returnAmount = IERC20(params.wrappedToken).balanceOf(address(this)).sub(toBeforeBalance);
require(returnAmount >= params.minReturnAmount, "Too little received");
TransferHelper.safeWithdraw(params.wrappedToken, returnAmount);
TransferHelper.safeTransferETH(params.dstReceiver, returnAmount);
} else {
returnAmount = IERC20(params.dstToken).balanceOf(params.dstReceiver).sub(toBeforeBalance);
require(returnAmount >= params.minReturnAmount, "Too little received");
}
_emitTransit(params.srcToken, params.dstToken, params.dstReceiver, params.amount, returnAmount, 0, params.channel);
}
Step 3:
Due to this lack of validation, the attacker was able to fake a pool and manipulate the actualAmountIn
value of this function in the first swap.
This caused the SwapRouter to take the forged actualAmountIn
as the initial value for swap in the next WBNB/BUSD pool, thereby making the router supply more assets than intended.
Step 4:
The stolen assets were then sent to a PancakePair created by the attacker prior to the exploit, which were later inflated and removed to take away the profits.
Step 5:
The exploiter has already laundered 36 ETH worth $78,000 and 147 BNB worth $37,300 to Tornado Cash.
Aftermath
The team didn’t directly acknowledge the occurrence of the exploit on their social media handle across X but commented that the user assets were unaffected and that they had completed the contract upgrade.
Solution
In addressing the vulnerabilities suffered by the Transit Finance exploit, several measures can be implemented to enhance the security and robustness of DeFi platforms.
Firstly, rigorous validation of data types is crucial. Smart contracts should be designed to strictly verify the types of inputs. This prevents the misuse of functions due to incorrect or maliciously crafted data. For instance, ensuring that an address is indeed a valid token address or that numerical values do not exceed expected ranges can mitigate risks significantly.
Sanitizing user input is equally important. This involves implementing mechanisms to cleanse or reject inputs that could potentially cause unexpected or harmful outcomes. It’s about anticipating the ways in which a user might interact with the contract in unintended ways and safeguarding against these scenarios. Utilizing fuzzing tools represents an advanced and proactive approach to identifying vulnerabilities. Fuzzing involves providing a wide range of random, unexpected, or invalid inputs to the system to see how it responds. This can uncover hidden bugs or vulnerabilities that would not be apparent through standard testing methods.
Developing and testing against a comprehensive set of edge cases ensures that the contract behaves correctly under all possible conditions, not just the most common or expected ones. This means deliberately creating scenarios that push the limits of the contracts’ logic and observing how they handle these extreme cases.
This article was originally published by Pukar Acharya elsewhere.
Enjoy Reading This Article?
Here are some more articles you might like to read next: