Delegate - d4r3d3v1l's results

Securing onchain identities by linking cold and hot wallets

General Information

Platform: Code4rena

Start Date: 05/09/2023

Pot Size: $50,000 USDC

Total HM: 2

Participants: 16

Period: 6 days

Judge: GalloDaSballo

Total Solo HM: 2

Id: 284

League: ETH

Delegate

Findings Distribution

Researcher Performance

Rank: 2/16

Findings: 1

Award: $17,118.75

๐ŸŒŸ Selected for report: 1

๐Ÿš€ Solo Findings: 1

Findings Information

๐ŸŒŸ Selected for report: d4r3d3v1l

Labels

bug
2 (Med Risk)
downgraded by judge
selected for report
sponsor confirmed
edited-by-warden
M-01

Awards

17118.75 USDC - $17,118.75

External Links

Lines of code

https://github.com/code-423n4/2023-09-delegate/blob/main/src/DelegateToken.sol#L134 https://github.com/code-423n4/2023-09-delegate/blob/main/src/DelegateToken.sol#L168 https://github.com/code-423n4/2023-09-delegate/blob/main/src/libraries/DelegateTokenStorageHelpers.sol#L149

Vulnerability details

Impact

There is no way to revoke the approval which given via DelegateToken.approve(address,delegateTokenId). They can able call the DelegateToken.transferFrom even the tokenHolder revoke the permission using the DelegateToken.setApprovalForAll

if the spender address is approved by the PT token ,we can call the DelegateToken.withdraw.

Proof of Concept

Alice is the token Holder.

Alice approves Bob via DelegateToken.setApprovalForAll(Bob,true).

Bob approves himself using DelegateToken.approve(Bob,delegateTokenId)

Alice revokes the Bob approval by calling DelegateToken.setApprovalForAll(Bob,false);

Now Bob can still calls the DelegateToken.transferFrom(Alice,to,delegateTokenId) which is subjected to call only by approved address.

The transfer will be successful.

Code details :

https://github.com/code-423n4/2023-09-delegate/blob/main/src/libraries/DelegateTokenStorageHelpers.sol#L143


function revertNotApprovedOrOperator(
        mapping(address account => mapping(address operator => bool enabled)) storage accountOperator,
        mapping(uint256 delegateTokenId => uint256[3] info) storage delegateTokenInfo,
        address account,
        uint256 delegateTokenId
    ) internal view {
        if (msg.sender == account || accountOperator[account][msg.sender] || msg.sender == readApproved(delegateTokenInfo, delegateTokenId)) return;
        revert Errors.NotApproved(msg.sender, delegateTokenId);
    }

Even after revoking the approval for operator using setApprovalAll this

msg.sender == readApproved(delegateTokenInfo, delegateTokenId) will be true and able to call transferFrom function.

Test function :*


function testFuzzingTransfer721(
        address from,
        address to,
        uint256 underlyingTokenId,
        bool expiryTypeRelative,
        uint256 time
    ) public {
        vm.assume(from != address(0));
        vm.assume(from != address(dt));

        (
            ,
            /* ExpiryType */
            uint256 expiry, /* ExpiryValue */

        ) = prepareValidExpiry(expiryTypeRelative, time);
        mockERC721.mint(address(from), 33);

        vm.startPrank(from);
        mockERC721.setApprovalForAll(address(dt), true);

        vm.stopPrank();
        vm.prank(from);
        dt.setApprovalForAll(address(dt), true);
        vm.prank(from);
        uint256 delegateId1 = dt.create(
            DelegateTokenStructs.DelegateInfo(
                from,
                IDelegateRegistry.DelegationType.ERC721,
                from,
                0,
                address(mockERC721),
                33,
                "",
                expiry
            ),
            SALT + 1
        );

        vm.prank(address(dt));
        dt.approve(address(dt), delegateId1);

        vm.prank(from);

        dt.setApprovalForAll(address(dt), false);
        address tmp = dt.getApproved(delegateId1);
        console.log(tmp);
        vm.prank(address(dt));
        dt.transferFrom(from, address(0x320), delegateId1);
    }

Tools Used

Manual Analysis

If token Holder revokes the approval for a operator using DelegateToken.setApprovalForAll , revoke the all the approvals(DelegateToken.approve) which is done by the operator.

Assessed type

Access Control

#0 - c4-judge

2023-09-18T11:10:58Z

GalloDaSballo marked the issue as satisfactory

#1 - c4-judge

2023-09-18T11:11:11Z

GalloDaSballo removed the grade

#2 - c4-sponsor

2023-09-20T23:34:04Z

0xfoobar (sponsor) confirmed

#3 - 0xfoobar

2023-09-20T23:34:14Z

Need to double-check but looks plausible

#4 - GalloDaSballo

2023-09-29T10:25:09Z

In contrast to ERC721, which only allows the owner to changeย approve https://github.com/OpenZeppelin/openzeppelin-contracts/blob/ef3e7771a7e2d9b30234a57f96ee7acf5dddb9ed/contracts/token/ERC721/ERC721.sol#L448-L454

DT.approve allows the operator to set themselves as approved, which can technically be undone via a multicall but may not be performed under normal usage

#5 - c4-judge

2023-10-01T18:49:58Z

GalloDaSballo changed the severity to 2 (Med Risk)

#6 - GalloDaSballo

2023-10-01T18:51:27Z

Leaning towards Medium Severity as the finding requires:

  • Alice approvesForAll Bob
  • Bob approves self
  • Alice revokes Bobs approvalForAll
  • Alice doesn't revoke the _approve that Bob gave to self

Notice that any transfer would reset the approval as well

#7 - c4-judge

2023-10-02T07:38:48Z

GalloDaSballo marked the issue as selected for report

#8 - 0xfoobar

2023-10-03T22:40:43Z

A note about how ERC721 works: there are two different types of approvals. approve() lets an account move a single tokenId, while setApprovalForAll() marks an address as an operator to move all tokenIds. We can note the difference in the core OpenZeppelin ERC721 base contract, two separate mappings: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC721/ERC721.sol#L32-L34

So plausible that an operator could be permitted to add new tokenid-specific approvals, but see your point that OZ ERC721 doesn't allow this by default and so could lead to confusing states that are possible but not fun to get out of.

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax ยฉ 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter