Putty contest - pedroais's results

An order-book based american options market for NFTs and ERC20s.

General Information

Platform: Code4rena

Start Date: 29/06/2022

Pot Size: $50,000 USDC

Total HM: 20

Participants: 133

Period: 5 days

Judge: hickuphh3

Total Solo HM: 1

Id: 142

League: ETH

Putty

Findings Distribution

Researcher Performance

Rank: 28/133

Findings: 3

Award: $509.54

🌟 Selected for report: 0

πŸš€ Solo Findings: 0

Findings Information

Labels

bug
duplicate
2 (Med Risk)
disagree with severity

Awards

41.8933 USDC - $41.89

External Links

Lines of code

https://github.com/code-423n4/2022-06-putty/blob/3b6b844bc39e897bd0bbb69897f2deff12dc3893/contracts/src/PuttyV2.sol#L636 https://github.com/code-423n4/2022-06-putty/blob/3b6b844bc39e897bd0bbb69897f2deff12dc3893/contracts/src/PuttyV2.sol#L440

Vulnerability details

Impact

An attacker can make a scam with an option that uses a real NFT but can't be exercised.

Proof of Concept

In putty, users can make calls and puts on NFTs. The code also allows to include an array of ERC20s or/and ERC721's as part of a call or put. https://github.com/code-423n4/2022-06-putty/blob/3b6b844bc39e897bd0bbb69897f2deff12dc3893/contracts/src/PuttyV2.sol#L80

An attacker can make an order for a call with a rea NFT (let's sey a BAYC) but include a fake attacker token as part of the offer. The user seeing this would think he's buying a call on a BAYC and some useless token and decide to buy the call based on the BAYC. In the worst case, it would be reasonable to think he should at least get the BAYC when exercising.

This fake token could be crafted to return true or false at the attacker's will.

Example attacker token transfer function :

Function transferFrom(address from, address to) public { if (setTransferOn) return true Else return false }

function setTransfer(bool transfer){ serTransferOn = transfer }

In this way, the attacker can choose the return value for transferFrom.

Step-by-step attack:

The attacker makes a call order with a BAYC and this fake token as one of the ERC20's. Transfer is first set to true so the BAYC is transfered into the contract when the order is filled by a user. Users see the call order with a very attractive strike price and decides to pay the premium (let's say 1 eth) to take it.

A few days later the user decides to exercise his call. The fake token is now set to return false so the option exercising transaction reverts and the user can't get the bayc. https://github.com/code-423n4/2022-06-putty/blob/3b6b844bc39e897bd0bbb69897f2deff12dc3893/contracts/src/PuttyV2.sol#L440 After order expiration the attacker sets the token to return true and withdraws his bayc.

The attacker got the premium (1 eth) without incurring in any risk since the taker couldn't exercise his option.

An argument could be made this issue shouldn't be high severity since it requires the user to accept a malicious offer. I think the attack is high severity since the user would be reasonable to think if he is accepting an offer for a BAYC and some token that in the worse case he should at least get the BAYC. This attack doesn't require the user to be dumb or act recklessly but just normal functioning of the protocol. The fake token shouldn't prevent the user from exercising the real BAYC.

If this isn't fixed, the door is left open for a full gamma of scams in which a single malicious token can compromise an offer that includes other real and valuable NFT's or ERC20's.

One token failing shouldn't prevent the user from getting the real NFT from the option. When sensing assets out a try-catch mechanism should be implemented. In that way, if one of the malicious tokens reverts the real token can still be exercised.

#0 - outdoteth

2022-07-07T13:35:36Z

Duplicate: Setting malicious or invalid erc721Assets, erc20Assets or floorTokens prevents the option from being exercised: https://github.com/code-423n4/2022-06-putty-findings/issues/50

Findings Information

🌟 Selected for report: 0xc0ffEE

Also found by: horsefacts, pedroais, unforgiven

Labels

bug
duplicate
2 (Med Risk)
sponsor acknowledged

Awards

420.5209 USDC - $420.52

External Links

Lines of code

https://github.com/code-423n4/2022-06-putty/blob/3b6b844bc39e897bd0bbb69897f2deff12dc3893/contracts/src/PuttyV2.sol#L303 https://github.com/code-423n4/2022-06-putty/blob/3b6b844bc39e897bd0bbb69897f2deff12dc3893/contracts/src/PuttyV2.sol#L324 https://github.com/code-423n4/2022-06-putty/blob/3b6b844bc39e897bd0bbb69897f2deff12dc3893/contracts/src/PuttyV2.sol#L442 https://github.com/code-423n4/2022-06-putty/blob/3b6b844bc39e897bd0bbb69897f2deff12dc3893/contracts/src/PuttyV2.sol#L646

Vulnerability details

Impact

An attacker can force a flash loan and get temporary ownership of any NFT held inside putty and perform arbitrary logic with it.

Proof of Concept

A reentrancy gate can be exploited to provoke an unintended flash loan of any NFT held inside the contract.

Initial state : The putty contract is holding an NFT (from now on called "real NFT") from a legitimate option opened by regular users. An attacker can use reentrancy to get a flash loan of "real NFT" and perform arbitrary transactions with ownership of that NFT before giving it back (in the same transaction).

Attack :

An attacker makes and fulfills a fake order (he is the maker and the taker) with a base asset (the asset in which the premium and strike are paid) equal to an attacker contract. The base asset isn't a token but a contract that can perform arbitrary logic.

The order is a long call with a strike and premium of 0 for the NFT that's inside the contract. The erc721Assets[] array in the order includes the real NFT to be flashloaned and then a fake NFT allowing to do another external call. The attacker doesn't have the real NFT (it's inside the contract). The goal of the attack is to get a flash loan on that NFT and perform arbitrary logic with it with the goal of getting to claim any rewards or funds claimable by the real NFT. The erc721Assets[] array is composed of [realNFTAdddress, fakeNFTaddress]

Attack flow : -The attacker calls fillOrder() with the order made by himself. Fill order first mints the option NFT and then makes the base asset transfer. https://github.com/code-423n4/2022-06-putty/blob/3b6b844bc39e897bd0bbb69897f2deff12dc3893/contracts/src/PuttyV2.sol#L303 https://github.com/code-423n4/2022-06-putty/blob/3b6b844bc39e897bd0bbb69897f2deff12dc3893/contracts/src/PuttyV2.sol#L324

-This base asset transfer is the first reentrancy gate. The base asset is an attacker contract that will immediately call the exercise() function (the attacker is the maker and taker so he can do this with contracts). Note that the real NFT still didn't have to get transfered in since the base asset transfer happens before that.

-The exercise function sets exercised to true, burns the position and transfers the real NFT out. https://github.com/code-423n4/2022-06-putty/blob/3b6b844bc39e897bd0bbb69897f2deff12dc3893/contracts/src/PuttyV2.sol#L442

-The attacker now has ownership of the real NFT -Then the fake NFT is transferred out in the function loop which handles execution back to the attacker. At this point, he can perform arbitrary logic with the real NFT including cashing out on anything that requires being the owner of the NFT. This attack is only useful with NFTs that provide access to funds (this is why the issue is medium and not high severity).

https://github.com/code-423n4/2022-06-putty/blob/3b6b844bc39e897bd0bbb69897f2deff12dc3893/contracts/src/PuttyV2.sol#L646

After the arbitrary logic is performed by the attacker. The execution comes back to fillOrder() and assets have to be transferred inside the contract again, ending the forced flash loan. https://github.com/code-423n4/2022-06-putty/blob/3b6b844bc39e897bd0bbb69897f2deff12dc3893/contracts/src/PuttyV2.sol#L376

To recap the flow : The attacker calls fillOrder() and reenters the contract in the baseAssetTransfer by calling exercise() which means the NFT is transferred out before being transferred in. The real NFT is transferred out. The fake NFT transfer external call is used to perform arbitrary logic. Then the execution comes back to fillOrder() and the NFT is transferred into putty again.

The attack has no cost besides gas since both premium and strike are paid in the fake attacker contract.

Add reentrancy protection

#0 - GalloDaSballo

2022-07-05T01:45:10Z

"-The exercise function sets exercised to true, burns the position and transfers the real NFT out."

If fillOrder is being exited during the premium-transfer, the NFT is still not in the contract, meaning the "flashborrow" would revert at the last part of exercise because PuttyV2 still has to receive the NFT tokens.

Also, this would require a user that is wiling to be the counter-party to a fake token

#1 - Pedroais

2022-07-05T04:12:10Z

"-The exercise function sets exercised to true, burns the position and transfers the real NFT out."

If fillOrder is being exited during the premium-transfer, the NFT is still not in the contract, meaning the "flashborrow" would revert at the last part of exercise because PuttyV2 still has to receive the NFT tokens.

Also, this would require a user that is wiling to be the counter-party to a fake token

Hey Alex ! I think your points are not true 1 - the NFT was already in the contract, that's the idea of the attack, to make a flashborrow on an NFT that's already inside the contract (from an unrelated legitimate position). Transfer out is made before transfer in and won't revert since the contract was already holding the nft. Then the attacker uses the nft and transfers it back in.

2 - The attacker can make an order and take it himself so a counterparty is not needed

I actually sent a help request to edit the submission but it wasn't responded since the team is on holiday. The fake token isn't actually needed since the external call of ERC721 safe transfer can be used to allow the attacker to perform arbitrary logic on the NFT. The issue is still correct as described but could be a bit simpler.

So to recap lets use BAYC as an example:

BAYC 1 is inside the contract and an attacker wants to get a flash loan on it

-Attacker makes order with himself and reenters in base transfer to exercise -BAYC 1 is transfered out before in and it doesn't revert since it's already inside putty -The attacker performs arbitrary logic on the BAYC using the ERC721 safe transfer -Execution comes back to fillOrder() and the attacker returns the BAYC

#2 - GalloDaSballo

2022-07-05T14:28:30Z

I was wrong, finding is valid: -> fillOrder -> premium transfer execution hijack -> exerciseΒ -> Receive any NFT inside the contract -> Do w/e -> Execution returns to exercise

Because fee token can be the malicious token, this is a free flashloan

#3 - outdoteth

2022-07-06T19:32:33Z

Duplicate: It’s possible to flashloan all assets in the contract without paying a protocol fee: https://github.com/code-423n4/2022-06-putty-findings/issues/377

#0 - GalloDaSballo

2022-07-05T01:35:33Z

Admin functions are set as payable to save gas

#1 - outdoteth

2022-07-05T13:23:03Z

Confirming what @GalloDaSballo said.

#2 - rotcivegaf

2022-07-06T03:26:02Z

Duplicate of #259

#3 - HickupHH3

2022-07-13T06:59:43Z

User has no QA

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