Sablier

Sablier
DeFiFoundry
53,440 USDC
View results
Submission Details
Severity: low
Valid

Attacker can reduce total token can be received of stream when NFT is used on blur marketplace

Vulnerability Details

Function withdraw() is used to withdraw token from NFT:

function withdraw(
    uint256 streamId,
    address to,
    uint128 amount
)
    public
    override
    noDelegateCall
    notNull(streamId)
    updateMetadata(streamId)
{
    // Check: the stream is not depleted.
    if (_streams[streamId].isDepleted) {
        revert Errors.SablierV2Lockup_StreamDepleted(streamId);
    }

    // Check: the withdrawal address is not zero.
    if (to == address(0)) {
        revert Errors.SablierV2Lockup_WithdrawToZeroAddress(streamId);
    }

    // Check: the withdraw amount is not zero.
    if (amount == 0) {
        revert Errors.SablierV2Lockup_WithdrawAmountZero(streamId);
    }

    // Retrieve the recipient from storage.
    address recipient = _ownerOf(streamId);

    // Check: if `msg.sender` is neither the stream's recipient nor an approved third party, the withdrawal address
    // must be the recipient.
    if (to != recipient && !_isCallerStreamRecipientOrApproved(streamId)) {       //  <---- (1)
        revert Errors.SablierV2Lockup_WithdrawalAddressNotRecipient(streamId, msg.sender, to);
    }

    // Check: the withdraw amount is not greater than the withdrawable amount.
    uint128 withdrawableAmount = _withdrawableAmountOf(streamId);
    if (amount > withdrawableAmount) {
        revert Errors.SablierV2Lockup_Overdraw(streamId, amount, withdrawableAmount);
    }

    // Retrieve the sender from storage.
    address sender = _streams[streamId].sender;

    // Effects and Interactions: make the withdrawal.
    _withdraw(streamId, to, amount);

    // Interaction: if `msg.sender` is not the recipient and the recipient is a contract, try to invoke the
    // withdraw hook on it without reverting if the hook is not implemented, and also without bubbling up
    // any potential revert.
    if (msg.sender != recipient && recipient.code.length > 0) {
        try ISablierV2Recipient(recipient).onLockupStreamWithdrawn({              //  <---- (2)
            streamId: streamId,
            caller: msg.sender,
            to: to,
            amount: amount
        }) { } catch { }
    }

    // Interaction: if `msg.sender` is not the sender, the sender is a contract and is different from the
    // recipient, try to invoke the withdraw hook on it without reverting if the hook is not implemented, and also
    // without bubbling up any potential revert.
    if (msg.sender != sender && sender.code.length > 0 && sender != recipient) {              //  <---- (3)
        try ISablierV2Sender(sender).onLockupStreamWithdrawn({
            streamId: streamId,
            caller: msg.sender,
            to: to,
            amount: amount
        }) { } catch { }
    }
}

From (1), it can be seen that if msg.sender is neither the stream's recipient nor an approved third party, but withdrawal address is the recipient, transaction wont be revert. Moreover, after this condition, transaction cant be revert in (2) and (3) due to try - catch request. Which mean, it is able for other user to withdraw token for anyone to NFT owner's address.

From documentation, it is said that blur marketplace is one of marketplace that can be used to trade NFT. But blur marketplace have buy now pay later option, which require user to deposit token to blur's contract to lock token link, after transfer, NFT owner will be blur's contract:

function borrow(
    LoanOffer calldata offer,
    bytes calldata signature,
    uint256 loanAmount,
    uint256 collateralTokenId
) external returns (uint256 lienId) {
    lienId = _borrow(offer, signature, loanAmount, collateralTokenId);

    /* Lock collateral token. */
    offer.collection.safeTransferFrom(msg.sender, address(this), collateralTokenId);  // <---

    /* Transfer loan to borrower. */
    _POOL.transferFrom(offer.lender, msg.sender, loanAmount);
}

When user use this option, attacker can call withdraw() function to transfer token to blur's address, reduce total token amount can be withdrawn from NFT. Attacker can not get profit from this attack, but victim will lose tokens forever.

Impact

Token are lost and stuck in the blur's contract

Tools Used

Manual review

Recommendations

Do not allow other user except NFT's owner or approved user call withdraw() function

Updates

Lead Judging Commences

inallhonesty Lead Judge
over 1 year ago
inallhonesty Lead Judge over 1 year ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity
Assigned finding tags:

Info/Gas/Invalid as per Docs

https://docs.codehawks.com/hawks-auditors/how-to-determine-a-finding-validity

DEFI integrations

zukanopro Submitter
over 1 year ago
inallhonesty Lead Judge
about 1 year ago
inallhonesty Lead Judge about 1 year ago
Submission Judgement Published
Validated
Assigned finding tags:

NFTs integration with DEFI projects (market, lending etc) can be exploited/won't work

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.