Flow

Sablier
FoundryDeFi
20,000 USDC
View results
Submission Details
Severity: low
Invalid

An Approved Actor Can Withdraw from Stream to any Address

Summary

In a stream there are a few actors: The sender (who sends the funds), the recipient (who receives the funds), approved users and unknown users. Some functionalities in the system are reserved for specific actors within a stream. In this case, the withdraw functionality should be available to all, however the funds should only be sent to the recipient. Due to bad checks in the _withdraw function, this could be exploited, or even if the user makes a mistake in the address they pass in.

Vulnerability Details

In the _withdraw function, a to address is passed. The intention that the to should always be the recipient. The code does have an if statement to check that all the right conditions are met, however an approved user can exploit the arbitrary to address, or even make a mistake in which address they put in, and can pass in any address.

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

Impact

Let us consider the following scenario:

  1. A user is approved.

  2. The user calls the withdraw function.

  3. The user passes in a false address as the to address. Could be maliciously or mistakenly.

  4. The withdraw function calls the internal _withdraw.

  5. The if statement does not revert as it needs BOTH conditionals to be true, but it is an approved user, even though the to address conditional does not equal to _ownerOf(streamId).

  6. Funds are transferred to a false address.

Tools Used

Manual Review

Recommendations

Consider not even having an arbitrary to address. Only let it be possible to pass to recipient as the address is already saved in storage:

- address recipient = _ownerOf(streamId);
- if (to != _ownerOf(streamId) && !_isCallerStreamRecipientOrApproved(streamId))
+ if (!_isCallerStreamRecipientOrApproved(streamId)) {
revert Errors.SablierFlow_WithdrawalAddressNotRecipient({ streamId: streamId, caller: msg.sender, to: to });
}
.
.
.
- token.safeTransfer({ to: to, value: amount });
+ token.safeTransfer({ to: recipient, value: amount });
Updates

Lead Judging Commences

inallhonesty Lead Judge 7 months ago
Submission Judgement Published
Invalidated
Reason: Design choice
inallhonesty Lead Judge 7 months ago
Submission Judgement Published
Invalidated
Reason: Design choice

Support

FAQs

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