Flow

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

Attacker can steal tokens due to reorg

Vulnerability Details

https://github.com/Cyfrin/2024-10-sablier/blob/main/src/SablierFlow.sol#L564-#L621

https://github.com/Cyfrin/2024-10-sablier/blob/main/src/SablierFlow.sol#L792-#L794

In the _create()function, streamId is consecutive increase by one:

function _create(
address sender,
address recipient,
UD21x18 ratePerSecond,
IERC20 token,
bool transferable
)
internal
returns (uint256 streamId)
{
// Check: the sender is not the zero address.
if (sender == address(0)) {
revert Errors.SablierFlow_SenderZeroAddress();
}
uint8 tokenDecimals = IERC20Metadata(address(token)).decimals();
// Check: the token decimals are not greater than 18.
if (tokenDecimals > 18) {
revert Errors.SablierFlow_InvalidTokenDecimals(address(token));
}
// Load the stream ID.
streamId = nextStreamId; // <----
. . . . . . .
}

Along with allowing set senderaddress to arbitrary address, it opened attack vector when reorg happen:

  • User A create stream with id = 50, set user B as sender

  • Sometimes later, user B deposit token to that streamId

  • Reorg happen, attacker do these actions in one transaction:

  • 1, create streamId with same id = 50due to reorg with same sender but receiver is different

  • 2, approve permission of that streamId for attacker's controlled address, then transfer it to original recipient

  • Later, user B deposit to that streamId, attacker can withdraw part of them due to being approved before:

    function _withdraw(
    uint256 streamId,
    address to,
    uint128 amount
    )
    internal
    returns (uint128 withdrawnAmount, uint128 protocolFeeAmount)
    {
    . . . . . . .
    if (to != _ownerOf(streamId) && !_isCallerStreamRecipientOrApproved(streamId)) { // <---
    revert Errors.SablierFlow_WithdrawalAddressNotRecipient({ streamId: streamId, caller: msg.sender, to: to });
    }
    . . . . . . .
    }

if (to != _ownerOf(streamId) && !_isCallerStreamRecipientOrApproved(streamId)) {
revert Errors.SablierFlow_WithdrawalAddressNotRecipient({ streamId: streamId, caller: msg.sender, to: to });
}Impact

Victim's token can be stolen when reorg happen

Tools Used

Manual review

Recommendations

streamIdshould be randomly generated by using caller's address and other parameters.

Updates

Lead Judging Commences

inallhonesty Lead Judge
11 months ago
inallhonesty Lead Judge 10 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement
Assigned finding tags:

Another way to exploit that reorg

Support

FAQs

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