NFTBridge
60,000 USDC
View results
Submission Details
Severity: high
Invalid

Malicious actor can bypass address verification in `collection_manager::verify_collection_address()`

Summary

Vulnerability Detail

The verify_collection_address() function in the collection_manager.cairo contract is responsible for validating the addresses of collections during the bridging process. It takes four parameters: l1_req (L1 address from the request), l2_req (L2 address from the request), l1_bridge (L1 address stored in the bridge), and l2_bridge (L2 address stored in the bridge).

The function is designed to handle different scenarios:

  1. When l2_req is zero (new collection being bridged)

  2. When both l1_req and l2_req are non-zero (existing collection being bridged)

However, the function fails to properly handle a crucial edge case: when l2_req is non-zero but l2_bridge is zero. This oversight can lead to a situation where a malicious actor can bypass the address verification process, potentially allowing unauthorized collections to be bridged or causing valid collections to be incorrectly rejected.

The root cause of the issue lies in the following code section:

if l2_req.is_zero() {
if l2_bridge.is_zero() {
// It's the first token of the collection to be bridged.
return ContractAddressZeroable::zero();
}
} else {
// L1 address is present, and L2 address too.
if l2_bridge != l2_req {
panic!("Invalid collection L2 address");
}
if l1_bridge != l1_req {
panic!("Invalid collection L1 address");
}
}

The function assumes that if l2_req is non-zero, l2_bridge must also be non-zero. However, this assumption is not enforced, creating a vulnerability that can be exploited.

Impact

This vulnerability allows a malicious actor to potentially bridge unauthorized collections or cause valid collections to be incorrectly rejected. By manipulating the l2_req parameter, an attacker could bypass the address verification process, leading to a breach in the integrity of the bridging mechanism.

Proof of Concept

  1. An attacker initiates a bridge request with a non-zero l1_req (e.g., a valid L1 address) and a non-zero l2_req (e.g., an address controlled by the attacker).

  2. The l2_bridge stored in the contract is zero (indicating no previous bridging for this collection).

  3. The verify_collection_address() function is called with these parameters.

  4. The function enters the else block because l2_req is non-zero.

  5. The checks for l2_bridge != l2_req and l1_bridge != l1_req are bypassed because l2_bridge is zero.

  6. The function returns l2_bridge (which is zero), effectively allowing the attacker to bridge an unauthorized collection.

Tools Used

Manual review

Recommendation

To mitigate this vulnerability, an additional check should be added to ensure that l2_bridge is non-zero when l2_req is non-zero. Here's the recommended fix:

fn verify_collection_address(
l1_req: EthAddress,
l2_req: ContractAddress,
l1_bridge: EthAddress,
l2_bridge: ContractAddress,
) -> ContractAddress {
// L1 address must always be set as we receive the request from L1.
if l1_req.is_zero() {
panic!("L1 address cannot be 0");
}
// L1 address is present in the request and L2 address is not.
if l2_req.is_zero() {
if l2_bridge.is_zero() {
// It's the first token of the collection to be bridged.
return ContractAddressZeroable::zero();
}
} else {
// L1 address is present, and L2 address too.
+ if l2_bridge.is_zero() {
+ panic!("L2 bridge address cannot be 0 when L2 request address is non-zero");
+ }
if l2_bridge != l2_req {
panic!("Invalid collection L2 address");
}
if l1_bridge != l1_req {
panic!("Invalid collection L1 address");
}
}
l2_bridge
}

This additional check ensures that the function properly handles all possible scenarios, preventing potential exploits and maintaining the integrity of the address verification process.

Updates

Lead Judging Commences

n0kto Lead Judge 12 months ago
Submission Judgement Published
Invalidated
Reason: Design choice

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.