All incidents

Loan Foreclosure Drains Pooled ETH Via Global Balance Distribution

Share
Feb 12, 2026 13:58 UTCAttackLoss: 5.25 ETHManually checked4 exploit txWindow: 1m
Estimated Impact
5.25 ETH
Label
Attack
Exploit Tx
4
Addresses
1
Attack Window
1m
Feb 12, 2026 13:58 UTC → Feb 12, 2026 13:59 UTC

Exploit Transactions

TX 1Ethereum
0x2c728af49d254dd60b0e0918106f32873b5c8492f9b6f3964227373a8e6e9fab
Feb 12, 2026 13:58 UTCExplorer
TX 2Ethereum
0x5ae94fb2225f473ecb302bf35170cac826beedc2766bcd0ab9d35707df07f9d3
Feb 12, 2026 13:58 UTCExplorer
TX 3Ethereum
0xc1c4f4c11cd3ef56c77f40906f5c3b061c60688b2cdea6b141ca68f3c9c05553
Feb 12, 2026 13:58 UTCExplorer
TX 4Ethereum
0x26eb9f4e7c8ab5eb589dfc7f447486cf8e557d91646d51927d86b8969da98090
Feb 12, 2026 13:59 UTCExplorer

Victim Addresses

0xdb005b73f591922b4689824aa4035053269ffa44Ethereum

Loss Breakdown

5.25ETH

Similar Incidents

Root Cause Analysis

Loan Foreclosure Drains Pooled ETH Via Global Balance Distribution

1. Incident Overview TL;DR

An unprivileged adversary drained ETH from the Loan Contract proxy at 0xdb005b73f591922b4689824aa4035053269ffa44 by creating a new loan (loanId=14), assigning themselves as the only shareholders for that loan, and then triggering foreclosure. The foreclosure payout logic computed the distribution amount using a function that returns the proxy's global ETH balance rather than a per-loan balance, so foreclosing the attacker-controlled loan distributed pooled ETH that belonged to other loans/protocol state.

Root cause: initiateLoanForeclose(uint256 loanId) uses getContractBalance(loanId) as the payout base, but getContractBalance(uint256) is effectively independent of loanId and matches address(proxy).balance, breaking per-loan asset isolation.

2. Key Background

  • Victim contract: upgradeable proxy at 0xdb005b73f591922b4689824aa4035053269ffa44 (delegatecalls into implementation 0x03f44e563dd447449f48f8103b5df70aff7cf577, visible in the seed trace).
  • Loans are tracked by an integer loanId and have a shareholder/share mechanism.
  • On-chain state about a loan's shareholders is queryable via view functions (used by the auditor/collector evidence):
    • getSHAddress(uint256 loanId, uint256 idx) to enumerate shareholder addresses.
    • getShareholderShares(uint256 loanId, address shareholder) to read each shareholder's shares for a given loan.
  • Foreclosure: a function initiateLoanForeclose(uint256 loanId) distributes ETH to the loan's shareholders.
  • Critical accounting function: getContractBalance(uint256 loanId) is expected (by name/usage) to represent a per-loan balance, but empirically returns the proxy's global ETH balance.

3. Vulnerability Analysis & Root Cause Summary

This is an accounting / asset-isolation bug in the foreclosure payout calculation. The protocol stores multiple loans' ETH inside a single contract balance (pooled custody), and foreclosure should distribute only the ETH attributable to the specified loanId. Instead, the distribution amount is derived from a function that returns the contract's total ETH balance regardless of loanId. As a result, an attacker can create a fresh loan with attacker-controlled shareholders, then foreclose it to withdraw pooled ETH that is unrelated to the attacker-created loan. The exploit does not require privileged roles or compromised keys; it uses publicly callable entrypoints and on-chain state. The seed transaction shows the proxy paying out nearly all pooled ETH to the attacker-controlled shareholders, after which those shareholders forward ETH to the attacker EOA.

4. Detailed Root Cause Analysis

4.1 Pre-state facts (block 24441150)

At the end of block 24441150 (pre-state for the exploit tx), the victim proxy held a large pooled ETH balance:

contract ETH balance (block 24441150): 5302821111758431738 wei (5.302821111758431738 ETH)

The key behavioral indicator for the bug is that getContractBalance(loanId) returns the same value for multiple different loanIds at this pre-state:

getContractBalance(<loanId>) at block 24441150:
  loan 0:  5302821111758431738
  loan 1:  5302821111758431738
  loan 2:  5302821111758431738
  loan 13: 5302821111758431738
  loan 14: 5302821111758431738

This demonstrates that the function is independent of loanId and is tied to the proxy's global ETH balance.

Separately, loan 14 was created shortly before the exploit and had only attacker-controlled shareholders at the pre-state block:

Loan 14 shareholders at block 24441150:
  0x76788300996D41a03676bBab19A71D616f782BB7  (100 shares)
  0x608d18C6B73ef7af28F9fE6E7a72694957458699 (10395 shares)
  0x656Eb28d9AbD6bAEA34eC1bb31E636626Df2c4de (10395 shares)
Total shares: 20890

4.2 Exploit trigger (block 24441151, tx 0x26eb…8090)

In block 24441151, the exploit is realized in transaction:

  • 0x26eb9f4e7c8ab5eb589dfc7f447486cf8e557d91646d51927d86b8969da98090

Observed effects from canonical balance diffs for this transaction:

{
  "victim_proxy": {
    "address": "0xdb005b73f591922b4689824aa4035053269ffa44",
    "before_wei": "5302821111758431738",
    "after_wei":  "50373127912881386",
    "delta_wei":  "-5252447983845550352"
  }
}

So the proxy loses 5252447983845550352 wei (5.252447983845550352 ETH) during the exploit transaction.

The seed trace shows the proxy transferring ETH to the three loan-14 shareholder addresses (amounts match the share proportions):

0x767883... (100 / 20890 shares)    receives 25143360382219006 wei    (0.025143360382219006 ETH)
0x608d18... (10395 / 20890 shares)  receives 2613652311731665673 wei  (2.613652311731665673 ETH)
0x656eb2... (10395 / 20890 shares)  receives 2613652311731665673 wei  (2.613652311731665673 ETH)
Total transferred: 5252447983845550352 wei (5.252447983845550352 ETH)

Because those recipients are the attacker-controlled shareholders for the newly created loan 14, and because the payout base is the contract's global ETH balance rather than a loan-14-specific balance, the attacker can withdraw pooled ETH that was already in the contract prior to creating loan 14.

4.3 Profit realization

After receiving ETH from the victim proxy, the shareholder contracts forward ETH to the attacker EOA (0x3b1e24061478560d91f72f895e0cf7972f45d1ef) within the same transaction (visible in the trace as fallback{value: ...} calls).

The collector's native balance delta for the attacker EOA over the exploit transaction is:

attacker EOA before: 0.032975056195356989 ETH
attacker EOA after:  5.289685302397209232 ETH
delta:              +5.256710246201852243 ETH (net of gas)

5. Adversary Flow Analysis

The incident's on-chain sequence (all permissionless transactions from an unprivileged EOA) is:

  1. 0x2c728af49d254dd60b0e0918106f32873b5c8492f9b6f3964227373a8e6e9fab (block 24441146): deploy helper contract 0x608d18c6b73ef7af28f9fe6e7a72694957458699.
  2. 0x5ae94fb2225f473ecb302bf35170cac826beedc2766bcd0ab9d35707df07f9d3 (block 24441148): transfer 50 DAI to the helper contract.
  3. 0xc1c4f4c11cd3ef56c77f40906f5c3b061c60688b2cdea6b141ca68f3c9c05553 (block 24441149): create loan 14, deposit 0.050538154368260197 ETH as collateral, and set loan-14 shareholders to attacker-controlled contract addresses.
  4. 0x26eb9f4e7c8ab5eb589dfc7f447486cf8e557d91646d51927d86b8969da98090 (block 24441151): trigger initiateLoanForeclose(14) (via the helper stack), causing the victim proxy to distribute pooled ETH to loan-14 shareholders, which then forward ETH to the attacker EOA.

Key decision point: once the attacker has a loan with attacker-only shareholders, triggering foreclosure causes the protocol to compute payouts from the global balance, not from loan-specific collateral, enabling the drain.

6. Impact & Losses

  • Victim: 0xdb005b73f591922b4689824aa4035053269ffa44
  • Asset: ETH
  • Loss (victim proxy balance reduction in the exploit tx): 5.252447983845550352 ETH (5252447983845550352 wei)
  • Attacker profit (EOA net balance delta, after gas): 5.256710246201852243 ETH (profit exceeds victim delta due to intra-tx forwarding and accounting of intermediate contract balances; victim delta is the canonical protocol loss)

7. References

  • Exploit transaction (seed): 0x26eb9f4e7c8ab5eb589dfc7f447486cf8e557d91646d51927d86b8969da98090
  • Victim proxy: 0xdb005b73f591922b4689824aa4035053269ffa44
  • Proxy implementation (as observed via delegatecall in trace): 0x03f44e563dd447449f48f8103b5df70aff7cf577
  • Trace evidence (cast run): artifacts/collector/seed/1/0x26eb9f4e7c8ab5eb589dfc7f447486cf8e557d91646d51927d86b8969da98090/trace.cast.log
  • Balance delta evidence: artifacts/collector/seed/1/0x26eb9f4e7c8ab5eb589dfc7f447486cf8e557d91646d51927d86b8969da98090/balance_diff.json
  • Pre-state calls demonstrating getContractBalance is independent of loanId: artifacts/auditor/iter_0/onchain/contract_balance_calls.txt
  • Loan-14 shareholder state at pre-state: artifacts/auditor/iter_0/onchain/loan14_shareholders_block24441150.txt
  • Loan-14 lifecycle and victim balance across blocks: artifacts/auditor/iter_0/onchain/loan14_state_across_blocks.txt