Stratax Contracts

First Flight #57
Beginner FriendlyDeFi
100 EXP
Submission Details
Impact: high
Likelihood: high

Missing User Authorization in Flash Loan Callback

Author Revealed upon completion

Root + Impact

Description

  • Flash loan callbacks should validate that the operation is being executed for the legitimate user who initiated the transaction.

  • The executeOperation function decodes a user address from parameters but never validates that this user actually authorized the operation, allowing potential unauthorized position manipulation.

function executeOperation(
address _asset,
uint256 _amount,
uint256 _premium,
address _initiator,
bytes calldata _params
) external returns (bool) {
require(msg.sender == address(aavePool), "Caller must be Aave Pool");
require(_initiator == address(this), "Initiator must be this contract");
// @> User address is decoded but never validated
OperationType opType = abi.decode(_params, (OperationType));
if (opType == OperationType.OPEN) {
return _executeOpenOperation(_asset, _amount, _premium, _params);
} else {
return _executeUnwindOperation(_asset, _amount, _premium, _params);
}
}
function _executeOpenOperation(address _asset, uint256 _amount, uint256 _premium, bytes calldata _params)
internal
returns (bool)
{
// @> User is decoded but not checked against msg.sender or any authorization
(, address user, FlashLoanParams memory flashParams) =
abi.decode(_params, (OperationType, address, FlashLoanParams));

Risk

Likelihood:

  • The contract uses onlyOwner modifier on entry functions, but the owner is a single address

  • If owner key is compromised or contract is called through unexpected path, user parameter can be arbitrary

  • The decoded user address is only used for event emission, not authorization

Impact:

  • Positions could be created or unwound for wrong users

  • Events would emit incorrect user addresses

  • Accounting and position tracking would be corrupted

  • Users could lose funds if positions are manipulated

Proof of Concept

// If owner key is compromised or there's an unexpected call path:
function maliciousCall() external {
// Encode victim's address in params
bytes memory params = abi.encode(
OperationType.OPEN,
victimAddress, // Victim's address encoded
flashParams
);
// Flash loan executes with victim's address in event
// but actual position belongs to attacker
aavePool.flashLoanSimple(address(stratax), token, amount, params, 0);
}

Recommended Mitigation

function _executeOpenOperation(address _asset, uint256 _amount, uint256 _premium, bytes calldata _params)
internal
returns (bool)
{
(, address user, FlashLoanParams memory flashParams) =
abi.decode(_params, (OperationType, address, FlashLoanParams));
+
+ // Validate user authorization - store authorized user in state variable
+ require(user == authorizedUser, "Unauthorized user in params");
+ delete authorizedUser; // Clear after use
// ... rest of function
}
function createLeveragedPosition(
address _flashLoanToken,
uint256 _flashLoanAmount,
uint256 _collateralAmount,
address _borrowToken,
uint256 _borrowAmount,
bytes calldata _oneInchSwapData,
uint256 _minReturnAmount
) public onlyOwner {
require(_collateralAmount > 0, "Collateral Cannot be Zero");
+
+ // Store authorized user before flash loan
+ authorizedUser = msg.sender;
+
IERC20(_flashLoanToken).transferFrom(msg.sender, address(this), _collateralAmount);
function _executeOpenOperation(address _asset, uint256 _amount, uint256 _premium, bytes calldata _params)
internal
returns (bool)
{
(, address user, FlashLoanParams memory flashParams) =
abi.decode(_params, (OperationType, address, FlashLoanParams));
+
+ // Validate user authorization - store authorized user in state variable
+ require(user == authorizedUser, "Unauthorized user in params");
+ delete authorizedUser; // Clear after use
// ... rest of function
}
function createLeveragedPosition(
address _flashLoanToken,
uint256 _flashLoanAmount,
uint256 _collateralAmount,
address _borrowToken,
uint256 _borrowAmount,
bytes calldata _oneInchSwapData,
uint256 _minReturnAmount
) public onlyOwner {
require(_collateralAmount > 0, "Collateral Cannot be Zero");
+
+ // Store authorized user before flash loan
+ authorizedUser = msg.sender;
+
IERC20(_flashLoanToken).transferFrom(msg.sender, address(this), _collateralAmount);

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.

Give us feedback!