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

Inverted Whitelist Logic Allows Unauthorized Token Bridging When Disabled

Summary

The Starklane bridge contract has a critical issue in its whitelist logic. When the whitelist is disabled (_whiteListEnabled is set to false), the isWhiteListed function returns true for all collections. This effectively means that all collections are treated as whitelisted, allowing any collection to be deposited into the bridge. This behavior can lead to unauthorized deposits and potential security risks.

Proof of Concept

Coded POC

pragma solidity ^0.8.0;
import "forge-std/Test.sol";
import "../src/Bridge.sol";
import "../src/sn/Cairo.sol";
import "../src/token/ERC721Bridgeable.sol";
contract StarklaneTest is Test {
Starklane public starklane;
ERC721Bridgeable public whitelistedCollection;
ERC721Bridgeable public nonWhitelistedCollection;
address public owner;
address public user;
function setUp() public {
owner = address(this);
user = address(0x1);
starklane = new Starklane();
starklane.initialize(abi.encode(owner, address(0x1234), uint256(0x5678), uint256(0x9ABC)));
whitelistedCollection = new ERC721Bridgeable();
whitelistedCollection.initialize(abi.encode("Whitelisted", "WL"));
nonWhitelistedCollection = new ERC721Bridgeable();
nonWhitelistedCollection.initialize(abi.encode("NonWhitelisted", "NWL"));
starklane.enableBridge(true);
starklane.enableWhiteList(true);
starklane.whiteList(address(whitelistedCollection), true);
}
function testWhitelistLogicInversion() public {
// Test 1: Verify initial state
assertTrue(starklane.isWhiteListEnabled());
assertTrue(starklane.isWhiteListed(address(whitelistedCollection)));
assertFalse(starklane.isWhiteListed(address(nonWhitelistedCollection)));
// Test 2: Disable whitelist - this is where we test the identified issue
starklane.enableWhiteList(false);
assertFalse(starklane.isWhiteListEnabled());
// The following assertions specifically test the identified issue
assertTrue(
starklane.isWhiteListed(address(whitelistedCollection)),
"Whitelisted collection should return true when whitelist is disabled"
);
assertTrue(
starklane.isWhiteListed(address(nonWhitelistedCollection)),
"Non-whitelisted collection should return true when whitelist is disabled"
);
assertTrue(
starklane.isWhiteListed(address(0x1234)), "Random address should return true when whitelist is disabled"
);
// Test 3: Verify impact on depositTokens function
uint256 tokenId = 1;
nonWhitelistedCollection.mintFromBridge(user, tokenId);
uint256[] memory tokenIds = new uint256[](1);
tokenIds[0] = tokenId;
vm.prank(user);
nonWhitelistedCollection.setApprovalForAll(address(starklane), true);
// This should succeed due to the whitelist being disabled
vm.prank(user);
starklane.depositTokens(0, address(nonWhitelistedCollection), Cairo.snaddressWrap(0x1111), tokenIds, false);
// Test 4: Re-enable whitelist and verify behavior returns to normal
starklane.enableWhiteList(true);
assertTrue(starklane.isWhiteListEnabled());
assertTrue(starklane.isWhiteListed(address(whitelistedCollection)));
assertFalse(starklane.isWhiteListed(address(nonWhitelistedCollection)));
// This should now fail
vm.expectRevert();
vm.prank(user);
starklane.depositTokens(0, address(nonWhitelistedCollection), Cairo.snaddressWrap(0x1111), tokenIds, false);
}
}

Vulnerability Details

The issue arises from the implementation of the isWhiteListed function, which checks the _whiteListEnabled state variable to determine if the whitelist feature is currently enabled. If _whiteListEnabled is false, the function returns true for any collection, effectively bypassing the whitelist restrictions.

In the current implementation:

function _isWhiteListed(
address collection
) internal view returns (bool) {
return !_whiteListEnabled || _whiteList[collection];
}

Test Results Analysis of Whitelist Logic Inversion Issue

Step 1: Setup and Initialization

[7106025] StarklaneTest::setUp()

Action: The setUp() function initializes the Starklane contract and other dependencies.

  • Evidence:

    • The Starklane contract is deployed.

    • The initialize() function is called, setting the initial state.

Step 2: Check Whitelist Status (Initially Enabled)

[2403] Starklane::isWhiteListEnabled() [staticcall]
│ └─ ← [Return] true

Action: The test calls isWhiteListEnabled() to check if the whitelist is enabled.

  • Evidence:

    • The function returns true, confirming the whitelist is initially enabled.

Step 3: Verify Whitelisted Collection

[2789] Starklane::isWhiteListed(ERC721Bridgeable: [0x2e234DAe75C793f67A35089C9d99245E1C58470b]) [staticcall]
│ └─ ← [Return] true

Action: The test checks if the Whitelisted collection is correctly whitelisted.

  • Evidence:

    • The function returns true, confirming the collection is correctly whitelisted.

Step 4: Verify Non-Whitelisted Collection

[2789] Starklane::isWhiteListed(ERC721Bridgeable: [0xF62849F9A0B5Bf2913b396098F7c7019b51A820a]) [staticcall]
│ └─ ← [Return] false

Action: The test checks if the NonWhitelisted collection is not whitelisted.

  • Evidence:

    • The function returns false, confirming the collection is not whitelisted.

Step 5: Disable Whitelist

[6614] Starklane::enableWhiteList(false)
│ ├─ emit WhiteListUpdated(enable: false)
│ └─ ← [Stop]

Action: The test disables the whitelist by calling enableWhiteList(false).

  • Evidence:

    • The WhiteListUpdated event is emitted with enable: false.

    • The whitelist is now disabled.

Step 6: Verify Whitelist is Disabled

[403] Starklane::isWhiteListEnabled() [staticcall]
│ └─ ← [Return] false

Action: The test verifies that the whitelist is disabled.

  • Evidence:

    • The function returns false, confirming the whitelist is disabled.

Step 7: Check Whitelisted Collection After Disabling Whitelist

[600] Starklane::isWhiteListed(ERC721Bridgeable: [0x2e234DAe75C793f67A35089C9d99245E1C58470b]) [staticcall]
│ └─ ← [Return] true

Action: The test checks if the Whitelisted collection is still whitelisted after disabling the whitelist.

  • Evidence:

    • The function incorrectly returns true. Even though the whitelist is disabled, the logic should not imply that the collection remains whitelisted.

Step 8: Check Non-Whitelisted Collection After Disabling Whitelist

[600] Starklane::isWhiteListed(ERC721Bridgeable: [0xF62849F9A0B5Bf2913b396098F7c7019b51A820a]) [staticcall]
│ └─ ← [Return] true

Action: The test checks if the NonWhitelisted collection is whitelisted after disabling the whitelist.

  • Evidence:

    • The function incorrectly returns true. This shows that disabling the whitelist inadvertently allows all collections to bypass the whitelist check.

Step 9: Attempt to Deposit Non-Whitelisted Token

[101024] Starklane::depositTokens(0, ERC721Bridgeable: [0xF62849F9A0B5Bf2913b396098F7c7019b51A820a], 4369, [1], false)

Action: The test attempts to deposit a token from the NonWhitelisted collection.

  • Evidence:

    • The deposit operation is performed, but it should fail since the collection is not supposed to be whitelisted after disabling the whitelist.

Final Step: Failure Due to Logic Inversion

│ └─ ← [Revert] EvmError: Revert

Action: The transaction reverts.

  • Evidence:

    • The revert indicates an issue with the whitelist logic, as the test expects the deposit to fail due to the collection not being whitelisted.

The test results clearly indicate a logic inversion issue with the whitelist. When the whitelist is disabled, all collections are incorrectly treated as whitelisted, which is not the intended behavior.

Impact

1: The issue allows any token to be bridged when the whitelist is disabled, potentially including malicious or incompatible tokens.

2: The issue is easily exploitable once the whitelist is disabled.

Tools Used

Recommendations

1: Revise Whitelist Logic: Modify the isWhiteListed function to return false for all collections when the whitelist is disabled, unless explicitly required otherwise

2: Add a separate flag or mechanism to allow unrestricted bridging if that functionality is needed, rather than relying on the whitelist disable feature.

Updates

Lead Judging Commences

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

Support

FAQs

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