#42903 [BC-High] Attackers are able to submit multiple dupplicate transactions due to mismatched Mempool Implementation

Submitted on Mar 28th 2025 at 17:54:17 UTC by @Berserk for Attackathon | Movement Labs

  • Report ID: #42903

  • Report Type: Blockchain/DLT

  • Report severity: High

  • Target: https://github.com/immunefi-team/attackathon-movement/tree/main/protocol-units/execution/maptos/opt-executor

  • Impacts:

    • Temporary freezing of network transactions by delaying one block by 500% or more of the average block time of the preceding 24 hours beyond standard difficulty adjustments

    • Causing network processing nodes to process transactions from the mempool beyond set parameters

Description

Brief/Intro

A vulnerability exists in Movement Protocol's transaction handling where attackers can DOS the network by submitting duplicate transactions with increased/incremented gas prices, exploiting a mismatched mempool implementation inherited from Aptos.

(the mempool in aptos, accepts dupplicate transaction, if the second transaction increases the unit gas price and will use it instead of the first one. In movment, both transactions will be added to the block and accepted.)

Vulnerability Details

The vulnerability stems from Movement using Aptos's mempool implementation despite having a different transaction lifecycle:

  1. Transaction Flow in Movement:

    • Transaction received via RPC

    • Initial validation (signature and format)

    • Processed by transaction_pipe.rs

    • If valid, directly passed to transaction_ingress (sent via channeltransaction_sender), and then directly be added to next block via transaction_ingress

  2. Key Implementation Issue:

protocol-units/execution/maptos/opt-executor/src/background/transaction_pipe.rs

As we can see in the submit_transaction() function, the final step in the checks would be to try to add the transaction to the mempool. and if it get accepted in the mempool we will directly send it using transaction_sender channel to the transaction_ingress to be posted in the next blob/block.

The vulnerability however occurs because we use the exact mempool implementation. in add_txn() we will do some checks and then will try to insert the given transaction in the transaction_store (let status = self.transactions.insert(txn_info)).

  1. Problematic Behavior: The vulnerability occurs because Aptos's mempool allows duplicate transactions if they increase gas price:

aptos-core/mempool/src/core_mempool/transaction_store.rs#L186-L200

In Movement's implementation, by the time a duplicate transaction with higher gas price is accepted, the original transaction has already been sent to transaction_ingress. This leads to both transactions being processed (added to the block and then executed), unlike Aptos where only one would be executed.

N.B by creating a batch of transaction with incrementing gas_unit_price, we will be able to bypass the validity check and submit multiple duplicates transactions to be processes by the working nodes

Impact

  • Attackers can flood the network with duplicate transactions (only pay gas for 1 transaction) -> DoS

  • Each duplicate will be processed during execution stage by the network processing node -> Causing network processing nodes to process transactions from the mempool beyond set parameters

Mitigation

As a dirty fix, we simply recommend that the function insert() in tansaction_store.rs from aptos_core be adjusted to also reject dupplicated transactions that increase the unit gas price.

References

  • protocol-units/execution/maptos/opt-executor/src/background/transaction_pipe.rs

  • aptos-core/mempool/src/core_mempool/transaction_store.rs

Proof of Concept

Proof of Concept

Attack Method:

  1. Use the RPC endpoint submit batch transactions

  2. Submit a batch of 10 identical Aptos transfer transactions

  3. Each subsequent transaction increases the unit_gas_field by 10

  4. The order of transactions (increasing gas price) is crucial for the attack

  5. All the transaction in the batch will be added to the block and executed (see fullnode log)

Coded Poc

  • fullnodelog: https://gist.github.com/aliX40/d5b618aa2bf01a6d83597b7f324ad6e9

  • main.rs Poc: https://gist.github.com/aliX40/37a95cc64d96757ec1837b6c8cff1ad9

  • cargo.toml for PoC: https://gist.github.com/aliX40/6fd53043b29b8e3c9ccf5f56a79c59c0

This is the result of running the poc in main.rs

The full-node logs confirm that all 10 transactions were included in the block and processed during execution, as shown in the compute_status_for_input_txns: output.

In the first batch, the initial transfer failed due to insufficient funds (though the sequence number was still incremented). All subsequent transactions were discarded for having outdated sequence numbers.

This is the results of the second batch read form the full node logs: (all the transactions in it have an old transaction nr and thus dropped )

As we can see we were able to submit 10 dupplicated through rpc bypassing mempool restrictions and forcing the full nodes to process those transactions during the execution stage.

( see full log here: https://gist.github.com/aliX40/d5b618aa2bf01a6d83597b7f324ad6e9)

N.B in each block the first transaction is a system generated one and that's why we have always keep(success) at first position in the compute_status_for_input_txns array

Was this helpful?