HIGH severity: TransitFinance — Root cause:  1. The vulnerability's root cause was the lack of proper input validation. 2. The vulnerability allowed the attacker to use a forged pool and manipulate the WBNB/BUSD pool path. 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. 3. The attacker manipulated the first swap by controlling the actualAmountIn parameter, substituting it with a forged value. 4. This led to the SwapRouter using the manipulated actualAmountIn as the initial value for the swap in the WBNB/BUSD pool.. https://blog.solidityscan.com/transit-finance-hack-analysis-998400e2bd0c https://lunaray.medium.com/transitfinace-hack-analysis-9304a4863aaa https://blog.verichains.io/p/decoding-transit-finance-attack

Vulnerable code:
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]; // <-- Vulnerable point
                (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);
        }
    }
} ... 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];

        if (i == len - 1 && !isToETH) {
            recipient = params.dstReceiver;
            thisTokenOut = tokenOut;
        }
        // fake pool -> WBNB/BUSD
        (thisTokenIn, tokenInAndPoolSalt) = _verifyPool(thisTokenIn, thisTokenOut, thisPool);
        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);
}

Vulnerability type: Insufficient validation