Reproduced Exploit

Cover Protocol — unlimited approve lets an attacker take 1000 DAI off the router

addCoverAndCreatePools pulls the depositor's collateral onto the router and approves _protocol for the maximum uint256. _protocol is not checked. The attacker's addCover returns false, so the 1,000 DAI stays on the router, and the attacker transferFroms it.

Jan 2021•Other•access-control•3 min read

Chain

Other

Category

access-control

Date

Jan 2021

Source

AuditVault

EVM Playground

Source-level debugger — step opcodes and Solidity in sync

evm-hack-analyzer

The attack is replayed in an in-browser EVM preloaded with the exact dumped fork state. The execution tree shows every call; step by Solidity line or by opcode across all depths — source, Stack, Memory, Storage, Balances (native / ERC-20 / NFT), Transient storage and Return value stay in sync. Click a tree node, opcode, or source line to jump. No backend, no live RPC.

Loading fork state…

Source & credit. Reproduction of a public audit finding curated by AuditVault — the original finding: 28663-possibility-to-steal-tokens-from-the-contract-balance-mixbyt. Standalone Foundry PoC and full write-up: 28663-possibility-to-steal-tokens-from-the-contract-balance-mixbyt_exp in the evm-hack-registry mirror.


Vulnerability classes: vuln/access-control/missing-auth · vuln/logic/missing-validation

Reproduction: a self-contained Foundry PoC that compiles and runs offline with only forge-std — no fork and no RPC. Trace: output.txt. PoC: test/28663-possibility-to-steal-tokens-from-the-contract-balance-mixbyt_exp.sol.


Key info#

ImpactHIGH — the attacker receives N = 1,000 DAI from the router
ProtocolCover Protocol CoverRouter
Vulnerable codeapprove(_spender, uint256(-1)) with no protocol check
Bug classUnlimited allowance plus a failed addCover that still succeeds
FindingMixBytes — Cover Protocol Peripheral, 11 January 2021 · id 28663
ReportCover Protocol Peripheral
SourceAuditVault finding
StatusReproduced. uint256(-1) is written as type(uint256).max so solc 0.8 accepts it.
Compilersolc 0.8.27, cancun, via IR

TL;DR#

addCoverAndCreatePools pulls the depositor's collateral onto the router and approves _protocol for the maximum uint256. _protocol is not checked. The attacker's addCover returns false, so the 1,000 DAI stays on the router, and the attacker transferFroms it.

The depositor ends at 0. The router ends at 0. The attacker holds 1,000 DAI.

The vulnerable code#

SOLIDITY
if (_token.allowance(address(this), _spender) < _amount) {
    // @> VULN: audited approve(_spender, uint256(-1))
    _token.approve(_spender, type(uint256).max);
}

The audited source is Solidity 0.7 and writes uint256(-1). That literal does not compile on 0.8. The value is the same maximum.

Root cause#

Two missing checks stack. The spender of the unlimited allowance is whatever address the caller passed as _protocol. And a false return from addCover does not revert or refund, so the tokens the router just pulled are left sitting under that allowance.

Preconditions#

  • A depositor holds 1,000 DAI and calls the router with the attacker's address as the protocol.
  • addCover on that address returns false and does not move the tokens itself.

Attack walkthrough#

  1. Mint 1,000 DAI to the depositor.
  2. The depositor approves the router and calls addCoverAndCreatePools(attacker, DAI, 1000).
  3. The router pulls 1,000 DAI.
  4. The allowance is below the amount, so the router approves type(uint256).max to the attacker.
  5. addCover returns false. The DAI is still on the router.
  6. The attacker transferFroms 1,000 DAI to themselves.

Control: the router accepts only one protocol address and reverts before the pull when the caller names anyone else. The depositor still holds 1,000 DAI.

Diagrams#

flowchart TD A["Depositor sends 1000 DAI into the router"] --> B["Router approves the attacker for the maximum"] B --> C["addCover returns false"] C --> D["1000 DAI stays on the router"] D --> E["Attacker transferFrom pulls N"] E --> F["Depositor and router are at 0"]

Impact#

The depositor loses 1,000 DAI. The attacker holds it. Any later balance left on the router is exposed to the same allowance.

Remediation#

Approve only the amount this call needs, and only to a known protocol. If addCover returns false, revert and send the collateral back.

Sources#

Taxonomy#

  • vuln/access-control/missing-check · vuln/logic/missing-validation

Sources & further analysis#

Reproductions & code

Alerts & third-party analyses

  • Web3Sec X hacked database: search.
  • Rekt leaderboard: search.
  • Solodit incident search: search.

These dashboards index community alerts tweets, post-mortems, and independent write-ups. Reach them through the protocol name above to cross-check this reproduction against other analyses.