Since the current contract doesn't have any nonReentrant restrictions
Then the user can use reentrant and pay only once when multiple _execSellNftToMarket()s share the same transfer of funds
Here are some examples.
alice supplies a fake NFT_A
alice executes sellNftToMarket(), assuming sellAmount=10
execSellNftToMarket() inside the IERC721(collection).safeTransferFrom() for re-entry.
Note: The collection is an arbitrary contract, so safeTransferFrom() can be any code.
Reenter the execution of another Lien's sellNftToMarket(), and really transfer to amount=10
After the above re-entry, go back to step 3, this step does not need to actually pay, because step 4 has been transferred to sellAmount = 10, so it can pass this verification address(this).balance - ethBefore - wethBefore ! = amount
so that only one payment is made, reaching the sellNftToMarket() twice
$ forge test --match testReenter -vvv
Running 1 test for test/ParticleExchange.t.sol:ParticleExchangeTest
[PASS] testReenter() (gas: 1869563)
Logs:
before particleExchange balance: 100
before fakeNft balance: 10
after particleExchange balance: 90
after fakeNft balance: 20
after particleExchange lost: 10
after fakeNft steal: 10
Test result: ok. 1 passed; 0 failed; finished in 4.80ms
Tools Used
Recommended Mitigation Steps
Add nonReentrant restrictions to all Lien-related methods
Lines of code
https://github.com/code-423n4/2023-05-particle/blob/1caf678bc20c24c96fc8f6b0046383ff0e9d2a6f/contracts/protocol/ParticleExchange.sol#L291
Vulnerability details
Impact
re-enter steal funds
Proof of Concept
_execSellNftToMarket()
The number of changes in the balance to represent whether the corresponding amount has been receivedSince the current contract doesn't have any
nonReentrant
restrictions Then the user can use reentrant and pay only once when multiple_execSellNftToMarket()
s share the same transfer of fundsHere are some examples.
IERC721(collection).safeTransferFrom()
for re-entry. Note: The collection is an arbitrary contract, sosafeTransferFrom()
can be any code.address(this).balance - ethBefore - wethBefore ! = amount
so that only one payment is made, reaching the
sellNftToMarket()
twiceTest code:
add to ParticleExchange.t.sol
Test result: ok. 1 passed; 0 failed; finished in 4.80ms
Tools Used
Recommended Mitigation Steps
Add
nonReentrant
restrictions to all Lien-related methodsAssessed type
Reentrancy