지난 한 주(2026/03/09 - 2026/03/15) 동안 BlockSec은 총 8건의 공격 사건을 탐지 및 분석했으며, 총 추정 손실은 약 $1.66M입니다. 아래 표는 각 사건을 요약하며, 이후 소절에서 각 사례에 대한 상세 분석을 제공합니다.
| 날짜 | 사건 | 유형 | 추정 손실 |
|---|---|---|---|
| 2026/03/09 | EtherFreakers 사건 | 결함 있는 비즈니스 로직 | ~$25K |
| 2026/03/10 | Alkemi 사건 | 결함 있는 비즈니스 로직 | ~$89K |
| 2026/03/10 | MT 사건 | 결함 있는 비즈니스 로직 | ~$242K |
| 2026/03/11 | AAVE 청산 사건 | 잘못된 설정 | ~$1.01M |
| 2026/03/11 | Planet Finance 사건 | 결함 있는 비즈니스 로직 | ~$10K |
| 2026/03/12 | AM 사건 | 결함 있는 비즈니스 로직 | ~$131K |
| 2026/03/12 | DBXen 사건 | 결함 있는 비즈니스 로직 | ~$149K |
| 2026/03/15 | Goose Finance 사건 | 결함 있는 비즈니스 로직 | ~$8K |
EtherFreakers 사건
간략한 요약
2026년 3월 9일, 이더리움의 NFT 게임인 EtherFreakers가 잘못된 이중 계산으로 인해 익스플로잇되어 약 $25K의 손실이 발생했습니다. 게임 내 각 NFT는 인출 가능한 ETH 잔액(이른바 "에너지")을 보유합니다. 게임 메커니즘으로서, 플레이어는 attack()을 사용하여 하나의 NFT가 다른 NFT를 포획하고 대상의 에너지를 획득할 수 있습니다. 그러나 컨트랙트는 회계 처리를 완료하기 전에 대상의 잔액을 지급하고 NFT를 전송합니다. 이때 전송 훅이 오래된 지급 이전 데이터를 읽어 그 일부를 글로벌 배당 풀에 다시 투입하여, 새로운 ETH 뒷받침 없이 풀을 부풀립니다. 공격자는 이 포획 메커니즘을 반복하여 글로벌 인덱스를 펌핑한 뒤, 다수의 NFT에서 부풀려진 잔액을 탈취했습니다.
배경
EtherFreakers는 온체인 NFT 게임으로, 각 NFT("Freaker"라고 불림)는 energy라는 인출 가능한 ETH 잔액을 보유합니다. 이 시스템은 배당 풀 방식으로 작동합니다: 특정 행동이 발생하면 ETH의 일정 비율이 모든 Freaker에게 비례하여 분배됩니다. 각 Freaker의 청구 가능한 ETH는 글로벌 누산기 freakerIndex와 토큰별 지분 가중치 fortune의 조합으로 추적됩니다.
구체적으로, 회계 공식은 다음과 같습니다: energyOf = basic + (freakerIndex - index) * fortune. freakerIndex는 _dissipateEnergyIntoPool(amount)가 실행될 때 증가하며, amount의 80%를 모든 Freaker에게 분배하고 20%는 창작자에게 분배합니다. charge()를 통한 직접 예치는 freakerIndex에 영향을 주지 않고 basic만 증가시킵니다. 따라서 freakerIndex의 증가는 항상 시스템에 실제로 유입되는 이더에 의해 뒷받침되어야 합니다. freakerIndex가 대응하는 ETH 유입 없이 증가하면, Freaker는 컨트랙트가 실제로 보유한 것보다 더 많은 이더를 상환할 수 있게 됩니다.
취약점 분석
근본 원인은 EtherFreak 컨트랙트(0x3A27...c0f33)의 잘못된 실행 순서입니다. 포획이 성공하면 attack() 함수는 다음 단계를 순서대로 실행합니다:
- 237번 줄: 방어자에게
targetCharge(대상 NFT의 전체 에너지)를 직접 ETH 전송으로 지급합니다. 이제 에너지는 소진됩니다. - 240번 줄:
_transfer(defender, capturer, targetId)를 호출하여 NFT를 이동시킵니다. 내부적으로_transfer()는 ERC-721 훅_beforeTokenTransfer()를 호출하며, 이는energyOf(targetId)의 0.1%를 인자로_dissipateEnergyIntoPool()를 호출합니다. 이것이_dissipateEnergyIntoPool()의 첫 번째 호출이며, 5단계가 아직 실행되지 않았으므로 오래된 값을 읽습니다. - 241번 줄:
_dissipateEnergyIntoPool(sourceSpent)를 명시적으로 호출합니다. 이것이 두 번째 호출이며, 일반 게임 로직의 일부입니다. - 244~251번 줄:
sourceId와targetId모두에 대해energyBalances를 업데이트합니다.
버그는 2단계에 있습니다: energyBalances[targetId]가 아직 업데이트되지 않았기 때문에, 훅은 여전히 지급 이전 잔액을 보고 이미 소진된 에너지의 일부를 배당 풀에 투입합니다. 1단계의 직접 ETH 지급과 2단계의 풀 투입이 동일한 에너지에서 발생하여, 새로운 ETH 뒷받침 없이 freakerIndex를 부풀립니다.


freakerIndex는 _dissipateEnergyIntoPool()가 호출될 때마다 증가합니다:

공격 분석
다음 분석은 트랜잭션 0x89e24d...9abd2942를 기반으로 합니다.
-
1단계:
1,700WETH를 대출합니다. -
2단계: 공격자가 제어하는 주소 아래에 새로운 Freaker, 토큰
590과 토큰591두 개를 발행합니다. -
3단계: 게임의
attack(590, 591)함수를 반복적으로 호출하여 포획 성공 분기에서 실행을 유지합니다. -
4단계: 각 성공 후, 같은 쌍을 재사용할 수 있도록 토큰
591을 헬퍼에게 다시 전송합니다. -
5단계: 각 성공적인 루프는 시스템이 실제로 보존하는 이더 이상으로
freakerIndex를 부풀립니다. -
6단계: 인덱스가 충분히 높아지면, 이전에 제어하던 Freaker 배치를 방전시킵니다. 토큰 ID
496에서520까지 각각0.278052246002402082이더로 방전됩니다. -
7단계: 탈취한 이더를
WETH로 변환하고,1,700WETH플래시 론을 상환한 뒤 약7.498WETH를 수익으로 보유합니다.
결론
근본 원인은 attack()의 포획 성공 흐름에 있습니다: EtherFreakers는 대상 토큰의 에너지 상태가 확정되기 전에 targetCharge를 지급합니다. 그 후 _transfer()가 _beforeTokenTransfer()를 트리거하여, 오래된 지급 이전 energyOf(targetId) 값을 읽고 그 일부를 풀에 투입합니다. 이는 새로운 이더 뒷받침 없이 freakerIndex를 증가시키므로, 동일한 대상 에너지가 지급과 풀 투입 모두에 계산됩니다. 이것은 재진입 버그가 아닌 비즈니스 로직 인플레이션 버그입니다.
향후 유사한 위험을 줄이기 위해:
-
동일한 트랜잭션이 아직 처리 중인 동안 전송 훅 내부에서 가변 상태의 경제적 값을 재계산하는 것을 피하십시오.
-
전송 훅이 상태 변수를 읽는 경우, 실행 순서가 결과에 영향을 미치지 않도록 하십시오(예: 훅이 실행된 후가 아닌 전에 상태를 확정).
Alkemi 사건
간략한 요약
2026년 3월 10일, 이더리움의 Alkemi 프로토콜이 익스플로잇되어 약 $89K의 손실이 발생했습니다. 근본 원인은 회계 오류와 결함 있는 비즈니스 로직입니다. 결함 있는 청산 로직은 누구든 동일한 트랜잭션 내에서 자신의 포지션을 청산하고 수익을 얻을 수 있게 합니다. 또한, 회계 오류로 인해 청산 시 공격자 자신의 담보 차감이 덮어쓰여져, 공격자가 의도된 비용 없이 청산 보상을 획득할 수 있습니다.
배경
Alkemi는 대출 프로토콜입니다. 차용자의 포지션이 담보 부족 상태가 되면, 누구든 liquidateBorrow()를 호출하여 부채의 일부를 상환하고 할인된 가격으로 담보를 압류할 수 있습니다. 과도한 청산을 방지하기 위해, 프로토콜은 트랜잭션당 상환 가능한 금액을 다음 세 값의 최솟값으로 제한합니다:
- 차용자의 현재 대출 잔액 (
currentBorrowBalance_TargetUnderwaterAsset). - 청산 할인 적용 후 차용자의 담보가 커버할 수 있는 최대 상환액 (
calculateDiscountedBorrowDenominatedCollateral()). - 계정을 청산 경계로 되돌리는 데 필요한 상환액 (
calculateDiscountedRepayToEvenAmount()), 시장이isSupported인 경우에만 확인.


취약점 분석
근본 원인은 Alkemi 프로토콜(0x4822...a888)의 결함 있는 비즈니스 로직과 회계 오류입니다. 차용자에게 미상환 부채가 있는 한 currentBorrowBalance_TargetUnderwaterAsset은 필연적으로 0보다 크고, 차용자에게 담보가 있는 한 calculateDiscountedBorrowDenominatedCollateral()이 반환하는 값도 필연적으로 0보다 크기 때문에, AlkemiEarnPublic 프로토콜은 사실상 calculateDiscountedRepayToEvenAmount()에 의존하여 특정 대출이 청산 가능한지 여부를 결정합니다. 이 함수에서 청산해야 할 부채의 양은 accountShortfall_TargetUser라는 변수를 기반으로 계산되어야 합니다.
그러나 실제 구현에서는 함수가 대신 전역 변수 closeFactorMantissa를 사용하여 상환 허용 부채 금액의 상한선을 계산하고 이 값을 반환합니다. 결과적으로, 공격자는 동일한 트랜잭션 내에서 대출을 받고 즉시 자신의 포지션을 청산할 수 있게 됩니다.
또한, liquidateBorrow() 함수에서 청산자와 차용자가 동일한 주소일 때, 변수 supplyBalance_TargetCollateralAsset과 supplyBalance_LiquidatorCollateralAsset은 동일한 스토리지 슬롯을 가리킵니다. 함수는 동일한 초기 잔액을 기반으로 "감소된 잔액"과 "보상된 잔액"을 각각 계산하고, 이후 동일한 스토리지 슬롯에 순서대로 다시 씁니다. 감소된 잔액이 먼저 쓰여지고 보상된 잔액이 이후에 쓰여지기 때문에, 감소 효과는 덮어쓰여져 사라지고 보상 결과만 남게 됩니다. 이를 통해 공격자는 수익을 더욱 증폭시킬 수 있습니다.
공격 분석
다음 분석은 트랜잭션 0xa170...6d9d를 기반으로 합니다.
-
1단계: 공격자는 Balancer에서
51e18WETH플래시 론을 실행합니다. -
2단계: 공격자는
51e18WETH를ETH로 언래핑하여 Alkemi 프로토콜에 공급합니다. -
3단계: 공격자는 Alkemi에서
39.5e18ETH를 대출합니다. -
4단계: 공격자는
39.5395e18ETH를 사용하여 자신의 포지션을 청산합니다. -
5단계: 공격자는 Alkemi에서
93.5e18ETH를 인출합니다. -
6단계: 공격자는 플래시 론을 상환하고
43.4e18ETH의 수익을 얻습니다.

결론
근본 원인은 결함 있는 청산 로직이 공격자로 하여금 동일한 트랜잭션 내에서 대출 후 자신의 포지션을 청산하여 수익을 얻을 수 있게 하는 반면, 잘못된 회계 로직은 공격자의 이득을 더욱 증폭시킨다는 것입니다.
향후 유사한 위험을 줄이기 위해:
- 차용자와 청산자에 대한 잔액 업데이트 시, 프로토콜은 잔액을 별도 계산 및 재기록을 위해 임시 메모리 변수에 복사하는 대신 스토리지 변수에 직접 연산해야 합니다.
MT 사건
간략한 요약
2026년 3월 10일, BNB Chain의 디플레이션 토큰 MT Token이 익스플로잇되어 약 $242K의 손실이 발생했습니다. 근본 원인은 결함 있는 거래 제한 로직과 특수 전송 조건의 일관성 없는 처리입니다. 디플레이션 단계 중 컨트랙트는 풀 준비금이 고정 임계값을 초과할 때 매수 작업을 제한합니다. 그러나 컨트랙트는 정확한 금액(예: 2e17 MT)의 전송을 리퍼럴 바인딩 행동으로 처리하여, 공격자가 매수 제한을 우회하고 초기 토큰을 획득할 수 있게 합니다. 또한, 제한 로직은 불완전한 경로 감지(isBuy)에 의존하여 Pair에서 Router로의 간접 스왑 경로를 커버하지 못하며, 화이트리스트 확인이 중요한 검증을 추가로 단락시킵니다. 공격자는 제한이나 수수료를 트리거하지 않고 MT 토큰을 축적하고, 통제된 유동성 운영과 거래를 통해 pendingBurnAmount를 조작하여, 토큰 가격이 인위적으로 부풀려진 비정상적인 상태로 풀을 몰았습니다.
배경
MT Token은 내장된 거래 제한이 있는 BNB Chain의 디플레이션 토큰입니다. 디플레이션 단계에서 컨트랙트는 풀의 MT 준비금이 21,000e18을 초과할 때 매수 작업을 차단합니다. 준비금이 이 임계값 아래로 떨어지면 디플레이션 단계가 종료되고 매수가 재활성화됩니다. MT Token에는 리퍼럴 메커니즘도 포함되어 있습니다: 정확히 2e17 MT 또는 1e17 MT의 전송은 일반 거래가 아닌 추천인 바인딩 행동으로 처리됩니다.
취약점 분석
근본 원인은 MT 컨트랙트(0x037E...b449)의 결함 있는 매수 제한 설계입니다. 정상적인 상황에서 공격자는 제한 단계에서 시드 자본으로 MT를 획득할 수 없어야 합니다. 그러나 컨트랙트는 정확히 2e17 MT의 전송을 매수가 아닌 리퍼럴 바인딩 행동으로 처리하여, 공격자가 매수 제한을 우회하면서 2e17 MT를 구매할 수 있게 합니다.

또한, 거래 제한은 isBuy 분기에 의존하여 구매를 차단하지만, "Pair에서 Router로" 경로를 커버하지 않습니다. Router와 Pair 모두 화이트리스트 주소이므로, 그러한 전송은 화이트리스트 확인에서 단락되어 매수 제한 로직에 도달하지 않습니다. 이를 통해 공격자는 매수를 Router로 라우팅하고 이후 유동성을 제거하여 토큰을 자신에게 돌려받는 방식으로 MT를 획득할 수 있습니다.

공격 분석
다음 분석은 트랜잭션 0xfb57...fca6를 기반으로 합니다.
-
1단계: 공격자는
~358,681e18WBNB를 플래시 론합니다. -
2단계: 공격자는
2e17MT를 구매하여 매수 제한을 우회합니다. -
3단계: 공격자는 유동성 추가를 위해 Pair에
4e12WBNB와2e17MT를 공급합니다. 이 전송은 위와 같은 이유로 수수료 부과 로직을 우회했습니다. -
4단계: 공격자는 Pair에서 Router로
~10,000,000e18MT토큰을 매수하여, 매수 제한과 수수료 부과 로직 모두를 우회합니다. -
5단계: 공격자는 유동성 포지션의 절반을 제거하여 Router가 보유한 모든
MT토큰을 추출한 뒤, 회수한MT를WBNB로 매도합니다. 이 단계에서pendingBurnAmount가 약9,000,000e18로 조작됩니다.
-
6단계: 공격자는
~10,000,000e18MT토큰을 다시 매수하여, 풀의MT준비금을~6,756,516e18으로 낮추었으며, 이는pendingBurnAmount보다 낮은 수준입니다.
-
7단계: 공격자는 남은 유동성 포지션 절반을 제거하고, 구매한
MT토큰을 인출한 후,distributeDailyRewards()를 호출하여 풀에서MT를 소각합니다. 결과적으로MT준비금이21,000e18으로 감소합니다.
-
8단계: 공격자는 모든
MT를~1,198e18WBNB로 스왑하고, 플래시 론을 상환한 뒤 수익을 확정합니다.
결론
이 익스플로잇은 잘못된 거래 제한으로 인해 발생했으며, 공격자가 정확히 BINDING_AMOUNT의 MT 토큰을 구매하여 매수 금지를 우회할 수 있었습니다. MT 토큰을 획득한 후, 공격자는 유동성 추가, Router로 MT 매수, 유동성 제거를 통한 토큰 회수를 통해 수수료 부과 로직과 매수 제한을 모두 우회할 수 있었습니다. 공격자는 이후 매도 작업을 통해 pendingBurnAmount를 축적하고 소각을 실행하여 풀 준비금을 비정상적인 상태로 몰아, 인위적으로 부풀려진 가격에 MT를 매도하고 수익을 얻었습니다.
향후 유사한 위험을 줄이기 위해:
- 전송 의미론과 거래 로직 사이에 엄격한 분리를 적용하십시오.
AAVE 청산 사건
간략한 요약
2026년 3월 11일, AAVE는 이더리움에서 $21M에 달하는 잘못된 청산을 겪어 약 $1.01M의 손실이 발생했습니다. 근본 원인은 wstETH에 대한 잘못된 오라클 가격으로, 원래 건전했던 포지션이 담보 부족 상태가 되었습니다. 결과적으로 사용자의 포지션이 청산되어 재정적 손실이 발생했습니다.
배경
AAVE는 오라클 어댑터를 사용하여 wstETH와 같은 래핑된 자산의 가격을 책정합니다. 어댑터 CAPO(Capped Price Oracle)는 기본 ETH/USD 가격에 전환 비율(getRatio(), 즉 wstETH 하나가 얼마만큼의 ETH에 해당하는지)을 곱하여 wstETH 가격을 도출합니다. 비율 조작을 방지하기 위해, CAPO는 스냅샷 기반 성장 상한을 적용합니다:
maxRatio = snapshotRatio + maxGrowthPerSecond x (currentTime - snapshotTimestamp)
그리고 가격 책정 시 getRatio() 출력을 고정합니다(currentRatio > maxRatio이면 maxRatio를 사용). 이 메커니즘은 비율과 결과 가격의 최대 상향 드리프트를 효과적으로 제한합니다.
취약점 분석
근본 원인은 CAPO 오라클 앵커 설정(0xe1D9...61Ef)의 시간-비율 불일치였습니다: 스냅샷 타임스탬프와 스냅샷 비율이 설정되었지만, 스냅샷 비율이 실제 wstETH/ETH 비율보다 낮게 구성되었습니다. 결과적으로 어댑터가 계산한 maxRatio가 실시간 비율보다 낮아져 getRatio()를 하향 고정하여, 체계적으로 wstETH/USD 오라클 가격을 과소평가했습니다. 이로 인해 담보 가치가 낮아져 wstETH를 담보로 사용하는 포지션의 건전성 지수가 감소하였고, 건전한 계정이 잘못된 방식으로 비건전 상태로 분류되어 청산되었습니다.


공격 분석
다음 분석은 트랜잭션 0x9064...8a9c를 기반으로 합니다.
-
1단계: 청산자는
~6304e18WETH를 플래시 론하여 차용자를 청산합니다. -
2단계: 청산자는 플래시 론을 상환하며 청산을 완료합니다.
결론
이 청산은 잘못된 오라클 가격 설정으로 인해 발생했으며, 건전 상태를 유지해야 할 차용자를 잘못된 방식으로 비건전 상태로 몰아 포지션 청산을 트리거했습니다.
향후 유사한 위험을 줄이기 위해:
-
각 업데이트 전에 중요한 파라미터의 정확성을 검증하십시오.
-
잘못된 파라미터를 거부하고 잘못된 설정이 성공적으로 적용되는 것을 방지하기 위해 구현에 검증 확인을 추가하십시오.
Planet Finance 사건
간략한 요약
2026년 3월 11일, Planet Finance가 BNB Chain에서 익스플로잇되어 약 $10K의 손실이 발생했습니다. 근본 원인은 프로토콜이 차용자의 저장된 대출 잔액 증가를 발생된 이자로 잘못 처리하여, 공격자가 반복적으로 대출을 받고 할인 정산을 트리거하여 기록된 부채를 과소계상할 수 있었다는 것입니다.
배경
Planet Finance는 차용자가 이자 할인으로 상환할 수 있는 대출 프로토콜입니다. 할인은 등급제로 적용되며, 사용자의 스테이킹된 GAMMA와 다른 자산의 스테이킹된 가치 비율에 따라 결정됩니다: 이 비율이 높을수록 상환 할인이 높아집니다. 할인 일정은 세 등급으로 구성되며, 최소 0%에서 최대 50%까지 범위입니다.
취약점 분석
근본 원인은 changeUserBorrowDiscount()에서 차용자의 할인을 정산할 때, 프로토콜(0x4c9E...F467)이 차용자의 저장된 대출 잔액 증가를 새롭게 발생된 이자로 잘못 처리했다는 것입니다. 결과적으로 발생 이자에만 적용되어야 할 할인이 새로 빌린 원금에 잘못 적용되어, 차용자의 기록된 부채가 부적절하게 감소했습니다. 공격자는 borrow와 changeUserBorrowDiscount 루프를 반복적으로 수행하여 과도한 할인을 축적하고, 온체인 기록 부채를 실제 대출 금액보다 지속적으로 낮게 만들어 궁극적으로 그 차이에서 수익을 얻을 수 있었습니다.


공격 분석
다음 분석은 트랜잭션 0x5f45...5ec9를 기반으로 합니다.
-
1단계: 공격자는
200,000e18USDT를 플래시 론합니다. -
2단계: 공격자는
5,000e18USDT를 사용하여WBNB를 구매한 뒤, 획득한WBNB로~8,726,524e18GAMMA를 구매합니다. -
3단계: 공격자는 먼저 획득한 모든
GAMMA를 gGAMMA 마켓에 스테이킹한 후, 나머지USDT를 담보로 공급하여 상환 할인을 5%로 높이고 이후 대출을 가능하게 합니다. -
4단계: 공격자는
borrow와updateUserDiscount를 반복적으로 호출하여 기록된 부채를 지속적으로 감소시킵니다.
-
5단계: 공격자는 최종적으로 부채를 상환하고 담보를 환매하여 수익을 실현합니다.
결론
이 사건은 Planet Finance의 changeUserBorrowDiscount()의 결함 있는 할인 정산 로직으로 인해 발생했으며, 차용자의 저장된 대출 잔액 증가를 새롭게 발생된 이자로 잘못 처리하고 이자 할인을 그 증분에 적용합니다. 공격자는 borrow와 updateUserDiscount를 반복적으로 호출하여 기록된 부채를 과소계상하고 궁극적으로 실제 부채보다 적게 상환하여 수익을 추출할 수 있습니다.
향후 유사한 위험을 줄이기 위해:
- 대출 프로토콜에서 이자와 신규 대출을 구별하십시오.
AM 사건
간략한 요약
2026년 3월 12일, BNB Chain의 디플레이션 토큰 AM Token이 익스플로잇되어 약 $131K의 손실이 발생했습니다. AM Token은 각 매도 시 유동성 풀에서 추가 소각을 트리거하여 총 공급량을 줄이는 디플레이션 메커니즘을 구현합니다. 그러나 소각은 즉시 실행되지 않습니다 - 대신, 전체 매도 금액이 toBurnAmount로 기록되고 실제 소각은 다음 매도 시로 연기됩니다. 이 지연은 기록과 실행 사이에 창구를 만들며, 그 동안 공격자는 AM을 다시 매수하여 풀의 AM 준비금을 toBurnAmount까지 줄일 수 있습니다. 다음 매도가 지연된 소각을 트리거하면, 전체 AM 준비금이 소멸되어 가격이 극단적으로 상승하고, 공격자는 AM을 매도하여 수익을 얻을 수 있습니다.
배경
AM Token은 BNB Chain의 디플레이션 토큰입니다. 각 매도 시 컨트랙트는 스왑에 관련된 AM 금액을 toBurnAmount로 기록하고, 다음 매도 시 해당 기록된 금액을 유동성 풀에서 소각합니다. 사실상, 매도는 풀의 AM 준비금을 줄이는 지연된 소각을 트리거합니다. 또한, 소각을 실행하기 전에 프로토콜은 누적된 totalTokenFee를 USDT로 스왑하여 수수료 할당 로직에 따라 분배합니다.
취약점 분석
근본 원인은 토큰(0x27f9...213f)의 매도 로직이 전체 스왑된 AM 금액을 toBurnAmount로 누적하고 다음 매도 시 AM/USDT 페어에서 토큰을 제거하고 pair.sync()를 호출하여 준비금을 업데이트하는 방식으로 소각을 실행한다는 것입니다. 이 설계는 공격자가 풀의 AM 준비금을 조작하고, 온체인 가격을 왜곡하여 차익 거래로 수익을 얻을 수 있게 합니다.


공격 분석
다음 분석은 트랜잭션 0xd0d1...f859를 기반으로 합니다.
-
1단계: 공격자는
~27,265,119e18USDC와~361,710e18WBNB를 플래시 론한 후, 이를~100,423,811e18USDT로 스왑합니다. -
2단계: 공격자는
~5,062e18AM토큰을USDT로 스왑하여, 컨트랙트의 기록된toBurnAmount를~4,303e18으로 조작합니다.
-
3단계: 공격자는
USDT를 AM Token으로 스왑하여, 풀의AM준비금을~4,303e18으로 낮춥니다.
-
4단계: 공격자는
6 weiAM을 풀에 전송하여 매도 경로 소각 로직을 트리거합니다. 결과적으로 컨트랙트는 풀에서 전체AM잔액을 소각하여AM준비금을 0으로 낮춥니다. 참고: 프로토콜은 소각 전에 먼저 누적된 수수료를USDT로 스왑하려 시도합니다. 이 수수료 전환 경로도 매도 분기 소각 로직을 트리거합니다. 소각이 실행되고AM준비금이 0에 도달하면, 수수료 스왑이 실패합니다. try/catch로 감싸져 있기 때문에, 실패는 트랜잭션을 되돌리지 않습니다. 대신 실행이 계속되고 수수료 누산기가 0으로 재설정됩니다.
-
5단계: 공격자는
pool.sync()를 호출하고 남은USDT와1 weiAM을 풀에 전송합니다. 두 토큰이 동시에 전송되었기 때문에, 컨트랙트는 이를 addLiquidity로 처리하여toBurnAmount가 누적되지 않았습니다.AM준비금은 7로 업데이트되었습니다.

-
6단계: 공격자는 남은
AM토큰을USDT로 스왑합니다. 이 스왑 중AM을 Pair에 전송하면 매도 경로 소각 로직이 트리거되어AM준비금이 1로 감소합니다. 또한, 4단계에서totalFeeAmount가 0으로 재설정되었기 때문에, 수수료를USDT로 전환하는 작업이 더 이상 실행되지 않아, 공격자가 인위적으로 부풀려진 가격에AM을 매도할 수 있었습니다.
-
7단계: 공격자는 플래시 론을 상환하고 남은 수익을 실현합니다.
결론
이 사건은 AM Token의 결함 있는 소각 메커니즘으로 인해 발생했으며, 각 매도의 스왑 관련 AM을 toBurnAmount로 누적하고 다음 매도 시 AM/USDT Pair에서 해당 금액을 소각하면서 pool.sync()를 호출합니다. 이를 통해 공격자는 Pair의 AM 준비금을 극단적인 수준으로 조작하고 인위적으로 부풀려진 가격에 AM을 매도하여 USDT를 탈취할 수 있습니다.
향후 유사한 위험을 줄이기 위해:
- 트랜잭션당 최대 소각 금액을 제한하고 소각 트리거 빈도를 제한하여, 공격자가 단기간에 풀의 토큰 준비금을 대량으로 소비하는 것을 방지하십시오.
DBXen 사건
간략한 요약
2026년 3월 12일, 이더리움과 BNB Chain의 번-투-언(burn-to-earn) 프로토콜 DBXen이 익스플로잇되어 총 약 $149K의 손실이 발생했습니다. 근본 원인은 _msgSender()와 msg.sender 사이의 불일치입니다. burnBatch()가 forwarder를 통해 호출될 때, 소각된 XEN 금액은 _msgSender()(공격자 제어)에 기록되지만, 사이클 기록은 msg.sender(forwarder)에 업데이트됩니다. 이 분리를 통해 공격자는 오래된 사이클 기록에 대한 보상과 수수료를 청구하여 비정상적으로 큰 지급을 받을 수 있습니다.
배경
DBXen은 번-투-언 프로토콜입니다: 사용자는 XEN 토큰을 소각하여 DXN 보상과 누적된 프로토콜 수수료의 일부를 받습니다. 핵심 메커니즘은 사이클 단위로 작동합니다. 사용자가 burnBatch()를 호출하면 두 가지가 발생합니다: (1) 소각된 XEN 금액이 호출자 주소(_msgSender()로 식별)에 기록되고, (2) XEN 컨트랙트가 onTokenBurned()를 통해 DBXen으로 콜백하여 호출자의 사이클 기록(소각 사이클과 lastFeeUpdateCycle)을 현재 사이클로 업데이트합니다.
보상과 수수료는 updateStats()를 통해 정산됩니다. 보상은 소각 사이클에서 사용자의 총 소각된 XEN 비율에 비례합니다. 수수료는 사용자의 마지막 기록된 사이클 이후 발생한 누적 프로토콜 수수료를 기반으로 합니다. 두 계산 모두 사용자의 사이클 기록이 최신 상태인지에 따라 달라집니다.




취약점 분석
근본 원인은 DBXen 프로토콜(0xf5c8...2abd)의 결함 있는 비즈니스 로직입니다. _msgSender() 함수는 msg.sender가 forwarder인지 확인합니다. 그렇다면 calldata의 마지막 20바이트를 반환하며, 이 반환값은 forwarder 컨텍스트에서 임의로 제어될 수 있습니다. 그러나 burnBatch()는 msg.sender가 보유한 XEN을 직접 소각합니다. 결과적으로, 공격자는 forwarder를 통해 burnBatch()를 호출하여 프로토콜이 forwarder가 보유한 XEN을 소각하고 forwarder의 소각 사이클 기록과 수수료 업데이트 사이클 기록을 현재 사이클로 업데이트하게 만들 수 있습니다. 그러나 동시에, 프로토콜은 소각된 XEN 금액을 _msgSender()에 해당하는 주소에 기록합니다.
이후 공격자는 claimFees()를 호출하며, 이는 updateStats()를 실행합니다. _msgSender() 주소의 사이클 기록이 한 번도 업데이트되지 않았기 때문에(소각 사이클과 lastFeeUpdateCycle 모두 0으로 유지됨), updateStats()는 현재 사이클의 보상을 계산하고 사이클 0부터 누적된 수수료를 계산합니다 - 프로토콜의 전체 수수료 이력에 걸쳐. 공격자는 claimFees()와 claimRewards()를 호출하여 수익을 얻습니다.

공격 분석
다음 분석은 트랜잭션 0x914a5a...b808bc37를 기반으로 합니다.
-
1단계: 공격자는 먼저
Forwarder컨트랙트의registerDomainSeparator()함수를 호출하여 이후Forwarder.execute()호출을 가능하게 합니다. -
2단계: 공격자는 Uniswap V2 풀에서
0.14e18ETH를13,900,000,000e18XEN으로 스왑합니다. -
3단계: 공격자는
13,900,000,000e18XEN을Forwarder컨트랙트에 전송합니다. -
4단계: 공격자는
Forwarder.execute()를 사용하여 DBXen이Forwarder가 보유한13,900,000,000e18XEN을 사용하도록 승인합니다. -
5단계: 공격자는
Forwarder.execute()를 사용하여DBXen.burnBatch()를 호출하고13,900,000,000e18XEN을 소각합니다. 소각 금액은 주소0x425D3eC2DCeBE2c04bA1687504D43AFC6be7328d에 기록되고, 소각 실행 중XEN은onTokenBurned()를 통해DBXen으로 콜백하여Forwarder의 관련 사이클 기록을 업데이트합니다.
-
6단계: 공격자는
Forwarder.execute()를 사용하여DBXen.claimFees()를 호출하고65.36e18ETH를 획득합니다. -
7단계: 공격자는
Forwarder.execute()를 사용하여DBXen.claimRewards()를 호출하고2,305.4e18DXN을 발행합니다.
결론
이 사건의 근본 원인은 DBXen 프로토콜이 _msgSender()와 msg.sender를 일관성 없이 사용했다는 것입니다. 이 두 값이 다를 수 있기 때문에, 프로토콜의 내부 회계가 일관성을 잃어 공격자가 그 불일치를 악용하여 수익을 얻을 수 있었습니다.
향후 유사한 위험을 줄이기 위해:
- 모든 로직 경로에서
_msgSender()를 일관되게 사용하거나,msg.sender의존 작업과_msgSender()의존 회계가 항상 동일한 주소를 참조하도록 보장하십시오.
Goose Finance 사건
간략한 요약
2026년 3월 15일, BNB Chain의 이자 농사 프로토콜 Goose Finance가 약 $8K의 익스플로잇을 당했습니다. 근본 원인은 StrategyGooseEgg의 지분 가격 책정 순서 결함입니다: deposit()가 수확된 보상을 회계에 정산하기 전에 지분을 발행하여, 지분 가격 책정에 사용되는 총 자산 분모가 이러한 보상을 제외하여 실제 가치보다 낮아집니다. 이는 예치자가 받아야 할 것보다 더 많은 지분을 받는다는 것을 의미합니다. withdraw()가 호출되면 총 자산을 증가시키는 보상 수확을 트리거하여 각 지분의 가치가 더 높아집니다. 단일 트랜잭션 내에서 예치와 인출을 반복함으로써, 공격자는 반복적으로 과다 평가된 지분을 발행하고 수정된(더 높은) 가치로 상환하여 그 차액을 수익으로 추출했습니다.
배경
Goose Finance는 BNB Chain 이자 농사 프로토콜로, 사용자 자금이 금고에서 전략으로 흘러 들어가 MasterChef에 자산을 스테이킹하여 EGG 보상을 획득합니다.
이 사건과 관련된 구성 요소는 다음과 같습니다:
-
VaultChef(0x3f64...): 사용자 포지션을 추적하고StrategyGooseEgg에 자본을 전달합니다. -
StrategyGooseEgg(0x0980...):sharesTotal과wantLockedTotal로 전략 수준 회계를 유지합니다. -
MasterChef(0xe70e...): 스테이킹된 자산을 받고EGG보상을 지급합니다. -
WrappedEgg(0xb815...):EGG를 1:1로WEGG로 래핑하여 스테이킹합니다.
운영적으로, 예치는 VaultChef에서 StrategyGooseEgg로 라우팅된 후 MasterChef에 스테이킹됩니다. 인출은 VaultChef에서 시작되어 전략에 의해 실행됩니다.
핵심 회계 기대치는 지분 가격이 가격 책정 시점의 총 전략 자산(스테이킹된 원금 및 전략이 이미 보유한 유휴 보상 포함)을 반영해야 한다는 것입니다. 그러나 StrategyGooseEgg에서, deposit()는 _farm()이 유휴 자산을 wantLockedTotal에 정산하기 전에 지분을 발행하는 반면, withdraw()는 MasterChef에서 보상 수확을 트리거할 수 있습니다. 이 순서가 아래에서 분석하는 취약점의 기반입니다.


취약점 분석
근본 원인은 보상 수확과 지분 가격 책정 사이의 StrategyGooseEgg(0x0980...b26b)의 회계 비동기화입니다.
StrategyGooseEgg에서 지분 가격 책정은 wantLockedTotal을 분모로 사용합니다: shares = deposit * sharesTotal / wantLockedTotal. 이것이 공정하려면, wantLockedTotal은 전략이 실제로 보유한 모든 자산(컨트랙트에 있는 유휴 EGG 보상 포함)을 반영해야 합니다. 그러나 deposit()는 _farm()이 유휴 보상을 wantLockedTotal에 정산하기 전에 지분을 발행합니다. 이는 분모가 미계상 보상을 제외하여 실제 총 자산보다 낮아짐을 의미하며, 예치자가 받아야 할 것보다 더 많은 지분을 받게 됩니다.
또한, withdraw()는 MasterChef.withdraw()를 호출하여 스테이킹된 원금과 미지급 EGG 보상을 전략에 반환합니다. 전략의 회계는 요청된 _wantAmt만 wantLockedTotal에서 차감하므로, 수확된 보상은 wantLockedTotal에 반영되지 않고 전략의 잔액에 남아 있습니다. 이는 실제 보유 자산과 기록된 wantLockedTotal 사이의 격차를 넓혀, 이후 deposit() 지분 가격 책정을 더욱 부정확하게 만듭니다.
공격 분석
다음 분석은 트랜잭션 0x86efdf...ce316223를 기반으로 합니다.
-
1단계: 공격자는 두 Pancake 페어에서
EGG를 플래시 차입합니다. -
2단계:
VaultChef/StrategyGooseEgg에 첫 번째 예치 (10,170,000e18EGG). -
3단계: 첫 번째 인출 (
12,593,884e18EGG)이MasterChef에서 보상을 수확합니다;359,561e18EGG가StrategyGooseEgg에 전송되어 유휴/미계상 가치로 남습니다 (R > 0). -
4단계: 두 번째 예치는 인출된 자본을 재사용합니다 (
12,593,884e18EGG). 유휴 가치가 정산되기 전에 지분이 가격 책정되므로, 이것이 과다 발행 단계입니다. -
5단계: 두 번째 인출 (
12,826,027e18EGG)은 과다 발행된 지분에서 수익을 실현합니다 (즉, 4단계 예치 입력보다232,143EGG초과). -
6단계: 공격자는 플래시 스왑을 상환하고 순 스프레드를 보유합니다.
결론
이 익스플로잇은 StrategyGooseEgg의 지분 가격 책정 순서 결함에서 비롯됩니다: deposit()는 _farm()이 wantLockedTotal을 업데이트하기 전에 지분을 발행하는 반면, withdraw()는 MasterChef에서 보상을 수확하여 일시적으로 유휴 및 미계상 상태로 남을 수 있습니다. 이를 통해 예치는 오래된 분모로 발행되고 나중에 업데이트된 자산으로 인출됩니다.
향후 유사한 위험을 줄이기 위해:
-
지분 발행 및 지분 소각 계산 모두 이전에 보상을 정산하고 회계를 업데이트하십시오.
-
정확한 계산 시점에 단일
totalAssets(staked + idle)를 기준으로 지분을 가격 책정하십시오. -
비零 유휴 보상 조건 하에서
shares_minted <= D * S / (A + R)에 대한 불변 테스트를 추가하십시오.
BlockSec 소개
BlockSec은 풀스택 블록체인 보안 및 암호화폐 컴플라이언스 제공업체입니다. 저희는 고객이 코드 감사(스마트 컨트랙트, 블록체인 및 지갑 포함), 실시간 공격 차단, 사건 분석, 불법 자금 추적, AML/CFT 의무 이행을 수행할 수 있도록 돕는 제품과 서비스를 구축하며, 프로토콜과 플랫폼의 전체 생명주기에 걸쳐 지원합니다.
BlockSec은 권위 있는 컨퍼런스에서 다수의 블록체인 보안 논문을 발표했으며, DeFi 애플리케이션의 여러 제로데이 공격을 보고하고, 다수의 해킹을 차단하여 2천만 달러 이상을 구출하고, 수십억 달러의 암호화폐를 보호했습니다.
-
공식 웹사이트: https://blocksec.com/
-
공식 트위터 계정: https://twitter.com/BlockSecTeam
-
🔗 BlockSec 감사 서비스 : 요청 제출



