Calculated from recorded token losses using historical USD prices at the incident time.
0x20dbf42970eb3fabe62615534c4ef15fd4d59596BSC0x79f9b0b81e08efc3690bb78611620cb1b708fccbBSCAn unprivileged adversary repeatedly drained USDT from pool contract 0x20dbf42970eb3fabe62615534c4ef15fd4d59596 on BSC by calling poolWithdraw(address,uint256,address,uint256,address[],uint256,bytes[]) (0xc7e36d91) with allSigners=[] and signatures=[].
The root cause is a function-level authorization gap: poolWithdraw allows value transfer and internal pool-balance decrement without enforcing the configured signer quorum (requiredSignatures=3) when signer arrays are empty.
The attack is ACT (is_act=true) because it is permissionless and reproducible from unrelated EOAs using public on-chain data only.
0x20dbf42970eb3fabe62615534c4ef15fd4d59596.0x79f9b0b81e08efc3690bb78611620cb1b708fccb.0x55d398326f99059ff775485246999027b3197955.0xc7e36d91 -> poolWithdraw(address,uint256,address,uint256,address[],uint256,bytes[])0x43251e03 -> poolFeeWithdraw(address,uint256,address,uint256,uint256,address[],bytes[])0x91f4526052060d7137919a8e2bb3ce6c2169e5a376ab002c4745f69841cfd784poolFeeWithdraw rejects empty signer arrays (invalid allSigners length), while poolWithdraw succeeds.Evidence snapshot (BSC pre-state simulation at block 0x4fbb1cd):
{
"requiredSignatures": "3",
"attacker_whitelist": "false",
"attacker_isAllowedSigner": "false",
"poolWithdraw_empty_signers": { "status": "success" },
"poolFeeWithdraw_empty_signers": {
"status": "revert",
"error": "execution reverted: invalid allSigners length"
}
}
This incident is an ATTACK-class authorization failure. The intended invariant is: any pool asset withdrawal must require valid authorization (authorized caller and/or signer quorum). In the observed implementation path, poolWithdraw can be executed with empty allSigners and signatures arrays, even when requiredSignatures=3. The call then proceeds to transfer USDT out of the victim and decrement pool accounting. Trace evidence confirms execution enters the proxy and delegatecalls into implementation 0x79f9... with selector 0xc7e36d91, then performs ERC20 transfer to attacker-controlled receivers. The same pre-state confirms attacker addresses are not whitelisted and not allowed signers. Because this path is reachable by arbitrary EOAs/contracts without privileged keys, the exploit is permissionless and repeatable.
83603917 (0x4fbb1cd) is publicly reconstructible and shows signer policy active (requiredSignatures=3) with positive victim USDT pool balance.poolWithdraw on victim proxy 0x20db... with:
expireTime,allSigners=[], signatures=[].DELEGATECALL to implementation 0x79f9..., still with selector 0xc7e36d91.PoolWithdraw, transfers USDT from victim, and decreases internal pool balance.0x1111111111111111111111111111111111111111 reproduces the same success with empty arrays; under identical conditions poolFeeWithdraw reverts with invalid allSigners length.Representative execution snippet (exploit tx 0x0d9e... call trace):
{
"from": "0xd25f43449231218c8be3871073938fcc2202ab56",
"to": "0x20dbf42970eb3fabe62615534c4ef15fd4d59596",
"type": "CALL",
"input_prefix": "0xc7e36d91",
"nested_call": {
"to": "0x79f9b0b81e08efc3690bb78611620cb1b708fccb",
"type": "DELEGATECALL",
"input_prefix": "0xc7e36d91"
}
}
Invariant break and breakpoint:
poolBalances[token] and transferring pool assets must require valid signer quorum/authorization.poolWithdraw accepts empty signer/signature arrays and still performs state-changing withdrawal transfer.Stage A - Unauthorized pool withdrawal
0x0d9e4478567aa33dad3bd7c9a79de5f2afc9c7037c026795aa838f5ab834ca80 (block 83603918): withdraws 59,974.609375 USDT to sender 0xd25f....0x3d936c59c9d446ee222361acc820be47054aea45f9f5fc92482fe973a596e475 (block 83574859): withdraws 59,781.25 USDT to sender 0x8c98....0x91f4526052060d7137919a8e2bb3ce6c2169e5a376ab002c4745f69841cfd784 (block 83630514): helper 0xebd... withdraws 59,991.77 USDT and forwards to 0xc6e....Stage B - Profit realization
0x0d9e... and 0x3d93..., attacker swaps stolen USDT through pair 0x16b9... and unwraps WBNB to native BNB.0x0d9e..., measured native delta for 0xd25f... is +94047881930522033994 wei net of gas.Stage C - Repetition
179747.629375 USDT.poolWithdraw).root_cause.json.0x0d9e4478567aa33dad3bd7c9a79de5f2afc9c7037c026795aa838f5ab834ca800x3d936c59c9d446ee222361acc820be47054aea45f9f5fc92482fe973a596e4750x91f4526052060d7137919a8e2bb3ce6c2169e5a376ab002c4745f69841cfd784requiredSignatures, whitelist/signer status, empty-array simulation outcomes).PoolWithdraw and transfer logs.