Skip to content

Instantly share code, notes, and snippets.

@sujithsomraaj
Created May 18, 2023 21:23
Show Gist options
  • Save sujithsomraaj/af2867cad6b13330433c029a6d000570 to your computer and use it in GitHub Desktop.
Save sujithsomraaj/af2867cad6b13330433c029a6d000570 to your computer and use it in GitHub Desktop.

Colluded Transmitter Can Exploit Socket DL

Bug Description

Where: https://github.com/SocketDotTech/socket-DL/blob/dev/contracts/socket/SocketDst.sol#L134 Where: https://github.com/SocketDotTech/socket-DL/blob/dev/contracts/socket/SocketDst.sol#L94

This issue allows colluded transmitters to execute malicious commands on any plug connected to socket DL bypassing switchboard validations.

Affected Switchboards: FastSwitchBoard. (Other might be affected as well).

Impact

I believe the impact would fit into Message getting corrupted during transit and Decapacitor unable to validate message. This could impact all the application built on top of socket's DL.

Recommendation

The general architectural design has a lot of moving parts including attestation of packetIds and the proposal of packetIds on switchboard and socket respectively. Checks should have to be added in place to validate that all the variables connecting those validations have tight links and are closely validated.

Few proposed changes could be

POC

Step-1

Propose a random packetId with a different remoteSlug, Eg: use BSC (56) on SocketDst.sol

        function propose(
        bytes32 packetId_,
        bytes32 root_,
        bytes calldata signature_
    ) external override 

Step-2

The watchers will trip the path and remove you as transmitters which is totally fine. They're doing what we expect. (if they trip global path, we cannot move further but hey that's griefing).

Step-3

Wait for the timeoutPeriod to elapse between the proposal time and the current block.timestamp. Now call the execute() which is an open function using a msgId of your choice that maps with the random root you proposed in Step-1.

        function execute(
        bytes32 packetId_,
        ISocket.MessageDetails calldata messageDetails_,
        bytes memory signature_
    ) external override {
        if (messageExecuted[messageDetails_.msgId])
            revert MessageAlreadyExecuted();
        messageExecuted[messageDetails_.msgId] = true;

        if (packetId_ == bytes32(0)) revert InvalidPacketId();
        if (packetIdRoots[packetId_] == bytes32(0)) revert PacketNotProposed();

-       uint32 remoteSlug = _decodeSlug(messageDetails_.msgId); /// Exploitable Place (use a different chain id: eg: optimism)
        address localPlug = _decodePlug(messageDetails_.msgId);

        PlugConfig storage plugConfig = _plugConfigs[localPlug][remoteSlug];
        
-       make sure your root proposed is equal to the packed message
        bytes32 packedMessage = hasher__.packMessage(
            remoteSlug,
            plugConfig.siblingPlug,
            chainSlug,
            localPlug,
            messageDetails_.msgId,
            messageDetails_.msgGasLimit,
            messageDetails_.executionFee,
            messageDetails_.payload
        );

        (address executor, bool isValidExecutor) = executionManager__
            .isExecutor(packedMessage, signature_);
        if (!isValidExecutor) revert NotExecutor();
-       the verify should pass since the path is not tripped for optimism & the timeout has elapsed.
        _verify(
            packetId_,
            remoteSlug,
            packedMessage,
            plugConfig,
            messageDetails_.decapacitorProof
        );
        _execute(
            executor,
            messageDetails_.executionFee,
            localPlug,
            remoteSlug,
            messageDetails_.msgGasLimit,
            messageDetails_.msgId,
            messageDetails_.payload
        );
    }

The transmitter can bypass all checks easily by just adding a random packetId and waiting for the timeperiod to elapse on the switchboard.

The primary vulnerable code is that the packetId's src chain and the msg id's chain are not validated anywhere and the root is validated just against the local state available on the socket contract. Read the root from switchboard to avoid bypassing decapacitor validations as well.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment