Denial-of-Service Attack caused by malicious receiver

From WEB3 Vulnerapedia
Jump to navigation Jump to search

Denial-of-Service Attack caused by malicious receiver typically targets systems or networks where one entity, often a server or a service, is overwhelmed by a flood of requests or messages from malicious sources. In the context of communication protocols or distributed systems, a malicious receiver might exploit vulnerabilities in the system's design or implementation to disrupt the normal flow of communication or exhaust system resources.

Details

A malicious lien buyer can DoS to cause fund loss/lock of other lien holders & borrower collateral.

Anyone can call buyoutLien to purchase a lien token delivered to an arbitrary receiver.

function buyoutLien(ILienToken.LienActionBuyout calldata params) external {
    (bool valid, IAstariaRouter.LienDetails memory ld) = ASTARIA_ROUTER
      .validateCommitment(params.incoming);

    if (!valid) {
      revert InvalidTerms();
    }

    uint256 collateralId = params.incoming.tokenContract.computeId(
      params.incoming.tokenId
    );
    (uint256 owed, uint256 buyout) = getBuyout(collateralId, params.position);
    uint256 lienId = liens[collateralId][params.position];

    //the borrower shouldn't incur more debt from the buyout than they already owe
    if (ld.maxAmount < owed) {
      revert InvalidBuyoutDetails(ld.maxAmount, owed);
    }
    if (!ASTARIA_ROUTER.isValidRefinance(lienData[lienId], ld)) {
      revert InvalidRefinance();
    }

    TRANSFER_PROXY.tokenTransferFrom(
      WETH,
      address(msg.sender),
      getPayee(lienId),
      uint256(buyout)
    );

    lienData[lienId].last = block.timestamp.safeCastTo32();
    lienData[lienId].start = block.timestamp.safeCastTo32();
    lienData[lienId].rate = ld.rate.safeCastTo240();
    lienData[lienId].duration = ld.duration.safeCastTo32();

    _transfer(ownerOf(lienId), address(params.receiver), lienId);
  }

A malicious entity (even the borrower themselves) can purchase a small lien amount with its token delivered to their contract, which can implement supportsInterface() with arbitrary code, e.g., revert, that gets executed by the protocol in multiple places, giving the attacker control at those points.

function supportsInterface(bytes4 interfaceId)
    public
    pure
    override(IERC165)
    returns (bool)
  {
    return
      interfaceId == type(IPublicVault).interfaceId ||
      interfaceId == type(IVault).interfaceId ||
      interfaceId == type(ERC4626Cloned).interfaceId ||
      interfaceId == type(ERC4626).interfaceId ||
      interfaceId == type(ERC20).interfaceId ||
      interfaceId == type(IERC165).interfaceId;
  }

There are different DoS potential attacks:

  1. An attacker can revert endAuction() to prevent the release of collateral to the winner.
    unction endAuction(uint256 auctionId)
        external
        override
        requiresAuth
        returns (address winner)
      {
        [...]
    
      for (uint256 i = 0; i < liensRemaining.length; i++) {
          ILienToken.Lien memory lien = LIEN_TOKEN.getLien(liensRemaining[i]);
          if (
            PublicVault(LIEN_TOKEN.ownerOf(i)).supportsInterface(
              type(IPublicVault).interfaceId
            )
          ) {
            PublicVault(LIEN_TOKEN.ownerOf(i)).decreaseYIntercept(lien.amount);
          }
        }
        LIEN_TOKEN.removeLiens(auctionId, liensRemaining);
        delete auctions[auctionId];
    }
    
  2. An attacker can revert liquidate() to prevent the liquidation from even starting.
    function liquidate(uint256 collateralId, uint256 position)
        external
        returns (uint256 reserve)
      {
        require(
          canLiquidate(collateralId, position),
          "liquidate: borrow is healthy"
        );
    
        [...]
    
        address owner = LIEN_TOKEN.ownerOf(currentLien);
          if (
            IPublicVault(owner).supportsInterface(type(IPublicVault).interfaceId)
          ) {
            // subtract slope from PublicVault
    
            PublicVault(owner).updateVaultAfterLiquidation(
              LIEN_TOKEN.calculateSlope(currentLien)
            );
            if (
              PublicVault(owner).timeToEpochEnd() <=
              COLLATERAL_TOKEN.auctionWindow()
            ) {
              uint64 currentEpoch = PublicVault(owner).getCurrentEpoch();
              address accountant = PublicVault(owner).getLiquidationAccountant(
                currentEpoch
              );
              uint256 lienEpoch = PublicVault(owner).getLienEpoch(
                lien.start + lien.duration
              );
              PublicVault(owner).decreaseEpochLienCount(lienEpoch);
    
              // only deploy a LiquidationAccountant for the next set of withdrawing LPs if the previous set of LPs have been repaid
              if (PublicVault(owner).withdrawReserve() == 0) {
                if (accountant == address(0)) {
                  accountant = PublicVault(owner).deployLiquidationAccountant();
                }
                LIEN_TOKEN.setPayee(currentLien, accountant);
                LiquidationAccountant(accountant).handleNewLiquidation(
                  lien.amount,
                  COLLATERAL_TOKEN.auctionWindow() + 1 days
                );
                PublicVault(owner).increaseLiquidationsExpectedAtBoundary(
                  lien.amount
                );
              }
            }
          }
        }
    
        reserve = COLLATERAL_TOKEN.auctionVault(
          collateralId,
          address(msg.sender),
          liquidationFeePercent
        );
    }
    
  3. An attacker can revert _payment() to prevent any lien payments from succeeding in calls to makePayment(uint256 collateralId, uint256 paymentAmount)
    function _makePayment(uint256 collateralId, uint256 totalCapitalAvailable)
        internal
      {
        uint256[] memory openLiens = liens[collateralId];
        uint256 paymentAmount = totalCapitalAvailable;
        for (uint256 i = 0; i < openLiens.length; ++i) {
          uint256 capitalSpent = _payment(
            collateralId,
            uint8(i),
            paymentAmount,
            address(msg.sender)
          );
          paymentAmount -= capitalSpent;
        }
      }
    

Sources

https://medium.com/@bloqarl/uncovering-real-life-examples-of-denial-of-service-attacks-on-smart-contracts-8bc220c2cdd0