All incidents

STEAD Proxy Sweep Drain

Share
Jun 29, 2025 17:46 UTCAttackLoss: 135,000 STEADPending manual check1 exploit txWindow: Atomic
Estimated Impact
135,000 STEAD
Label
Attack
Exploit Tx
1
Addresses
2
Attack Window
Atomic
Jun 29, 2025 17:46 UTC → Jun 29, 2025 17:46 UTC

Exploit Transactions

TX 1Arbitrum
0x32dbfce2253002498cd41a2d79e249250f92673bc3de652f3919591ee26e8001
Jun 29, 2025 17:46 UTCExplorer

Victim Addresses

0xf9ff933f51ba180a474634440a406c95dfb27596Arbitrum
0x42f4e5fcd12d59e879dbcb908c76032a4fb0303bArbitrum

Loss Breakdown

135,000STEAD

Similar Incidents

Root Cause Analysis

STEAD Proxy Sweep Drain

1. Incident Overview TL;DR

On Arbitrum block 352509408, EOA 0x5fb0b8584b34e56e386941a65dbe455ad43c5a23 exploited STEAD inventory proxy 0xf9ff933f51ba180a474634440a406c95dfb27596 through helper contract 0x34c7c354c823af400696febdc4f31c4df0175828. The helper flash-borrowed WETH from Balancer vault 0xba12222222228d8ba445958a75a0704d566bf2c8, invoked selector 0x16fb27ce on the victim proxy, drained the proxy's full 135000000000 STEAD balance, dumped the tokens through the STEAD/USDT pool 0x3e08920c0ab3b590186b1e8eb84e66ee274c383f and WETH/USDT pool 0x641c00a822e8b671738d32a431a4fb6074e5c79d, repaid the flash loan, and exited with 5945402050982676896 wei of net ETH profit after gas.

The root cause is a public sweep path in implementation 0xca9d57cd258731a07c56c01ca353e8b0e2798e25 behind the transparent proxy. Selector 0x16fb27ce resolves the STEAD token from the registry, reads balanceOf(address(this)), and transfers the entire balance to CALLER without any authorization check. Because non-admin calls on the proxy delegate into this implementation, any unprivileged caller can drain the proxy inventory.

2. Key Background

The exploited contract is transparent proxy 0xf9ff933f51ba180a474634440a406c95dfb27596. Non-admin calls fall through to implementation 0xca9d57cd258731a07c56c01ca353e8b0e2798e25, so any missing access control in the implementation becomes externally reachable through the proxy.

The drained asset is STEAD token 0x42f4e5fcd12d59e879dbcb908c76032a4fb0303b. Its verified source confirms decimals() = 6, which matches the observed inventory and loss accounting.

function decimals() public view virtual override returns (uint8) {
    return uint8(6);
}

The monetization path used public liquidity only. The attacker sold STEAD into Uniswap V3 pool 0x3e08920c0ab3b590186b1e8eb84e66ee274c383f for USDT, then sold that USDT into Uniswap V3 pool 0x641c00a822e8b671738d32a431a4fb6074e5c79d for WETH, then repaid the Balancer flash loan. No privileged role, private key, or attacker-side artifact was required.

3. Vulnerability Analysis & Root Cause Summary

This is an ATTACK-category ACT issue caused by missing authorization on an asset-moving helper path. The violated invariant is straightforward: STEAD inventory held by proxy 0xf9ff... must only move through protocol-authorized flows, and an arbitrary external caller must never be able to redirect that inventory to itself. The collected selector disassembly for 0xca9d57... shows the exact unsafe sequence. First, the routine queries the registry for "SteadToken". Second, it calls balanceOf(address(this)) to read the proxy's own STEAD balance. Third, it constructs transfer(CALLER, balance) and executes it without first comparing CALLER against any owner, operator, or allowlist. That makes selector 0x16fb27ce a public sweep function exposed through the proxy fallback. Once the proxy inventory can be permissionlessly extracted, converting the drained STEAD into profit through public pools is deterministic.

4. Detailed Root Cause Analysis

The seed transaction 0x32dbfce2253002498cd41a2d79e249250f92673bc3de652f3919591ee26e8001 begins with helper contract 0x34c7... receiving a Balancer flash loan of 876541714919625 WETH units. The trace then shows the helper calling the victim proxy with selector 0x16fb27ce.

0xBA12222222228d8Ba445958a75a0704d566BF2C8::flashLoan(...)
  emit Transfer(from: 0xBA122..., to: 0x34c7..., value: 876541714919625)
  0xf9FF933f51bA180a474634440a406c95DfB27596::16fb27ce(...)
    0xca9d57Cd258731A07C56c01CA353e8B0e2798E25::16fb27ce(...) [delegatecall]

The selector-level reproduction with an unrelated EOA proves the call is permissionless. In the arbitrary-caller trace, the proxy delegates to the implementation, resolves the STEAD token address, reads the proxy balance, and transfers the full inventory to caller 0x1111111111111111111111111111111111111111.

TransparentUpgradeableProxy::fallback(0x16fb27ce...)
  Registry::getContractAddress("SteadToken")
  SteadToken::balanceOf(0xf9FF933f51bA180a474634440a406c95DfB27596) -> 135000000000
  SteadToken::transfer(0x1111111111111111111111111111111111111111, 135000000000)

The seed trace shows the same transfer in the real exploit path, except the recipient is the attacker helper 0x34c7....

emit Transfer(
  from: 0xf9FF933f51bA180a474634440a406c95DfB27596,
  to:   0x34c7c354c823af400696FEBDC4F31c4DF0175828,
  value: 135000000000
)

After receiving the STEAD inventory, the helper realizes value through two public swaps. The WETH/USDT pool pays out 5945405436886901223 WETH units to the helper, while the STEAD/USDT pool receives the drained 135000000000 STEAD and sends 14484878986 USDT to the WETH/USDT pool.

0x641C00A822e8b671738d32a431a4Fb6074E5c79d::swap(..., 14484878986, ...)
  emit Transfer(from: 0x641C..., to: 0x34c7..., value: 5945405436886901223)
  0x3e08920C0Ab3B590186B1E8eB84e66EE274C383f::swap(..., 135000000000, ...)
    emit Transfer(from: 0x3e089..., to: 0x641C..., value: 14484878986)
    emit Transfer(from: 0x34c7..., to: 0x3e089..., value: 135000000000)

The helper then unwraps WETH, re-wraps only the flash-loan principal, repays Balancer, and forwards the remaining ETH to the attacking EOA. This makes the ACT success predicate concrete: public flash liquidity plus a public sweep selector is enough to drain and monetize the proxy inventory in one transaction.

5. Adversary Flow Analysis

The attacker flow has three stages.

  1. Flash-loan funding. Helper 0x34c7... borrows WETH from Balancer vault 0xba122... with zero fee, as shown in the seed trace.
  2. Permissionless drain. The helper calls selector 0x16fb27ce on proxy 0xf9ff... and receives the proxy's full 135000000000 STEAD balance. The arbitrary-caller reproduction demonstrates that this call does not require ownership or operator status.
  3. Swap realization and payout. The helper routes the drained STEAD through the STEAD/USDT pool, converts the USDT leg through the WETH/USDT pool, withdraws WETH to ETH, repays the flash-loan principal, and forwards the residual ETH to EOA 0x5fb0....

The relevant adversary accounts are defensibly identified. EOA 0x5fb0... is the transaction sender and final profit recipient. Contract 0x34c7... is the execution helper that receives the flash loan, invokes the vulnerable selector, performs the swaps, repays Balancer, and transfers profit to the EOA.

6. Impact & Losses

The direct victim loss is the full STEAD inventory held by proxy 0xf9ff933f51ba180a474634440a406c95dfb27596: 135000000000 raw units, which equals 135,000 STEAD at 6 decimals. The balance diff and exploit trace also show the monetization result. The attacker path generated 14484878986 USDT of intermediate proceeds and 5945405437012676896 wei of gross ETH payout to the adversary EOA. After gas cost of 3386030000000 wei, the net ETH increase to the EOA was 5945402050982676896 wei.

7. References

  1. Seed transaction metadata for 0x32dbfce2253002498cd41a2d79e249250f92673bc3de652f3919591ee26e8001.
  2. Seed execution trace showing the Balancer flash loan, selector 0x16fb27ce call, Uniswap V3 swaps, and final ETH payout.
  3. Seed balance diff showing the adversary EOA net ETH gain and the intermediate USDT/WETH deltas.
  4. Selector 0x16fb27ce disassembly excerpt for implementation 0xca9d57cd258731a07c56c01ca353e8b0e2798e25.
  5. Arbitrary-caller reproduction trace proving an unrelated EOA can invoke the selector and drain the proxy inventory.
  6. Verified STEAD token source showing 6-decimal accounting.