배경

2025년 3월 14일, 우리는 BNB Smart Chain 프로젝트 H2O 에 대한 공격을 감지했습니다. 공격 해시는 다음과 같습니다.

한국어: https://bscscan.com/tx/0x729c502a7dfd5332a9bdbcacec97137899ecc82c17d0797b9686a7f9f6005cb7

https://bscscan.com/tx/0x994abe7906a4a955c103071221e5eaa734a30dccdcdaac63496ece2b698a0fc3

공격을 받은 프로젝트는 H2O였으며, 이 공격으로 인해 총 22,000달러의 손실이 발생했습니다.

공격 및 사고 분석

첫 번째 공격에서 공격자는 플래시론을 공격의 초기 자본으로 사용하여 PancakeSwapV3 풀에서 100,000 USDT를 먼저 빌렸습니다.

제로아워 테크놀로지 || H2O 공격 분석

이후, 초기 대출 자금은 USDT-H2O PancakeSwap 페어에서 66,439,209 H2O를 교환하는 데 사용되었습니다.

제로아워 테크놀로지 || H2O 공격 분석

그런 다음 공격자는 PancakeSwap 페어 USDT-H2O로 H2O를 자주 전송하여 페어 내 USDT 보유금과 H2O 보유금 간에 불균형을 초래한 다음, skim을 사용하여 페어 내 reserve0과 reserve1의 균형을 맞추어 공격을 수행했습니다. H2O의 취약성은 전달 함수에 나타난다. 우리는 전달 함수의 구체적인 구현을 볼 수 있다.

제로아워 테크놀로지 || H2O 공격 분석

문제는 구체적으로 함수에서 발생합니다. from이 PancakeSwap Pair인 경우 _calulate 함수가 호출됩니다. 공격자는 Pair의 Reserve0과 Reserve1 사이의 불균형을 이용하여 skim을 호출하여 자기 자신에게 전송하므로, 전송을 보낸 사람은 Pair 주소입니다. 다음으로, 이 기능의 구체적인 구현을 살펴보겠습니다.

제로아워 테크놀로지 || H2O 공격 분석

이 기능의 논리는 매우 간단합니다. 계약 자체에서 보유한 H2O 토큰의 잔액에 따라 보상 비율(1%-5%, 잔액이 낮을수록 비율 낮음)을 동적으로 설정하고, 온체인 난수를 통해 H2 또는 O2 토큰을 비례적으로 사용자에게 분배합니다. 사용자가 최소 10개의 H2와 5개의 O2를 보유하면 2:1 비율로 토큰을 파기하고(화학 반응 2H₂+O₂→2H₂O를 시뮬레이션) 계약에서 H2O 토큰을 상환할 수 있습니다. 실제 상환 금액은 계약 잔액에 따라 제한됩니다.

체인에서 난수를 생성하는 계약의 논리는 다음과 같습니다.

제로아워 테크놀로지 || H2O 공격 분석

msg.sender의 blocktime, blocknumber, keccak256을 기준으로 계산됩니다. 첫 번째 공격에서는 공격자가 H2 토큰과 O2 토큰을 가지고 있지 않았기 때문에 이 공격에서 그는 169,731,921개의 O2 토큰을 획득했습니다. 다음으로, 공격자는 getRandomOnchain()%2 == 1인 한 공격을 완료할 수 있습니다. 그런 다음 공격자는 세 번 더 시도했지만 처음 두 번은 getRandomOnchain()%2가 0이어서 공격이 실패했습니다. 공격은 네 번째 시도에서 실제로 완료되었습니다.

제로아워 테크놀로지 || H2O 공격 분석

네 번째 공격에서 공격자는 먼저 플래시론을 공격의 초기 자본으로 사용하여 PancakeSwapV3 풀에서 100,000 USDT를 빌렸습니다.

제로아워 테크놀로지 || H2O 공격 분석

이후, 초기 대출 자금은 USDT-H2O PancakeSwap 페어에서 66,439,209 H2O를 교환하는 데 사용되었습니다.

제로아워 테크놀로지 || H2O 공격 분석

그런 다음 공격자는 PancakeSwap 페어 USDT-H2O로 H2O를 자주 전송하여 페어 내 USDT 보유금과 H2O 보유금 간에 불균형을 초래한 다음, skim을 사용하여 페어 내 reserve0과 reserve1의 균형을 맞추어 공격을 수행했습니다.

제로아워 테크놀로지 || H2O 공격 분석

위의 분석에 따르면 이 공격에서는 getRandomOnchain()%2이 1이므로 H2O가 H2Token을 주조합니다. 첫 번째 공격 당시 getRandomOnchain()%2이 0이었기 때문에 공격자는 대량의 O2 토큰을 획득했습니다. 따라서 H2O는 H2와 O2를 연소시킨 후 자신의 H2O를 공격자에게 전달합니다.

마지막으로 공격자는 갑자기 나온 H2O로 122,820 USDT를 교환했습니다. 100,000 USDT 플래시 대출과 50 USDT 이자를 상환한 후 최종 수익은 22,770 USDT였습니다.

요약하다

이 취약점의 주요 원인은 H20 토큰 계약이 PancakeSwap Pair에서 구매하기 위한 경제 모델을 설계할 때, ERC20 전달 함수를 수정할 때 skim도 동일한 목적을 달성할 수 있다는 점을 고려하지 않았기 때문입니다. 그 결과 공격자가 skim을 이용해 허공에서 막대한 인센티브를 얻었습니다. 프로젝트 당사자는 경제 모델, 가격 계산 메커니즘, 코드 작동 로직을 설계할 때 다자간 검증을 실시하고, 온라인으로 계약을 감사하기 전에 교차 감사를 위해 여러 감사 회사를 선택하는 것이 좋습니다.