# Boost \_ Folks Finance 34066 - \[Smart Contract - Medium] Account Creation Front-Running Vulnerability

Submitted on Mon Aug 05 2024 04:00:21 GMT-0400 (Atlantic Standard Time) by @OxG0P1 for [Boost | Folks Finance](https://immunefi.com/bounty/folksfinance-boost/)

Report ID: #34066

Report type: Smart Contract

Report severity: Medium

Target: <https://testnet.snowtrace.io/address/0xa9491a1f4f058832e5742b76eE3f1F1fD7bb6837>

Impacts:

* Theft of gas

## Description

## Brief/Intro

A vulnerability has been identified where an attacker can front-run or accidentally intercept the account creation transaction of users, leading to the theft of users' gas fees.

## Vulnerability Details

Users can create an account by inputting a desired accountId through the `spokeCommon` contract:

```solidity
function createAccount(
    Messages.MessageParams memory params,
    bytes32 accountId,
    bytes32 refAccountId
) external payable nonReentrant {
    _doOperation(params, Messages.Action.CreateAccount, accountId, abi.encodePacked(refAccountId));
}
```

Since account management is conducted on the hub chain, the account creation transaction must be relayed by bridges. To facilitate this, a bridge router routes the payload to the corresponding adapter and receives the payload on the hub chain. These bridge routers maintain user balances to cover the fees required by the adapter to relay messages. These balances are stored in a mapping of `userId` to `amount`.

```solidity
function sendMessage(
    Messages.MessageToSend memory message
) external payable override onlyRole(MESSAGE_SENDER_ROLE) {
    // Check if valid adapter and retrieve
    IBridgeAdapter adapter = getAdapter(message.params.adapterId);

    // Verify if messager matches caller
    address messager = Messages.convertGenericAddressToEVMAddress(message.sender);
    if (messager != msg.sender) revert SenderDoesNotMatch(messager, msg.sender);

    // Get the fee from the adapter
    uint256 fee = adapter.getSendFee(message);

    // Check if sufficient funds are available (from balance and/or msg.value)
    bytes32 userId = _getUserId(Messages.decodeActionPayload(message.payload));
    uint256 userBalance = balances[userId];
    if (msg.value + userBalance < fee) revert NotEnoughFunds(userId);

    // Update user balance considering fee and msg.value
    balances[userId] = userBalance + msg.value - fee;

    // Call the adapter to send the message
    adapter.sendMessage{ value: fee }(message);
}
```

In the context of the bridge router deployed on the spoke chain, the `userId` is the `msg.sender`, which is the user's address:

```solidity
function _getUserId(Messages.MessagePayload memory payload) internal pure override returns (bytes32) {
    return payload.userAddress;
}
```

However, in the context of the bridge router on the hub chain, the `userId` is the `accountId` specified by the user:

```solidity
function _getUserId(Messages.MessagePayload memory payload) internal pure override returns (bytes32) {
    return payload.accountId;
}
```

Some bridges allow users to send more value than the fee required to relay the message. The extra amount, which is `value - fee`, is stored on the hub chain:

```solidity
if (msg.value > 0) {
    bytes32 userId = _getUserId(Messages.decodeActionPayload(message.payload));
    balances[userId] += msg.value;
}
```

This balance can be used for round-trip messages. The vulnerability arises when an attacker front-runs the account creation transaction of a user and creates an account with the same `accountId` as the victim. The extra amount, other than the fee that the bridge holds, will then be credited to the attacker's account. Consequently, the victim's account creation transaction will be stored as a failed message.

## Impact Details

The primary impact is the loss of funds for the user, specifically the funds used to relay bridge messages. This loss can occur every time a user attempts to create an account, and the extent of the loss depends on the extra value sent by the user beyond the required fee.

## References

<https://github.com/Folks-Finance/folks-finance-xchain-contracts/blob/fb92deccd27359ea4f0cf0bc41394c86448c7abb/contracts/bridge/BridgeRouterHub.sol#L25-L27>

<https://github.com/Folks-Finance/folks-finance-xchain-contracts/blob/fb92deccd27359ea4f0cf0bc41394c86448c7abb/contracts/bridge/BridgeRouterHub.sol#L25-L28>

<https://github.com/Folks-Finance/folks-finance-xchain-contracts/blob/fb92deccd27359ea4f0cf0bc41394c86448c7abb/contracts/bridge/BridgeRouter.sol#L107-L110>

## Proof of concept

## Proof of Concept

**Alice (Victim):** Wants to create an account with a specific `accountId` and send extra funds to cover bridge fees.

**Bob (Attacker):** Monitors pending transactions, identifies Alice’s transaction, and creates an account with the same `accountId` just before Alice's transaction is processed.

1. **Alice's Transaction Preparation:**
   * Alice prepares a transaction to create an account with a specific `accountId` and sends an extra amount of value (e.g., 2 ETH) to cover the bridge fee.
   * Transaction details:

     ```solidity
     function createAccount(
         Messages.MessageParams memory params,
         bytes32 accountId,
         bytes32 refAccountId
     ) external payable nonReentrant {
         _doOperation(params, Messages.Action.CreateAccount, accountId, abi.encodePacked(refAccountId));
     }
     ```
   * Alice's transaction code:

     ```solidity
     Messages.MessageParams memory params = /* ... */;
     bytes32 accountId = keccak256(abi.encodePacked("aliceAccountId"));
     bytes32 refAccountId = keccak256(abi.encodePacked("referenceAccountId"));

     bridgeRouter.createAccount{ value: 2 ether }(params, accountId, refAccountId);
     ```
2. **Bob Monitors Pending Transactions:**
   * Bob uses tools to monitor pending transactions in the mempool, looking for `createAccount` transactions.
   * Upon identifying Alice’s transaction with the desired `accountId`, Bob prepares to front-run the transaction.
3. **Bob Front-Runs the Transaction:**
   * Bob quickly sends a transaction to create an account with the same `accountId` specified by Alice. Bob ensures his transaction has a higher gas price to be mined before Alice's transaction.
   * Bob's transaction details:

     ```solidity
     function createAccount(
         Messages.MessageParams memory params,
         bytes32 accountId,
         bytes32 refAccountId
     ) external payable nonReentrant {
         _doOperation(params, Messages.Action.CreateAccount, accountId, abi.encodePacked(refAccountId));
     }
     ```
   * Bob's transaction code:

     ```solidity
     Messages.MessageParams memory params = /* ... */;
     bytes32 accountId = keccak256(abi.encodePacked("aliceAccountId"));
     bytes32 refAccountId = keccak256(abi.encodePacked("referenceAccountId"));

     bridgeRouter.createAccount{ value: 0.1 ether }(params, accountId, refAccountId);
     ```
4. **Alice's Transaction Execution:**
   * Alice's transaction is now processed after Bob's transaction.
   * The bridge router on the spoke chain identifies the `msg.sender` as the user address and maps the balances accordingly.
   * On the hub chain, the `accountId` is used to identify the user.
   * Since Bob's transaction has already created an account with the specified `accountId`, Alice’s transaction fails, and the extra funds intended for the bridge fee are now associated with Bob’s account.
5. **Resulting State:**
   * Bob successfully creates an account with Alice's intended `accountId` and receives the extra funds sent by Alice.
   * Alice's transaction fails, and the funds meant to cover the bridge fees are effectively stolen by Bob.

Note : This attack is especially profitable when the attacker front-runs the transaction from the hub chain. In this scenario, there is no bridge fee to be paid on the hub chain because there is no need to relay the message. The attacker can then withdraw the balance associated with the accountId on any other spoke chain.
