Malicious user can perpetually DoS AUTO-REFINANCER's refinance function
Summary
The ability for borrowers to toggle auto-refinancing at any time will cause a perpetual Denial of Service (DoS) for the AUTO-REFINANCER. A malicious borrower can frontrun the refinance transaction by disabling their auto-refinancing, causing the entire transaction to revert, and then backrun by re-enabling it, allowing them to be included in future batches and repeat the attack.
Root Cause
In PredictDotLoan.sol, there are two key design choices that contribute to this vulnerability:
The choice to allow borrowers to freely toggle auto-refinancing allows for repeated manipulation of the contract state, enabling a perpetual DoS attack on the refinancing mechanism.
In the refinance(Refinancing[] calldata refinancings) function, the implementation processes all refinancings in a single transaction without any error handling for individual items. This means that if any single refinancing in the array causes a revert, the entire transaction fails, affecting all other refinancings in the batch. The relevant code is at [Github link]
This "all-or-nothing" approach in processing the refinancings array makes the function vulnerable to a single malicious actor, allowing them to disrupt the entire batch operation. These two factors combined create a scenario where a malicious user can repeatedly cause the entire refinancing operation to fail, leading to a persistent DoS condition.
Internal pre-conditions
No response
External pre-conditions
No response
Attack Path
A malicious user creates a LoanOffer proposal from Address A with artificially long duration and low loanAmount, making it unattractive to legitimate users.
The same malicious user, using Address B, accepts this LoanOffer as a Borrower.
Using Address B (Borrower), the malicious user toggles auto-refinancing on, making this loan eligible for the AUTO-REFINANCER bot to seek better terms.
The malicious user, using Address C, creates another LoanOffer that is more attractive and suitable for auto-refinancing based on the terms of the loan held by Address B (but still unattractive to legitimate users)
The AUTO-REFINANCER bot identifies this new LoanOffer as a better option and includes it in a batch refinance([]) call.
Just before the AUTO-REFINANCER's transaction is processed, the malicious user (using Address B) frontruns by calling toggleAutoRefinancingEnabled() to disable auto-refinancing.
The AUTO-REFINANCER's refinance([]) transaction executes and reverts when processing the refinancing for Address B due to the check [Github link]:
if (autoRefinancingEnabled[borrower] == 0) {
revert BorrowerDidNotEnableAutoRefinancing(borrower);
}
The entire refinance operation fails, affecting all borrowers in the batch.
The malicious user (Address B) backruns the failed transaction by calling toggleAutoRefinancingEnabled() again to re-enable auto-refinancing.
The loan associated with Address B is now again eligible to be included in future refinancing batches.
Steps 5-10 can be repeated indefinitely, causing a perpetual DoS on the AUTO-REFINANCER's refinance function.
This revised attack path demonstrates how a malicious user can set up the conditions for the attack using multiple addresses and manipulate the system to repeatedly disrupt the auto-refinancing process.
Impact
The AUTO-REFINANCER suffers from a persistent Denial of Service, unable to execute batch refinancing operations. This affects all borrowers included in the refinancing batches, potentially causing them to miss out on better loan terms.
The Protocol team incurs losses due to wasted gas fees on repeatedly failed transactions, while the malicious user's costs remain minimal.
PoC
No response
Mitigation
Move the check for autoRefinancingEnabled earlier in the refinancing loop and use a continue statement to skip borrowers who have disabled auto-refinancing:
function refinance(Refinancing[] calldata refinancings) external nonReentrant whenNotPaused onlyRole(REFINANCIER_ROLE) {
RefinancingResult[] memory results = new RefinancingResult[](refinancings.length);
for (uint256 i; i < refinancings.length; ++i) {
Refinancing calldata refinancing = refinancings[i];
Loan storage loan = loans[refinancing.loanId];
address borrower = loan.borrower;
if (autoRefinancingEnabled[borrower] == 0) {
continue; // Skip this refinancing if auto-refinancing is disabled
}
(uint256 id, Loan memory newLoan, uint256 protocolFee) = _refinance(refinancing);
// ... rest of the function
}
emit LoansRefinanced(results);
}
This change allows the function to skip borrowers who have disabled auto-refinancing without causing the entire transaction to revert, ensuring that other borrowers in the batch can still benefit from refinancing.
Ikigai
Medium
Malicious user can perpetually DoS AUTO-REFINANCER's refinance function
Summary
The ability for borrowers to toggle auto-refinancing at any time will cause a perpetual Denial of Service (DoS) for the AUTO-REFINANCER. A malicious borrower can frontrun the refinance transaction by disabling their auto-refinancing, causing the entire transaction to revert, and then backrun by re-enabling it, allowing them to be included in future batches and repeat the attack.
Root Cause
In PredictDotLoan.sol, there are two key design choices that contribute to this vulnerability:
refinance(Refinancing[] calldata refinancings)
function, the implementation processes all refinancings in a single transaction without any error handling for individual items. This means that if any single refinancing in the array causes a revert, the entire transaction fails, affecting all other refinancings in the batch. The relevant code is at [Github link]This "all-or-nothing" approach in processing the refinancings array makes the function vulnerable to a single malicious actor, allowing them to disrupt the entire batch operation. These two factors combined create a scenario where a malicious user can repeatedly cause the entire refinancing operation to fail, leading to a persistent DoS condition.
Internal pre-conditions
No response
External pre-conditions
No response
Attack Path
A malicious user creates a LoanOffer proposal from Address A with artificially long duration and low loanAmount, making it unattractive to legitimate users.
The same malicious user, using Address B, accepts this LoanOffer as a Borrower.
Using Address B (Borrower), the malicious user toggles auto-refinancing on, making this loan eligible for the AUTO-REFINANCER bot to seek better terms.
The malicious user, using Address C, creates another LoanOffer that is more attractive and suitable for auto-refinancing based on the terms of the loan held by Address B (but still unattractive to legitimate users)
The AUTO-REFINANCER bot identifies this new LoanOffer as a better option and includes it in a batch refinance([]) call.
Just before the AUTO-REFINANCER's transaction is processed, the malicious user (using Address B) frontruns by calling toggleAutoRefinancingEnabled() to disable auto-refinancing.
The AUTO-REFINANCER's refinance([]) transaction executes and reverts when processing the refinancing for Address B due to the check [Github link]:
The entire refinance operation fails, affecting all borrowers in the batch.
The malicious user (Address B) backruns the failed transaction by calling toggleAutoRefinancingEnabled() again to re-enable auto-refinancing.
The loan associated with Address B is now again eligible to be included in future refinancing batches.
Steps 5-10 can be repeated indefinitely, causing a perpetual DoS on the AUTO-REFINANCER's refinance function.
This revised attack path demonstrates how a malicious user can set up the conditions for the attack using multiple addresses and manipulate the system to repeatedly disrupt the auto-refinancing process.
Impact
The AUTO-REFINANCER suffers from a persistent Denial of Service, unable to execute batch refinancing operations. This affects all borrowers included in the refinancing batches, potentially causing them to miss out on better loan terms.
The Protocol team incurs losses due to wasted gas fees on repeatedly failed transactions, while the malicious user's costs remain minimal.
PoC
No response
Mitigation
Move the check for
autoRefinancingEnabled
earlier in the refinancing loop and use acontinue
statement to skip borrowers who have disabled auto-refinancing:This change allows the function to skip borrowers who have disabled auto-refinancing without causing the entire transaction to revert, ensuring that other borrowers in the batch can still benefit from refinancing.