All incidents

BSC bridge admin self-grant DEFAULTADMINROLE and vault drain

Share
Aug 28, 2024 22:48 UTCAttackLoss: 10,463.64 BEP20DAIManually checked1 exploit txWindow: Atomic
Estimated Impact
10,463.64 BEP20DAI
Label
Attack
Exploit Tx
1
Addresses
1
Attack Window
Atomic
Aug 28, 2024 22:48 UTC → Aug 28, 2024 22:48 UTC

Exploit Transactions

TX 1BSC
0x56d3ed5f635b009e19d693e432479323b23b3eb368cf04e161adbc672a15898e
Aug 28, 2024 22:48 UTCExplorer

Victim Addresses

0x667dfed3c4d56df32ecc3f2e3ce5bcc4ef03a6dcBSC

Loss Breakdown

10,463.64BEP20DAI

Similar Incidents

Root Cause Analysis

BSC bridge admin self-grant DEFAULTADMINROLE and vault drain

Protocol: Unknown BSC bridge/vault pair (0x8de7... and 0x667d...) ACT classification: ATTACK on BSC (is_act = True)

Incident Overview & TL;DR

On BSC, an unprivileged EOA 0x8477... deployed an orchestrator that created helper contract 0xd0a6..., used 0xd0a6... to self-assign DEFAULT_ADMIN_ROLE on bridge/admin contract 0x8de7..., and in the same transaction called adminWithdraw so that vault 0x667d... transferred 10463638549999999999999 BEP20DAI to profit EOA 0x603d....

Bridge/admin contract 0x8de7... implements grantRole and adminWithdraw so that a caller without any prior role membership can grant itself DEFAULT_ADMIN_ROLE and immediately pass the adminWithdraw authorization check, which breaks the intended invariant that only pre-authorized admins can move vault funds.

Key Background

  • Vault/handler contract 0x667d... holds BEP20DAI and exposes a withdrawal entrypoint that requires msg.sender to equal a configured bridge/admin address stored in slot 0 (_bridgeAddress); only that bridge/admin contract can instruct the vault to transfer out tokens.
  • Bridge/admin contract 0x8de7... is an AccessControl-style contract with DEFAULT_ADMIN_ROLE (0x0) and another non-zero role, and it exposes grantRole, revokeRole, adminAddRelayer, adminWithdraw, and pause/unpause functions wired through role membership mappings and CALLER-based checks.
  • RoleGranted and RoleRevoked logs for 0x8de7... before the incident show that DEFAULT_ADMIN_ROLE was held by 0xe9fcb406ac9a93942dd9318f397027146a9ed1d9 and that helper 0xd0a6... had no roles prior to the incident block.
  • The attacker cluster consists of funding/sender EOA 0x8477..., orchestrator contract 0xa2d1..., helper contract 0xd0a6..., and profit EOA 0x603d..., linked by deployment relationships in the incident trace and the BEP20DAI transfer to 0x603d....

Vulnerability Analysis & Root Cause

Category: ATTACK

0x8de7... has flawed internal access control: its grantRole implementation allows a caller without existing admin permissions to grant itself DEFAULT_ADMIN_ROLE, and adminWithdraw relies on this role check to authorize withdrawals from vault 0x667d..., so a single self-grant and adminWithdraw call drains vault funds.

Invariant Only accounts that already hold DEFAULT_ADMIN_ROLE on 0x8de7... are allowed to grant or revoke DEFAULT_ADMIN_ROLE and invoke adminWithdraw to move assets out of vaults that trust 0x8de7... as _bridgeAddress.

Breakpoint In tx 0x56d3ed5f635b009e19d693e432479323b23b3eb368cf04e161adbc672a15898e, helper contract 0xd0a6... without any prior RoleGranted history executes grantRole(0x0,0xd0a6...) on 0x8de7..., emitting RoleGranted(DEFAULT_ADMIN_ROLE,0xd0a6...,0xd0a6...), and then immediately satisfies the adminWithdraw authorization check in the same transaction to instruct vault 0x667d... to transfer BEP20DAI to 0x603d....

Decompiled and bytecode-level analysis of 0x8de7... shows an AccessControl-style role mapping where grantRole(bytes32 role, address account) records membership and emits RoleGranted events, and adminWithdraw constructs and sends a call to a vault only after verifying that msg.sender holds an admin role. RoleGranted/RoleRevoked logs before block 41770502 show that DEFAULT_ADMIN_ROLE was assigned to 0x5e44... and then to 0xe9fc..., with 0x5e44... revoked, and no grants to 0xd0a6.... In the incident transaction, helper 0xd0a6... calls grantRole(0x0,0xd0a6...) on 0x8de7... and the resulting RoleGranted(DEFAULT_ADMIN_ROLE,0xd0a6...,0xd0a6...) log proves that 0xd0a6... successfully added itself as an admin without any pre-existing role. Immediately afterward, 0xd0a6... calls adminWithdraw on 0x8de7..., which checks the role mapping using CALLER and treats 0xd0a6... as an authorized admin, then calls vault 0x667d... (whose slot0 storage snapshot at block 41770501 equals 0x8de7...) to execute its withdrawal function and forward 10463638549999999999999 BEP20DAI to 0x603d.... This control flow demonstrates that 0x8de7... does not enforce the invariant that only already-authorized admins can grant admin rights or withdraw vault funds; instead, any caller can self-grant DEFAULT_ADMIN_ROLE and immediately exercise full adminWithdraw power.

Vulnerable components

  • Bridge/admin contract 0x8de7...: grantRole(bytes32,address) implementation and associated role mapping and RoleGranted logging.
  • Bridge/admin contract 0x8de7...: adminWithdraw function that checks DEFAULT_ADMIN_ROLE for CALLER and then calls vault 0x667d... to move BEP20DAI.
  • Vault/handler contract 0x667d...: withdrawal entrypoint that trusts _bridgeAddress (0x8de7...) and executes ERC-20 transfers when called by 0x8de7....

ACT exploit conditions

  • Vault 0x667d... stores 0x8de7... in slot0 as _bridgeAddress so that only 0x8de7... can trigger withdrawals.
  • 0x8de7... deploys with grantRole and adminWithdraw logic that allows a caller without prior admin role membership to grant itself DEFAULT_ADMIN_ROLE and immediately pass the adminWithdraw authorization check.
  • An unprivileged EOA can deploy a helper contract with the same logic as 0xd0a6... and send a transaction that self-grants DEFAULT_ADMIN_ROLE on 0x8de7... and calls adminWithdraw in a single atomic sequence.
  • Vault 0x667d... holds sufficient BEP20DAI balance at the time of the adminWithdraw call so that the transfer to 0x603d... succeeds.

Security principles violated

  • Broken authorization and role management: 0x8de7... allows a caller without existing admin privileges to grant itself DEFAULT_ADMIN_ROLE and then use adminWithdraw to move assets, violating the principle that only pre-authorized administrators control high-impact operations.
  • Improper trust boundary between vault and bridge/admin: vault 0x667d... treats any call from 0x8de7... as authorized while 0x8de7... demonstrably has flawed internal access control in its grantRole/adminWithdraw implementation, so the combined system does not maintain end-to-end authorization guarantees for vault funds.
  • Lack of separation of duties and defense in depth: the same DEFAULT_ADMIN_ROLE on 0x8de7... can both assign itself and execute adminWithdraw, and vault 0x667d... does not enforce any additional checks beyond trusting 0x8de7..., concentrating authority in a single, internally misconfigured contract.

Adversary Flow Analysis

Single BSC transaction in which an unprivileged EOA deploys an orchestrator and helper, self-grants DEFAULT_ADMIN_ROLE on 0x8de7..., and immediately calls adminWithdraw to drain BEP20DAI from vault 0x667d... to a profit EOA.

Adversary-related cluster accounts

  • 0x847705eeb01b4f2ae9a92be12615c1052f52e7ad (BSC:56), EOA=True, contract=False: Sender of the incident transaction 0x56d3ed5f... that deploys orchestrator 0xa2d1... and funds the attack by paying gas.
  • 0xa2d1e47e1a154dd51f2eae0413100c4f8abe13c7 (BSC:56), EOA=False, contract=True: Contract created in the incident transaction by EOA 0x8477... that orchestrates deployment of helper 0xd0a6... and calls into 0x8de7....
  • 0xd0a60158b6a5ef01cee3ba9652df695671f366e3 (BSC:56), EOA=False, contract=True: Helper contract created by orchestrator 0xa2d1... within the incident transaction that calls grantRole and adminWithdraw on 0x8de7..., and receives DEFAULT_ADMIN_ROLE according to the RoleGranted log.
  • 0x603dd1d86b9bc3ba7ab5c0267eaf7293ca2abc52 (BSC:56), EOA=True, contract=False: EOA that receives 10463638549999999999999 BEP20DAI from vault 0x667d... in the incident transaction and keeps this balance over the observed window with no outgoing BEP20DAI transfers.

Victim candidates

  • Bridge/admin contract 0x8de7... (BSC:56, 0x8de7eaba58efb23b6f323984377af582b23134e9), is_verified=unknown
  • Vault/handler contract 0x667d... (BSC:56, 0x667dfed3c4d56df32ecc3f2e3ce5bcc4ef03a6dc), is_verified=unknown

Adversary lifecycle stages

  • Adversary initial funding and setup: EOA 0x8477... uses its existing BNB balance to pay gas for the incident transaction that deploys the orchestrator and executes the exploit. Evidence: artifacts/root_cause/seed/56/0x56d3ed5f635b009e19d693e432479323b23b3eb368cf04e161adbc672a15898e/metadata.json
  • Adversary contract deployment: The incident transaction deploys orchestrator contract 0xa2d1... from EOA 0x8477..., and 0xa2d1... deploys helper contract 0xd0a6... within the same transaction. Evidence: artifacts/root_cause/seed/56/0x56d3ed5f635b009e19d693e432479323b23b3eb368cf04e161adbc672a15898e/trace.cast.log
  • Adversary privilege escalation and vault drain: Helper contract 0xd0a6... calls grantRole(0x0,0xd0a6...) on 0x8de7..., receives DEFAULT_ADMIN_ROLE as shown by the RoleGranted log, and then calls adminWithdraw on 0x8de7..., which passes its role check and instructs vault 0x667d... to transfer 10463638549999999999999 BEP20DAI to attacker EOA 0x603d.... Evidence: artifacts/root_cause/seed/56/0x56d3ed5f635b009e19d693e432479323b23b3eb368cf04e161adbc672a15898e/trace.cast.log; artifacts/root_cause/data_collector/iter_4/contract/56/0x8de7eaba58efb23b6f323984377af582b23134e9/logs/role_granted_logs_chunked_smallrange.json; artifacts/root_cause/data_collector/iter_4/address/56/0x603dd1d86b9bc3ba7ab5c0267eaf7293ca2abc52/transfer_logs_window.json

References

  • [1] Seed transaction trace and metadata: artifacts/root_cause/seed/56/0x56d3ed5f635b009e19d693e432479323b23b3eb368cf04e161adbc672a15898e/
  • [2] Decompiled bridge/admin 0x8de7... and vault 0x667d...: artifacts/root_cause/data_collector/iter_4/contract/56/
  • [3] RoleGranted/RoleRevoked logs for 0x8de7...: artifacts/root_cause/data_collector/iter_4/contract/56/0x8de7eaba58efb23b6f323984377af582b23134e9/logs/
  • [4] Vault slot0 storage snapshot for 0x667d... at block 41770501: artifacts/root_cause/data_collector/iter_4/contract/56/0x667dfed3c4d56df32ecc3f2e3ce5bcc4ef03a6dc/storage/slot0_block_41770501.json
  • [5] BEP20DAI Transfer logs for attacker EOA 0x603d...: artifacts/root_cause/data_collector/iter_4/address/56/0x603dd1d86b9bc3ba7ab5c0267eaf7293ca2abc52/transfer_logs_window.json