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

Users can create NFT collections on L1 relates to `ERC20` tokens on L2

Vulnerability Details

When Bridging NFTs from L1 -> L2, we are doing some checks. This includes ensuring a supported ERC721 interface. and tokens to be bridged be greater than 0.

But When bridging from L2 -> L1, these two checks do not exist. we neither detect the interface nor check that the tokenIds is greater than zero.

  1. When we are getting erc721_metadata without checking interface support, which is named introspection or ERC165 in EVM. And we are doing the function with Option returned value. So if there is an error occur when calling token_uri we just return an empty string.

bridge.cairo#L266-L270

fn erc721_metadata(
contract_address: ContractAddress,
token_ids: Option<Span<u256>>
) -> Option<ERC721Metadata> {
...
let erc721_metadata = erc721_metadata(collection_l2, Option::Some(token_ids));
let (name, symbol, base_uri, uris) = match erc721_metadata {
Option::Some(data) => (data.name, data.symbol, data.base_uri, data.uris),
❌️ Option::None => ("", "", "", array![].span())
};

And since most ERC20 tokens implement the name()and symbol() like that in ERC721 tokens

collection_manager.cairo#L67-L74

Option::Some(
ERC721Metadata {
@> name: erc721.name(),
@> symbol: erc721.symbol(),
base_uri: "",
uris
}
)

So if we passed an ERC20 token

  1. When we send tokens to the L2Bridge via escrow_deposit_tokens, we are not checking that tokens to be bridged are greater than zero, which is not the case in L1Bridge where there is a check to ensure tokens are greater than zero.

L2Bridge:
bridge.cairo#L402-L407

// @audit No check for `0` amount tokens
fn escrow_deposit_tokens( ... ) {
let to = starknet::get_contract_address();
let erc721 = IERC721Dispatcher { contract_address };
let mut i = 0_usize;
loop { ... };
}

L1Bridge:
Escrow.sol#L33

function _depositIntoEscrow( ... ) internal {
❌️ assert(ids.length > 0);
for (uint256 i = 0; i < ids.length; i++) {
uint256 id = ids[i];
}

So after collecting these two issues together we will be able to pass any address as collection_l2, and the tx will get processed successfully.

A side issue

The first issue is that 0 tokenIds amount is not checked in L2Bridge which is not the case in L1Bridge this is a side LOW severity issue, where tokenIds greater than 0 invariant is not checked in L2Bridge, which will allow users to create NFTs collection on L1 and mapp them into the L2 NFT collections without sending a single token.

Returning back to our issue, we proved that an ERC20 token will be passed till it reaches _depositIntoEscrow(). If tokenId is zero as we illustrated in the side issue, then matching an ERC20 on L2 to an ERC721 on L1 will occur. But the issue is not just 0 tokens, since transfer_from(address,address,u256) is the function signature used when transfering ERC721 tokens, this function is exactly the same for ERC20 tokens in Starknet, so The user can simply but tokenID with any value and that value of tokens will get transfered from him to the L2 Bridge.

Example:

  • tokenId = 1, will send 1 wei from the user to the Bridge

  • tokenId = 1e18 will send 1e18 token amount from the user to the Bridge

And the function will successed and that user will be able to receive an NFT on L1 represents the amount he deposited on L2. and we can simply withdraw then by briding an NFT from L1 to L2 to receive the ERC20.

This will result in deploying an ERC721 collection on l1 and tie it with that ERC20 address passed as collection_l2 by the called of L2Bridge::deposit_tokens(), which breaks the invariant of Linking NFTs collection on L1 and L2 and will link L1 NFT collection with ERC20 address on L2.

Impact

Linking ERC20 tokens on L2 to L1 addresses on L1

Tools Used

Manual Review

Recommendations

For solving the side issue: Check that the tokens to be bridges is greater than zero, this will ensure that this is an NFT collection as we are calling erc721.transfer_from and if did not support this interface it will revert the tx.

diff --git a/apps/blockchain/starknet/src/bridge.cairo b/apps/blockchain/starknet/src/bridge.cairo
index 23cbf8a..3f7c980 100644
--- a/apps/blockchain/starknet/src/bridge.cairo
+++ b/apps/blockchain/starknet/src/bridge.cairo
@@ -405,6 +405,7 @@ mod bridge {
from: ContractAddress,
token_ids: Span<u256>,
) {
+ assert!(token_ids.len() > 0, "No Tokens to Transfer");
let to = starknet::get_contract_address();
let erc721 = IERC721Dispatcher { contract_address };

And for the main issue, we should implement the SRC5 check that is like ERC165 in solidity, to insure that the address is an ERC721 not an ERC20 address.

NOTEL these are two separate issues not related to each other the first one is about not checking tokenIds array to be greater than zero, and the other is that not checking the interface of the address passed will allow linking ERC20 tokens on L2 Bridge as an NFTs on L1.

Updates

Lead Judging Commences

n0kto Lead Judge 9 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity
Assigned finding tags:

Informational / Gas

Please, do not suppose impacts, think about the real impact of the bug and check the CodeHawks documentation to confirm: https://docs.codehawks.com/hawks-auditors/how-to-determine-a-finding-validity A PoC always helps to understand the real impact possible.

Appeal created

alqaqa Submitter
9 months ago
n0kto Lead Judge
8 months ago
n0kto Lead Judge 8 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity
Assigned finding tags:

Informational / Gas

Please, do not suppose impacts, think about the real impact of the bug and check the CodeHawks documentation to confirm: https://docs.codehawks.com/hawks-auditors/how-to-determine-a-finding-validity A PoC always helps to understand the real impact possible.

invalid-empty-tokenIds-starknet-side

No real impact. Attacker will have to pay the deployment of the new contract even with 0 token, and it won’t have any interest do to that since he won’t take the control of the contract.

Support

FAQs

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