Dough Finance ConnectorDeleverageParaswap flashloanReq() Unvalidated ParaSwap Calldata / Approval Drain (2024-07-12)

  • Name: Dough Finance ConnectorDeleverageParaswap flashloanReq() unvalidated ParaSwap calldata / approval drain

  • Date: 2024-07-12

  • Protocol: Dough Finance

  • Chain: Ethereum

  • Category: router approval abuse / arbitrary external call / token-approval drain / leveraged-position unwind abuse

  • Loss: about $2.1M-$2.55M gross across the exploit wave, including roughly 210,629 USDC and 795.25 ETH in Dough’s postmortem, with about 76.2 ETH later recovered by whitehat frontrunners and the main attacker keeping roughly $1.87M

  • Summary: Dough exposed a public flash-loan-assisted deleverage path in ConnectorDeleverageParaswap that trusted attacker-shaped ParaSwap calldata deep inside the unwind loop. The attacker first used a Balancer flash loan to repay a victim Dough DSA’s Aave debt, which made the victim’s collateral withdrawable through Dough’s own deloop flow. In the same execution path, the connector then forwarded a second attacker-crafted ParaSwap payload through a raw external call, and that payload simply invoked WETH.transferFrom(...) to move the freshly withdrawn collateral out of the victim DSA and into attacker-controlled accounts. The protocol did not lose funds because ParaSwap itself failed; it lost funds because a public connector with spender authority over user DSAs accepted arbitrary downstream calldata as if it were a legitimate swap instruction.

  • Trigger / entry point:

    • Public analyses and Dough’s incident report identify ConnectorDeleverageParaswap as the key exploited connector, with the vulnerable contract highlighted by CertiK at 0x9f54e8eAa9658316bB8006e03fff1Cb191AaFBe6.
    • The public entry point was flashloanReq(), which let callers kick off a Balancer-flash-loan-powered deleverage sequence.
    • Dough’s own postmortem later said the same flashLoanReq pattern existed across four connectors and that msg.sender DSA checks were added afterward because the flash-loan context had been too public.
    • Inside the deleverage chain, attacker-controlled ParaSwap bytes eventually reached a low-level external call rather than a tightly validated swap adapter.
  • Root cause:

    • Dough combined three dangerous assumptions in one path: public flash-loan orchestration, delegated asset-moving authority over user DSAs, and unvalidated downstream swap calldata.
    • The intended invariant was something like this public deleverage helper may only execute the exact unwind and swap steps needed for the targeted user's position; the deployed code instead let attacker-supplied ParaSwap payloads survive deep enough to become an arbitrary call surface.
    • Because the connector already had the authority it needed to unwind collateral from Dough DSAs, a malicious payload did not need to break Aave or Balancer. It only needed to redirect the last leg from perform intended swap to call token.transferFrom().
    • This was therefore not a classic protocol-TVL drain and not a direct EOA-approval phishing event. It was an approval-drain variant aimed at user-owned Dough proxy accounts whose collateral-management permissions had already been delegated to the connector.
  • Exploit path:

    1. The attacker funded EOA 0x67104175fc5fabbdb5A1876c3914e04B94c71741 and deployed attack contract 0x11A8DC866C5d03ff06bb74565b6575537B215978.
    2. In the first exploit transaction 0x92cdcc732eebf47200ea56123716e337f6ef7d5ad714a2295794fdc6031ebb2e, the attacker took a Balancer flash loan and used it to repay about 938,566 USDC of Aave debt for victim Dough DSA 0x534a3bb1ecb886ce9e7632e33d97bf22f838d085.
    3. With that debt temporarily cleared, the attacker invoked Dough’s flashloanReq() / deleverage flow so the victim DSA’s WETH collateral became withdrawable through Dough’s own unwind logic.
    4. CertiK’s trace describes the attacker-crafted paraswapCallData as two segments: the first segment nudged the deloop path far enough to withdraw collateral, while the second segment targeted the WETH contract directly and encoded transferFrom(...).
    5. Because Dough eventually forwarded that payload through a raw external call inside the deloop chain, the connector executed the malicious token call rather than a constrained swap. Roughly 596.844648055377423623 WETH from the first victim DSA moved to the attacker in that first transaction.
    6. The attacker swapped proceeds back into USDC, repaid the Balancer flash loan, and kept the spread.
    7. Dough and CertiK say the attacker repeated the same pattern across seven additional Dough DSA accounts using follow-on exploit transactions 0x28a934816ed0371a3fbb2f89986eecf87a22937c40309ff2f58697792215b4da, 0xf356d1b05d26de824baddf1a575a33dcf63e2ecec2a0eaa37b15653b94845b63, 0x1f76a6a208b481b107b96e3867f8cf346dbc7b5fc9bdb2d617cd55865a2492c8, 0x4d52f0dad3ddb7ac5523c51a75a9acd47b244e536b240d692784305b69ca6ab5, 0x96d311cec2f5a935b3252c551fca6b5f2a5764c646e02fe3551d43f4e854021d, 0xb77d3960d3c841c6a5d5919e54f5aae64f43588cc8c7c94556aee42d2f88ad4a, and 0x8e6630799608d868db8cba400853bf9bfee1e2acbb34d30ab0ca6f1bcefbb80e.
    8. Stolen assets were consolidated through addresses including 0x2913d90d94c9833b11a3e77f136da03075c04a0f and 0x34611F6BBB0A5F6F8EB48146F4474BFF842AE893, then portions were sent to Tornado Cash in 100 ETH batches.
    9. Whitehat / copytrader responders also hit the vulnerable path; Dough later recovered about 76.2 ETH via transactions 0xB44F3098EE7A8A58505017078CF80516E684963146B031C9E211A72869E1CA0B and 0x8e7f7241a1a8d162dabacc0ddfeba5424ec4b75b20cdd60022778477173a7b6f.
  • What the attacker needed to be true:

    • flashloanReq() had to remain callable without proving that only the rightful Dough DSA owner or a tightly scoped protocol dispatcher was initiating the unwind.
    • The connector had to retain enough standing authority over user DSAs and token balances that a malicious downstream call could move collateral once it had been withdrawn.
    • ParaSwap-related calldata needed to survive Dough’s parsing and control flow without target / selector validation strong enough to prevent direct token calls.
    • Balancer flash liquidity had to be large enough to temporarily clear each victim’s Aave debt so Dough’s own deleverage logic would release collateral before the loan was repaid.
  • Technical anatomy worth remembering:

    • The clever step was not the flash loan by itself. The flash loan only made the victim position look deleverage-ready for a moment. The real theft primitive was Dough’s willingness to treat attacker-shaped swap calldata as authenticated unwind intent.
    • This incident sits between an approval drain and a leverage-helper bug. The attacker did not drain ordinary EOAs with stale router approvals; they drained user-controlled Dough proxy accounts after Dough’s connector temporarily liberated collateral from Aave.
    • The exploit is a good reminder that public deleverage helper is already a privileged surface. Once it can both repay debt and move collateral, every downstream swap byte becomes security-critical.
    • Dough’s postmortem is also useful because it shows the bug was not isolated to one connector file. The team later acknowledged the same flashLoanReq trust model in multiple connectors, which is exactly how generic adapter architectures magnify one validation mistake.
  • Representative transactions and addresses:

    • Attacker EOA: 0x67104175fc5fabbdb5A1876c3914e04B94c71741
    • First attack contract: 0x11A8DC866C5d03ff06bb74565b6575537B215978
    • Vulnerable connector highlighted by CertiK: 0x9f54e8eAa9658316bB8006e03fff1Cb191AaFBe6
    • First victim Dough DSA: 0x534a3bb1ecb886ce9e7632e33d97bf22f838d085
    • Other victim DSAs highlighted by CertiK: 0x53d2D9D2b36d2784D80297E6532e3BD965435021, 0x0d2b0c59D6a51eAE239A6C6eE29cFE73b79cC35b, 0x8f690502964348acbab0E3E3E81192A582715d89, 0xC581d6Ef24146f745d5Bc014cc114C8F0CA74783, 0x460fDebe3D0B26e9DC194De80B3111b369B29272, 0x3e9F59f371dB249F2D95cf1E6F5224Ff1D7328Ab, 0x52F0b1B251FCa6f2b1191803a0aA4d9f4dB6F924
    • First exploit transaction: 0x92cdcc732eebf47200ea56123716e337f6ef7d5ad714a2295794fdc6031ebb2e
    • Consolidation / stolen-funds addresses from Dough’s postmortem: 0x2913d90d94c9833b11a3e77f136da03075c04a0f, 0x34611F6BBB0A5F6F8EB48146F4474BFF842AE893, 0x932e71261ba289c5bcf197c0a7eabfe69d145488, 0xBA25624eD80eb244f941281abb37d9589cFFdBF0
    • Whitehat recovery transactions: 0xB44F3098EE7A8A58505017078CF80516E684963146B031C9E211A72869E1CA0B, 0x8e7f7241a1a8d162dabacc0ddfeba5424ec4b75b20cdd60022778477173a7b6f
  • Response / mitigation:

    • Dough disabled the affected flash-loan-based connectors, including ConnectFlashloanUniV3, ConnectFlashloanParaswap, ConnectorDeleverageUniV3, and ConnectorDeleverageParaswap.
    • Dough’s own postmortem says the hardening pass added a connector / action kill switch, stricter ECDSA-backed DSA creation, msg.sender DSA checks to restrict public flashloanReq() usage, a validated Augustus V5 registry lookup instead of trusting offchain tokenTransferProxy data, and a dedicated flash-loan storage contract to carry state from delegateDoughCall into flashloanReq().
    • The same postmortem also leaves useful unfinished work in the open: verify ParaSwap and UniswapV3 bytes onchain, add balance-expectation and max-slippage checks for loop / deloop / swap actions, and consider a more private flash-loan context instead of the public escrow-style path.
    • Dough worked with SEAL 911 and cooperating whitehats, recovered a small portion of funds, and offered the main attacker a 10% bounty.
    • The durable fix for this family is not just validate ParaSwap bytes better; it is treat public leverage/unwind helpers as privileged approval-funded route surfaces and bind payer scope, canonical DSA identity, downstream target / selector family, and spend meaning before collateral movement begins.

Pattern tags

  • arbitrary-call-token-approval-drain
  • Public leverage helper acted as a privileged wallet-spend surface: flashloanReq() was not a harmless convenience wrapper; it was the path that could repay debt, release collateral, and then speak arbitrary downstream calldata from a connector already trusted by user DSAs
  • Unwind sequencing and downstream call meaning drifted apart: the same deloop chain that legitimately reached AaveAction to free collateral later accepted attacker-shaped ParaSwap bytes, so deleverage this position quietly became call this token contract with delegated authority
  • Delegated proxy-account custody broadened the approval-drain family: Dough was not draining plain EOAs with stale router approvals, but user-controlled DSAs whose collateral-management permissions had already been handed to protocol connectors
  • Generic connector architecture multiplied one validation miss: Dough’s own postmortem said the same flashLoanReq trust model existed across four connectors, which is exactly how adapter fleets turn one under-validated execution primitive into a wider class of wallet-funded risk

Why the exploit worked

  • The exploit paid because Dough fused three powers into one public path without re-binding them tightly enough at runtime: anyone could trigger the flash-loan deleverage helper, the helper could temporarily reshape a victim DSA’s solvency state, and the same execution chain could forward attacker-controlled ParaSwap calldata through a raw external call.
  • The flash loan was only the setup. Its job was to make the victim position look momentarily deleverage-ready by repaying Aave debt. The actual theft happened later, when Dough treated the second-stage ParaSwap payload as if it still represented canonical unwind intent.
  • Dough’s connector already sat inside the victim DSA’s trust boundary. Once collateral had been withdrawn from Aave into the DSA / connector-controlled flow, the attacker did not need to break ParaSwap, Aave, or Balancer; they only needed Dough to keep believing that attacker-shaped bytes were still part of a legitimate deleverage route.
  • CertiK’s reconstructed call chain is the useful anatomy to preserve: flashloanReq() → executeOperation() → deloopInOneOrMultipleTransactions() → deloopAllCollaterals() → flashloanVars.paraSwapContract.call(). The vulnerability was not merely bad calldata somewhere, but a privileged unwind pipeline whose final external-call boundary never proved that the downstream target and selector still matched the unwind it had just authorized.
  • Dough’s own postmortem sharpens the broader lesson: once the team discovered the same trust model in four flash-loan connectors, the real failure stopped looking like one ParaSwap integration mistake and started looking like a reusable approval-funded route-authority problem across the connector layer.

Detection and audit pivots

  • Treat every public debt-repay / loop / deloop / flash-loan helper as a wallet-drain surface when it can act through user proxy accounts. The first question is not can it deleverage? but who is allowed to start this state transition for which DSA, and what exact downstream call family remains authorized after collateral is freed?
  • Reconstruct the full authority chain, not only the final malicious call. For Dough-style connectors, auditors should walk entrypoint -> flash-loan callback -> debt repayment -> collateral release -> swap / settlement call and ask at which hop attacker-controlled bytes can stop meaning canonical unwind and start meaning arbitrary token call.
  • Search connector fleets for repeated public entrypoints like flashloanReq() that share the same trust model across multiple files. Dough’s own mitigation list is the clue: if one connector is vulnerable and three siblings expose the same public orchestration surface, review them as one family before production does it for you.
  • Watch for downstream calls from deleverage connectors to token contracts or arbitrary ParaSwap / aggregator targets that are inconsistent with expected unwind counterparties. In this incident, the key anomaly was not flash loan happened; it was connector that should be swapping now directly called WETH.transferFrom(...).
  • Test victim-owned proxy accounts, not just EOAs. Systems that route through DSAs, smart wallets, or user vault proxies often hide approval-drain risk inside delegated account-management permissions rather than inside classic ERC-20 approvals granted by a plain wallet.

Failure-prevention checklist

  • Restrict flash-loan deleverage entrypoints to the canonical actor for the DSA being unwound: the DSA owner, a signed route intent, or a tightly scoped protocol dispatcher. public flashloanReq() plus delegated DSA authority is already too much ambient power.
  • Bind one flash-loan context object before external control leaves: DSA identity, connector branch, debt asset, collateral set, canonical downstream swap target family, recipient semantics, and one-shot consumption rules should all be committed in trusted local storage before executeOperation() resumes.
  • Validate aggregator bytes semantically, not cosmetically. Onchain checks need to prove that ParaSwap / Uniswap payloads still mean sell this asset through this approved Augustus path for this recipient rather than merely some calldata blob that balances could survive.
  • Derive privileged downstream helpers from validated registries or code-pinned contracts, not from attacker-shaped or offchain-supplied tokenTransferProxy-style fields. Dough’s own postmortem is explicit that this had to change.
  • Separate repay debt, withdraw collateral, and swap / settle proceeds into stage-scoped permissions. Freeing collateral for a deleverage should not automatically authorize arbitrary downstream token calls from the same connector frame.
  • Add per-connector kill switches and keep a live inventory of sibling connectors sharing the same flash-loan or swap trust model. Dough’s four-connector disable list is the operational clue that connector fleets must be reviewed as one risk surface.
  • Pair semantic validation with economic bounds: onchain balance expectations, max slippage, and per-asset amount caps should constrain the canonical unwind after the route has already been proven authentic, not substitute for target / selector validation.
  • Fuzz the whole deleverage chain using victim DSAs, not just happy-path swaps: adversarial tests should try fake ParaSwap targets, direct token transferFrom() calls, cross-connector context reuse, and arbitrary callers invoking someone else’s unwind.

Audit prompts

  • Who is actually authorized to call flashloanReq() for a given DSA, and where is that authorization re-checked after the flash-loan callback returns control?
  • What exact state object binds this flash loan belongs to this DSA and this connector branch across delegateDoughCall, flashloanReq(), executeOperation(), and the final swap / settlement step?
  • Can paraswapCallData, tokenTransferProxy, Augustus address selection, or any equivalent downstream routing field still be attacker-defined at the moment the connector makes its raw external call?
  • After Aave debt is repaid and collateral becomes withdrawable, what prevents the next downstream call from targeting an ERC-20 token contract or arbitrary recipient instead of a canonical unwind counterparty?
  • Which sibling connectors share the same public flash-loan entrypoint or delegated-call trust model, and do they all inherit the same calldata validation, DSA binding, and kill-switch controls?
  • Are balance checks and slippage limits proving route authenticity, or are they only checking that value moved somehow after a semantically unconstrained external call already happened?
  • If the protocol uses registries such as Augustus for swap routing, is the connector deriving the executable helper from a validated onchain registry / code identity, or can route bytes still smuggle in a different spender or token-pull helper?
  • In logs and traces, what would distinguish a healthy deleverage from a Dough-style exploit wave early enough to pause connectors before copycats spread: repeated public callers, victim DSAs that never initiated the unwind, or connector-to-token transferFrom() calls during the swap stage?

Lessons that should already be standard

  • Public deleverage helpers are privileged wallet-spend surfaces. If a connector can repay debt, free collateral, and forward dynamic calldata, it belongs in the same risk class as an approval-funded router, not in the same class as an ordinary convenience wrapper.
  • Flash loans were not the root cause here; they were the temporary solvency shim. The security failure was letting position now looks unwindable collapse into attacker may choose the next external call.
  • Delegated smart-account custody does not remove approval-drain risk; it relocates it. Dough’s DSAs were not EOAs with stale allowances, but they still exposed the same core problem once a trusted connector could speak arbitrary token calldata on their behalf.
  • Offchain routing hints and proxy-helper fields are custody-critical. Dough’s own move to derive tokenTransferProxy from a validated Augustus address is the concrete reminder that integration metadata often sits directly on the spend-authority boundary.
  • Connector fleets amplify trust-boundary mistakes. Once Dough found the same flashLoanReq method family across four connectors, the real lesson became architectural: review shared entrypoint and callback patterns as one control surface, not as isolated files.
  • Kill switches and route inventory are part of the design, not just incident-response garnish. A system that can move user collateral through public helper flows needs a fast way to answer which connectors can spend with this authority right now?
  • Balance checks and slippage bounds are downstream controls. They help only after the protocol has already proven that the route, caller, DSA, and external counterparty are the canonical ones.

Why this incident matters

  • It is a strong arbitrary-call approval-drain exemplar where the stolen balances lived in user proxy accounts rather than plain EOAs.
  • It preserves a useful architecture lesson for account-abstraction-style DeFi products: if connectors can both manipulate debt and forward dynamic integration calldata, then route validation is as security-critical as collateral accounting.
  • The preserved healthy boundary was not just ParaSwap calldata should be cleaner, but one route-local authority object should have fused which Dough DSA was allowed to fund this unwind, which connector branch was active, which downstream swap targets / selectors were canonical, and what recipient semantics collateral release was supposed to have.
  • It also broadens the approval-drain corpus beyond pure aggregators like Dexible, Socket, and LI.FI into position-management helper with delegated collateral authority territory.

Internal linkages

Control surface