Open code423n4 opened 1 year ago
in the case of moonbirds, but also applying to all nfts that would be flash enabled, there is a security hooks mechanism that lets us query the state of the underlying asset, we would have to write and deploy a security hook for the moon bird contract that would check the nesting status, we would then prohibit the transaction because the state of the nft is changed.
the security hook can fetch any data about an nft from calls and then compare them after the nft has been returned.
androolloyd marked the issue as sponsor disputed
This is a remarkable and very creative finding. But considering this is specific to MoonBird's behavior and that Astaria has a securityHook
for this kind of case, downgrading to low.
Picodes changed the severity to QA (Quality Assurance)
Picodes marked the issue as grade-a
Lines of code
https://github.com/code-423n4/2023-01-astaria/blob/1bfc58b42109b839528ab1c21dc9803d663df898/src/CollateralToken.sol#L299 https://github.com/code-423n4/2023-01-astaria/blob/1bfc58b42109b839528ab1c21dc9803d663df898/src/ClearingHouse.sol#L217 https://github.com/code-423n4/2023-01-astaria/blob/1bfc58b42109b839528ab1c21dc9803d663df898/src/CollateralToken.sol#L345
Vulnerability details
Impact
Adversary can game the flashAuction feature to block further flashAuction after trading collateral token and make liquidatorNFTClaim function revert and block liquidation if the NFT is Moonbird
Proof of Concept
In the current implementation, according to
https://docs.astaria.xyz/docs/protocol-mechanics/flashactions
The corresponding implementation is in CollateralToken.sol
the code above did a few things: make sure the flashAction is enabled, make sure the NFT is not in auction, make sure owner the owner of the collateral token can call flashAction using onlyOwner(collateralId) modifier, then the code transfer the NFT to the receiver
In the end, the code check if the caller of the flashAction return the NFT by checking the ownership of the NFT
We need to look into ClearingHouse(s.clearingHouse[collateralId]).transferUnderlying function call:
In ClearingHouse.sol
the crucial part is that the normal safeTransferFrom is used.
However, if the tokenContract is Moonbird NFT, the adversary can game the flashAucton feature to block further flashAction after trading the collateral token.
We need to look into the MoonBird NFT.
Moonbird NFT is one of the bluechip that has a large communtiy and high trading volume.
https://cointelegraph.com/news/bluechip-nft-project-moonbirds-signs-with-hollywood-talent-agents-uta
This NFT MoonBird has a feature: bird nesting.
https://www.moonbirds.xyz/
The important things is that: when nesting is activated, the normal transfer is disabled (meaing the NFT trading for nested bird is disabled because normal transfer will revert) but user can use a speical function safeTransferWhileNesting to move NFT around.
How is this feature implemented?
Here is the function toggleNesting function
https://etherscan.io/address/0x23581767a106ae21c074b2276d25e5c3e136a68b#code#F1#L319
When the nesting is active, normal transfer is disabled. If the user tries to transfer while nesting, transaction will revert in _beforeTokenTransfers
https://etherscan.io/address/0x23581767a106ae21c074b2276d25e5c3e136a68b#code#F1#L272
Here is an example transaction that revert when user tries to transfer while nesting.
https://etherscan.io/tx/0x21caf32e33f37d808054bf9ef33273a1347961dfc914819fc972cc5f44d7e62e
Here is an example transaction that users try to toggle nesting
https://etherscan.io/tx/0xefc7b7e683b17b623b96c628e684613ab7e637f5e74dec7abdedebb99b4e64d1
If the NFT bird is in nesting, the user can call a speical function safeTransferWhileNesting to move NFT around.
https://etherscan.io/address/0x23581767a106ae21c074b2276d25e5c3e136a68b#code#F1#L258
example transaction for safeTransferWhileNesting
https://etherscan.io/tx/0x8d80610ff84c0f874fef28b351852e206fc38fe2818627881e437ed661d77fda
Ok, now we can formalize the exploit path:
The impact is severe, the new owner cannot use flashAuction.
In fact, all ERC721(tokenContract).safeTransferFrom call is blocked, the NFT cannot be liquidated because normal liquidation also requires transferFrom to change the ownership and settle the trade.
liquidatorNFTClaim also revert because the function calls:
which calls _releaseToAddress
which calls:
which calls transferUnderlying, which use ERC721(tokenContract).safeTransferFrom, which reverts while the moonbird nesting is on.
the function releaseToAddress is blocked and the NFT is locked in CollateralToken
Who have such incentives to exploits this feature? For example, a user use moonbird to borrow 10 ETH, but he does not want to pay the outstanding debt and does not want to get the NFT liquidated. He can exploit the moonbird to lock NFT. Without liquidating the NFT and locking the NFT, the lender bears the loss on the bad debt.
Le us see the coded POC:
Let us see the normal flashAction flow and then see how the above-mentioned exploit path can block further flashAction.
First, add the smart contract code in AstariaTest.t..sol
This code just transfer the NFT back in the onFlashAction callback function.
https://github.com/code-423n4/2023-01-astaria/blob/1bfc58b42109b839528ab1c21dc9803d663df898/src/test/AstariaTest.t.sol#L38
Then we add the test case in AstariaTest.t.sol
https://github.com/code-423n4/2023-01-astaria/blob/1bfc58b42109b839528ab1c21dc9803d663df898/src/test/AstariaTest.t.sol#L367
As we can see, we try to flashAction twice, and both of flashAction works fine if we run the test.
We can run the test:
the test result is:
Now we see if the underlying NFT is moonbird, how can we implement the exploit path mentioned above.
First, we implement a mock NFT that has toggleNest function in TestHelper.t.sol
https://github.com/code-423n4/2023-01-astaria/blob/1bfc58b42109b839528ab1c21dc9803d663df898/src/test/TestHelpers.t.sol#L89
Then we create a smart contract and in the smart contract callback, we toggleNesting, and use safeTransferWhileNesting to transfer the NFT back. This is the core exploit
https://github.com/code-423n4/2023-01-astaria/blob/1bfc58b42109b839528ab1c21dc9803d663df898/src/test/AstariaTest.t.sol#L38
Then we add the test below:
https://github.com/code-423n4/2023-01-astaria/blob/1bfc58b42109b839528ab1c21dc9803d663df898/src/test/AstariaTest.t.sol#L367
We make two modification here:
we use MockMoonbird instead of normal NFT, we use the malicious contract that implements the moonbird nesting callback function
and
we run the test again and we can see the second flashAction is blocked.
and the test result is:
Tools Used
Manual Review, Foundry
Recommended Mitigation Steps
We recommend the protocol whitelist the NFT address that can be used in the protocol to make sure such edge case does not impact the borrower and lender.