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
- A cross-chain bridge contract does not lock setTrustedRemote().
- An attacker (or a compromised owner) sets trustedRemote[101] = malicious address.
- The attacker crafts a fake LayerZero message from chain ID 101.
- Since the path is now trusted, the payload is accepted and executed by the target chain.
- 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