Skip to content

LayerZero Trusted Remote Not Locked

id: LS12C
title: LayerZero Trusted Remote Not Locked 
baseSeverity: C
category: cross-chain
language: solidity
blockchain: [ethereum, bsc, arbitrum, optimism, polygon]
impact: Remote contract impersonation, asset loss, protocol compromise
status: draft
complexity: high
attack_vector: external
mitigation_difficulty: medium
versions: [">=0.8.0", "<=0.8.25"]
cwe: CWE-345
swc: SWC-136

πŸ“ Description

  • In LayerZero-powered cross-chain applications, developers must explicitly define and lock trusted remotes via setTrustedRemote() or setTrustedRemoteAddress() to ensure that only pre-approved contracts from specific chains can deliver messages.
  • If a contract does not lock its trusted remote configurationβ€”or allows it to be changed laterβ€”an attacker can spoof LayerZero messages from malicious contracts on other chains, resulting in:
  • Unauthorized message execution
  • Funds or tokens bridged to attacker-controlled addresses
  • Bypassing application-level access controls
  • This vulnerability occurs when:
  • setTrustedRemote() is callable by non-owner or permanently callable
  • A trusted remote is never set or incorrectly validated in _lzReceive()

🚨 Vulnerable Code

pragma solidity ^0.8.0;

import "@layerzerolabs/solidity-examples/contracts/lzApp/NonblockingLzApp.sol";

contract VulnerableBridge is NonblockingLzApp {
    constructor(address _endpoint) NonblockingLzApp(_endpoint) {}

    function setTrustedRemote(uint16 _chainId, bytes calldata _path) external onlyOwner {
        trustedRemoteLookup[_chainId] = _path; // ❌ can be modified any time
    }

    function _nonblockingLzReceive(
        uint16 srcChainId,
        bytes memory srcAddress,
        uint64,
        bytes memory payload
    ) internal override {
        require(
            keccak256(srcAddress) == keccak256(trustedRemoteLookup[srcChainId]),
            "Invalid source"
        );
        // Process payload
    }
}

πŸ§ͺ Exploit Scenario

  1. A cross-chain bridge contract does not lock setTrustedRemote().
  2. An attacker (or a compromised owner) sets trustedRemote[101] = malicious address.
  3. The attacker crafts a fake LayerZero message from chain ID 101.
  4. Since the path is now trusted, the payload is accepted and executed by the target chain.
  5. The attacker drains funds or mints tokens to themselves.

Assumptions:

  • LayerZero endpoint forwards message
  • trustedRemoteLookup is not permanently locked or controlled
  • No nonce or additional validation is enforced in payload

βœ… Fixed Code

pragma solidity ^0.8.0;

contract SecureBridge is NonblockingLzApp {
    mapping(uint16 => bool) public isRemoteLocked;

    constructor(address _endpoint) NonblockingLzApp(_endpoint) {}

    function setTrustedRemote(uint16 _chainId, bytes calldata _path) external onlyOwner {
        require(!isRemoteLocked[_chainId], "Remote already locked");
        trustedRemoteLookup[_chainId] = _path;
        isRemoteLocked[_chainId] = true; // βœ… permanently lock remote
    }

    function _nonblockingLzReceive(
        uint16 srcChainId,
        bytes memory srcAddress,
        uint64,
        bytes memory payload
    ) internal override {
        require(
            keccak256(srcAddress) == keccak256(trustedRemoteLookup[srcChainId]),
            "Invalid source"
        );
        // Process payload
    }
}

🧭 Contextual Severity

- context: "LayerZero bridge contract on mainnet with unlocked remotes"
  severity: C
  reasoning: "Enables full cross-chain spoofing and fund theft."
- context: "Testnet or dev-only deployments"
  severity: L
  reasoning: "No real value at risk, but pattern should be corrected."
- context: "Contract uses one-time initialization or `lockRemote()`"
  severity: M
  reasoning: "Exposure window exists but mitigated post-setup."

πŸ›‘οΈ Prevention

Primary Defenses

  • Lock trusted remotes permanently after deployment
  • Do not expose setTrustedRemote() or setTrustedRemoteAddress() to external modification unless via trusted governance

Additional Safeguards

  • Add signature validation to message payloads
  • Whitelist remote chain IDs and contract addresses at compile-time for critical deployments

Detection Methods

  • Review contracts that inherit from NonblockingLzApp or LzApp and check setTrustedRemote() visibility and mutability
  • Tools: Manual audit, Slither (custom rule), symbolic analysis

πŸ•°οΈ Historical Exploits

  • Name: LayerZero Trusted Remote Misconfiguration
  • Date: 2023-06
  • Loss: ~$180,000
  • Post-mortem: Link to post-mortem
  • Name: Maia Protocol DoS via LayerZero Unlocked Remotes
  • Date: 2023-09
  • Loss: ~$50,000
  • Post-mortem: Link to post-mortem

πŸ“š Further Reading


βœ… Vulnerability Report

id: LS12C
title: LayerZero Trusted Remote Not Locked 
severity: C  
score:
impact: 5    
exploitability: 4 
reachability: 5 
complexity: 3  
detectability: 3 
finalScore: 4.4

πŸ“„ Justifications & Analysis

  • Impact: Spoofed messages can trigger unauthorized logic (e.g., fund transfer, mint, config change)
  • Exploitability: Moderately easy for attackers via governance or upgrade missteps
  • Reachability: Applies broadly across LayerZero-based apps unless mitigated
  • Complexity: Requires cross-chain architectural understanding
  • Detectability: Often missed unless the audit specifically focuses on trusted remote lock enforcement