Open code423n4 opened 1 year ago
PoC -> Marked as primary
hansfriese marked the issue as primary issue
hansfriese changed the severity to 3 (High Risk)
hansfriese marked the issue as satisfactory
wukong-particle marked the issue as disagree with severity
Acknowledged the issue and agreed with the suggestion. But this might be med severity, since it's contained with only this borrower's asset and fund, not speared to protocol level.
wukong-particle marked the issue as sponsor confirmed
@wukong-particle For the severity, I suggest HIGH is appropriate.
According to C4 guideline:
High: Assets can be stolen/lost/compromised directly (or indirectly if there is a valid attack path that does not have hand-wavy hypotheticals).
For this vulnerability, a malicious borrower can prevent the lender from taking action for defaulted lien. So a borrower can wait as long as he wants and the lender can not claim NFT or ETH. The likelihood and the impact are both high. I would like to note that there is no cost for the borrower for this exploit.
hansfriese marked the issue as selected for report
@hansfriese thanks for the suggestion, I agree. We can mark this issue high severity.
Fixed with https://github.com/Particle-Platforms/particle-exchange-protocol/pull/13, want to check with you guys the changes we made. There are three major modifications here -
(1) as suggested, we put the trader earning into a pull based approach -- we created a mapping(address => uint256) public accountBalance;
, and do accountBalance[account] += gainedAmount
for trader profit. In addition, besides auctionBuyNft
and withdrawEthWithInterest
, we default all trader profit (i.e., from buyNftFromMarket
, repayWithNft
) into accountBalance, as opposed to directly transfer back, for consistency.
(2) we merge accruedInterest
into accountBalance
too, for simplicity. So this is like each account has a wallet in the contract. For treasury calculation, we move all calculation into interest accrual time as opposed to accountBalance withdraw time, so that treasury still only takes the interest part, but not the trader gain, as before.
(3) at sellNftToMarket
, by default, the trader will use the balance from the contract as their margin, and if the balance is not enough, trader can choose to top up the margin. Thus, margin will be an input into the function, as opposed to msg.value
. The logic is as follows
if (margin > msg.value + accountBalance[msg.sender]) {
revert Errors.Overspend();
}
if (margin > msg.value) {
// newly deposited value not enough, use from account balance
accountBalance[msg.sender] -= (margin - msg.value);
} else if (margin < msg.value) {
// newly deposited value more than enough, top up account balance
accountBalance[msg.sender] += (msg.value - margin);
}
@rbserver @romeroadrian @bin2chen66 @d3e4 @minhquanym you guys all pointed out this issue, want to check if the fix makes sense to you, thanks!
Lines of code
https://github.com/code-423n4/2023-05-particle/blob/bbd1c01407a017046c86fdb483bbabfb1fb085d8/contracts/protocol/ParticleExchange.sol#L688-L748 https://github.com/code-423n4/2023-05-particle/blob/bbd1c01407a017046c86fdb483bbabfb1fb085d8/contracts/protocol/ParticleExchange.sol#L192-L223
Vulnerability details
Impact
When
lien.borrower
is a contract, itsreceive
function can be coded to conditionally revert based on a state boolean variable controlled bylien.borrower
's owner. As long aspayback > 0
is true,lien.borrower
'sreceive
function would be called when calling the followingParticleExchange.auctionBuyNft
function. In this situation, if the owner oflien.borrower
intends to DOS theParticleExchange.auctionBuyNft
function call, especially whenlien.credit
is low or 0, she or he would makelien.borrower
'sreceive
function revert.https://github.com/code-423n4/2023-05-particle/blob/bbd1c01407a017046c86fdb483bbabfb1fb085d8/contracts/protocol/ParticleExchange.sol#L688-L748
Moreover, after the auction of the lien is concluded, calling the following
ParticleExchange.withdrawEthWithInterest
function can calllien.borrower
'sreceive
function as long aslien.credit > payableInterest
is true. In this case, the owner oflien.borrower
can also makelien.borrower
'sreceive
function revert to DOS theParticleExchange.withdrawEthWithInterest
function call.https://github.com/code-423n4/2023-05-particle/blob/bbd1c01407a017046c86fdb483bbabfb1fb085d8/contracts/protocol/ParticleExchange.sol#L192-L223
The similar situations can happen if
lien.borrower
does not implement thereceive
orfallback
function intentionally in whichlien.borrower
's owner is willing to pay some position margin, which can be a low amount depending on the corresponding lien, to DOS theParticleExchange.auctionBuyNft
andParticleExchange.withdrawEthWithInterest
function calls.Proof of Concept
The following steps can occur for the described scenario for the
ParticleExchange.auctionBuyNft
function. The situation for theParticleExchange.withdrawEthWithInterest
function is similar.lien.borrower
for a lien.lien.borrower
'sreceive
function revert through changing the controlled state boolean variable for launching the DOS attack to true.ParticleExchange.auctionBuyNft
function calls all revert.ParticleExchange.auctionBuyNft
transaction is executed at the last second of the auction period, the auction is DOS'ed.Tools Used
VSCode
Recommended Mitigation Steps
The
ParticleExchange.auctionBuyNft
andParticleExchange.withdrawEthWithInterest
functions can be updated to record thepayback
andlien.credit - payableInterest
amounts that should belong tolien.borrower
instead of directly sending these amounts tolien.borrower
. Then, a function can be added to letlien.borrower
to call and receive these recorded amounts.Assessed type
DoS