Unsafe Transfers Leading to an Attack
On March 14, 2025, H2O Token suffered an exploit that resulted in a loss of approximately $22,000. Let's examine how this attack occurred.
Attacker: https://bscscan.com/address/0x8842dd26fd301c74afc4df12e9cdabd9db107d1e
Attack Contract: https://bscscan.com/address/0x03ca8b574dd4250576f7bccc5707e6214e8c6e0d
Vulnerable Contract: https://bscscan.com/address/0xe9c4d4f095c7943a9ef5ec01afd1385d011855a1#code
Attack Tx: https://bscscan.com/tx/0x729c502a7dfd5332a9bdbcacec97137899ecc82c17d0797b9686a7f9f6005cb7
https://bscscan.com/tx/0x994abe7906a4a955c103071221e5eaa734a30dccdcdaac63496ece2b698a0fc3
Exploit analysis
H2O is a BEP-20 token derived from the basic ERC20 standard. The token incorporates both H2 and O2 tokens. One of H2O's key features is a logical flaw in its transfer()
implementation — when tokens are transferred from the pair, a mechanism called _calulate
enables the minting of additional H2 or O2 tokens.
The system first calculates a rating amount that scales with the user's H2O balance—a higher balance results in a higher rating. Subsequently, H2 and O2 tokens are minted in random quantities determined by the getRandomOnchain
function.
If the minting amount of H2 is greater than 10
and O2 is greater than 5
(based on the 2:1 ratio of H2O — perhaps to simulate the H2O chemical formula), the token will burn an amount of both tokens to reduce the mint amount. The contract transfers the mint amount in H2O to the to
address.
An important note is that the getRandomOnchain
function is not completely random. Its randomness mechanism relies on block.timestamp
, msg.sender
, and block.number
— all parameters that can be predicted or calculated.
The issue specifically occurs with the _calulate
function. When the from
parameter is a pair, _calulate
is triggered. The attacker transfers H2O to the PancakeSwap USDT-H2O pair, which creates an imbalance between USDT and H2O reserves. By calling the skim
function to rebalance, the transfer is executed again — this time with the pair msg.sender
and the attacker as the to
parameter.
After each loop, an amount of H2/O2 is minted. The first attack gives the attacker approximately 169,731,921
O2.
At this time, if the attacker wants to access H2O, he needs to attack again using the H2 token. One method is to predict the getRandomOnchain
function by manipulating parameters. However, since the attacker encounters two revert transactions, he must simply run the attack script multiple times in the hope of success.
In the fourth transaction, he successfully mints H2 and burned all available O2 to drain approximately 22,000
USDT.
Conclusion:
The vulnerability occurred because the H20 Token contract, when designing the buying mechanism from the PancakeSwap Pair, failed to consider that skim
could achieve the same effect while modifying the ERC20 transfer function, leading to the attack.