Back to Blog

약 2,300만 달러 손실: Cosmos EVM, Moonwell 익스플로잇 | BlockSec Weekly

Code Auditing
2026년 9월 3일
21 min read
Key Insights
  • 이번 주 다섯 건의 사건으로 이더리움, 솔라나, 베이스, 크로노스, 그리고 TAC를 비롯한 추가 6개 코스모스 EVM 체인 전반에서 약 2,270만 달러의 손실이 발생했습니다.

  • 공유 코드로 인해 두 건의 사건이 다중 프로토콜 이벤트로 확대되었습니다. 코스모스 EVM 잔액 처리 결함은 같은 주에 TAC Chain을 포함한 6개 코스모스 EVM 체인을 악용했으며, Rain의 오래된 카드 컨트랙트는 Avici, Tria 및 기타 카드 프로그램을 노출시켰습니다. 공통 인프라의 결함 하나가 이를 실행하는 모든 취약한 다운스트림으로 확산될 수 있습니다.

  • 가격 조작이 가장 흔한 공격 유형으로, 다섯 건 중 두 건과 이번 주 손실의 상당 부분을 차지했습니다. Moonwell과 Tectonic 익스플로잇은 Compound 방식 마켓에서 동일한 형태를 취했습니다. 각각 오라클 가격과 마켓의 수령 토큰 교환 비율을 동시에 부풀린 다음, 과대평가된 담보를 기반으로 대출을 실행했습니다.

보고 기간(2026/08/22 - 2026/08/30) 동안 총 약 2,270만 달러의 손실이 발생한 5건의 보안 사고를 관찰했습니다.

날짜 사고 유형 추정 손실
2026/08/20* Cosmos EVM 익스플로잇 시리즈 (MANTRA, TAC, KiiChain, +3) 산술 언더플로우/오버플로우 ~$5.7M*
2026/08/27 Moonwell 가격 조작 ~$9.1M
2026/08/28 Ajna 부적절한 비즈니스 로직 ~$775K
2026/08/28 Rain Card Contract 익스플로잇 시리즈 (Avici, Tria 및 기타) 서명 검증 우회 ~$1.1M†
2026/08/30 Tectonic 가격 조작 ~$6M‡

*Cosmos EVM 시리즈는 이번 주 보고 기간 이전인 08/20(MANTRA)에 시작되어 지난주 보고서에 포함되지 않았습니다. 전체적인 완전성을 위해 여기에 포함합니다. ~$5.7M은 공식 Cosmos 사후 분석에 따르면 8월 19일 가격 기준으로 공격자가 6개 영향 체인 전반에서 실현한 금액입니다(~$2.87M은 DEX, ~$2.85M은 중앙화 거래소를 통해, 이후 동결됨). 명목상 유출액은 토큰 수량이 알려진 경우 더 컸지만(TAC 약 30억 TAC, ~$7.5M, MANTRA 7억 2090만 토큰, ~$3.6M, KiiChain 1억 4830만 KII), 대부분의 토큰은 판매되지 않았거나 동결되었거나 온체인에서 회수 가능한 상태로 남았습니다.

†~$1.1M은 공유 계약으로 인해 노출된 Rain 지원 프로그램 전반에 걸친 추정 총액입니다. Avici와 Tria가 가장 큰 두 곳으로 각각 ~$500,859(1,685명의 사용자) 및 ~$431,945(636명의 사용자)를 공개했습니다.

‡~$6M은 실현된 손실로, 롤백 전에 이더리움으로 브리지된 금액입니다. 총 유출 추정치는 ~$7,400만(공격자 지갑에서 추적)에서 ~$1억 1,950만(총 시장 유출)까지 다양합니다. 대부분은 Cronos에 남아 있었고 검증인들이 체인을 익스플로잇 이전 상태로 롤백했을 때 삭제되었습니다. Tectonic과 Cronos 모두 최종 손실 수치를 확정하지 않았습니다.

Best Security Auditor for Web3

Validate design, code, and business logic before launch

주간 하이라이트: Cosmos EVM 익스플로잇 시리즈 (TAC 체인에서 추적)

이 시리즈는 공유 인프라 공개에 대해 보여주는 점 때문에 선택되었습니다. 버그가 심각한 보안 위협으로 오판되었기 때문에 수정 사항은 조정된 비공개 배포가 아닌 조용한 공개 패치로 제공되었고, 제3자 포크가 익스플로잇 경로를 공개적으로 설명하면서 해당 모듈을 계속 실행하는 모든 패치되지 않은 체인이 노출되었습니다.

2026/08/20에서 08/25 사이에 공격자들은 6개의 Cosmos EVM 체인에서 공유 cosmos/evm 모듈의 두 가지 취약점을 결합한 단일 익스플로잇 체인을 실행했습니다. 두 버그 모두 EVM 상태를 Cosmos x/bank 원장과 조정하는 코드에 있었습니다: 잔액 언더플로우와 이에 대응하는 오버플로우로, 하나의 공급 중립 트랜잭션 내에서 연결되어 있었습니다.

공식 Cosmos 사후 분석에 따르면, 이 버그는 4월에 버그 바운티 프로그램을 통해 보고되었으며 처음에는 프로덕션 자금을 위협하지 않는 것으로 오판되어 조용한 공개 패치 프로세스를 거쳐 08/19에 v0.6.2 및 v0.7.2로 출시되었습니다. 08/20에 제3자 포크가 익스플로잇 경로를 공개적으로 설명했고, 몇 시간 후 첫 유출이 시작되어 MANTRA(08/20)를 먼저 타격한 다음 TAC와 KiiChain(08/22)을 타격했습니다 [1][2]. 6개 체인 전체에서 공격자들은 8월 19일 가격 기준으로 약 570만 달러를 실현했습니다(DEX에서 약 287만 달러 판매, 중앙화 거래소에서 약 285만 달러, 이후 동결됨) [1].

이 보고서는 작업 예시로 TAC Chain을 자세히 분석합니다. 이 시리즈에서 가장 큰 피해를 입은 체인으로, 스테이킹 풀 손실이 명목 기준 약 750만 달러입니다 [3].

배경

TAC Chain은 Cosmos SDK와 EVM을 모두 실행합니다. tac1... 주소와 0x... 주소는 동일한 기본 20바이트를 공유하므로, 단일 주소가 먼저 Cosmos 베스팅 계정으로 생성된 후 나중에 EVM 계약이 배포될 수 있습니다. 이들은 두 개의 별도 계정이 아닙니다. 동일한 주소가 동시에 베스팅 상태와 계약 코드를 보유합니다. 두 개가 나란히 실행되며, TAC는 스테이킹과 같은 기본 Cosmos 작업을 고정 주소의 사전 컴파일된 계약을 통해 EVM 호출자에게 노출하므로 EVM 계약이 다른 호출처럼 이를 호출할 수 있습니다.

베스팅 계정의 Bank 총 잔액에는 잠긴 토큰이 포함됩니다. 잠긴 부분은 베스팅 전에는 전송할 수 없지만, 사용 가능한 잔액은 총액에서 잠긴 금액을 뺀 나머지입니다. EVM이 계정을 로드할 때 사용 가능한 잔액에서 StateDB 잔액을 초기화하고, 실행 중 계약 전송은 해당 잔액을 기준으로 작동합니다.

트랜잭션 종료 시 StateDB의 잔액 변경 사항은 권위 있는 원장인 x/bank로 다시 정산되며, 거기서 정산된 것만 실제 전송 가능한 TAC입니다. TAC는 v0.7.x 라인을 실행하며, EVM 잔액을 x/bank에 직접 기록하지만 uint256-to-int256 변환을 통과하는 값만 기록하므로 2^256 근처의 잔액은 전혀 정산될 수 없습니다.

잠긴 토큰은 전송할 수 없지만 스테이킹을 위해 위임할 수는 있습니다. 위임은 잠긴 토큰을 포함하는 Bank 총 잔액을 확인한 다음 DelegatedVesting을 통해 베스팅 제한을 유지합니다. total=1인 완전히 잠긴 계정의 경우 해당 단위를 위임하면 Bank 총 잔액이 0으로 줄어들고 DelegatedVesting이 업데이트되는 반면, 사용 가능한 잔액은 전후 모두 올바르게 0으로 유지됩니다.

취약점 분석

버그가 있는 구성 요소는 모든 상태 저장 스테이킹 사전 컴파일 호출에서 실행되는 공유 cosmos/evm 핸들러의 잔액 동기화로, 0x0000...0800의 스테이킹 사전 컴파일을 통해 노출되며 [4]에 구현되어 있습니다. 확인되지 않은 uint256 산술을 통해 EVM 잔액을 Cosmos 원장과 조정하며, 이로 인해 두 가지 상호 보완적인 결함이 노출됩니다: 스테이킹 쓰기-백 경로의 잔액 언더플로우와 일반 추가 경로의 대응 오버플로우입니다. 각각은 단독으로는 위험하지 않습니다. 위험은 공유 산술 경로에서의 결합에서 비롯됩니다.

첫 번째 결함은 잔액 쓰기-백의 언더플로우입니다. 상태 저장 스테이킹 사전 컴파일 호출이 실행되면 기본 Cosmos 작업이 BeforeBalanceChangeAfterBalanceChange 훅 사이에서 실행되며, AfterBalanceChange는 기본 잔액 변경을 EVM StateDB에 다시 반영합니다.

위임은 Bank 총 잔액을 기준으로 검증하므로 완전히 잠긴 계정은 사용 가능한 잔액이 0으로 유지되는 동안 위임 확인을 통과할 수 있습니다. 기본 위임은 위임된 금액을 Bank 총 잔액에서 차감하고 coin_spent 이벤트를 발생시킵니다. 결함은 AfterBalanceChange가 해당 이벤트를 소비하는 방식에 있습니다. 계정의 현재 사용 가능한 잔액을 다시 로드하여 할당하는 대신 coin_spent 금액을 stateDB.SubBalance(spender, amount)로 재생하여 기존 EVM 잔액에서 차감합니다.

SubBalance는 확인되지 않은 uint256 산술로 빼기를 수행하므로 EVM 잔액이 이벤트 금액보다 작은 모든 계정은 언더플로우가 발생합니다. EVM 잔액이 0이고 coin_spent 금액이 1인 계정의 경우 0 - 1MAX_UINT256으로 래핑됩니다.

두 번째 결함은 추가 경로의 대응 오버플로우입니다. 모든 잔액 전송은 동일한 StateDB를 통해 송신자에 대해 SubBalance를, 수신자에 대해 AddBalance를 실행하므로 양방향이 하나의 산술 경로를 공유합니다.

둘 다 동일한 stateObject 기본 요소로 해석되며, 여기서 AddBalanceSubBalance는 범위 검사 없이 new(uint256.Int).Add(s.Balance(), amount).Sub(s.Balance(), amount)를 계산합니다. 빼기가 0 아래로 언더플로우하는 것처럼, 충분히 큰 더하기는 MAX_UINT256을 초과하여 오버플로우하고 다시 래핑됩니다.

두 결함은 상호 보완적입니다. 단독으로는 언더플로우가 MAX_UINT256 잔액을 생성하지만 이는 비활성 상태입니다. 2^256 근처의 값은 x/bank로의 정산에 필요한 uint256-to-int256 변환을 통과할 수 없기 때문입니다. 확인되지 않은 추가는 그 대응물입니다. 그러한 과대 잔액을 정산 상한선 아래로 다시 가져올 수 있는 유일한 경로입니다.

공격 분석

다음 분석은 트랜잭션 0xae4e9b...da46fc을 기반으로 합니다.

  • 1단계: 트랜잭션 0x4da591...df1af7에서 공격자는 CREATE2 팩토리를 배포하여 공격 계약 주소 0x5711...c978가 계약 배포 전에 계산될 수 있도록 했습니다.

  • 2단계: 트랜잭션 95F43742...6A885BA에서 공격자는 MsgCreateVestingAccount를 사용하여 미래 계약 주소에 기본 단위 1개(1utac)를 전송하고 잠가서 Bank 총 잔액이 위임 확인을 통과할 수 있게 하면서 사용 가능한 잔액은 0으로 유지했습니다.

  • 3단계: 트랜잭션 0x2400f8...c57c81에서 공격자는 CREATE2를 사용하여 동일한 0x5711...c978 주소에 공격 계약을 배포하여 Cosmos 베스팅 계정이자 EVM 계약이 되도록 했습니다. 이를 통해 외부 계정이 가스를 지불하는 동안 계약 주소는 spendable=0인 위임자로 유지될 수 있었습니다.
  • 4단계: 공격 계약은 스테이킹 사전 컴파일을 통해 잠긴 기본 단위를 위임했습니다. 스테이킹 흐름은 Bank 총 잔액을 기준으로 위임을 수락했고, 잔액 쓰기-백은 계약의 EVM 잔액을 MAX_UINT256으로 래핑했습니다.

  • 5단계: 래핑된 MAX_UINT256 잔액은 그대로 Cosmos 원장에 정산될 수 없었으므로, 공격 계약은 먼저 이를 정산 가능한 값으로 낮췄습니다. 거의 전체 잔액을 체인에서 가장 큰 계정이자 모든 스테이킹된 TAC를 보유한 bonded_tokens_pool로 보내면서, 풀의 잔액이 추가 시 오버플로우하여 0으로 래핑되도록 금액을 선택했습니다. 해당 금액은 공격 계약 자체의 MAX_UINT256에서 차감되었기 때문에 계약은 정확히 풀의 이전 잔액을 보유하게 되었고, 새로운 공급은 생성되지 않았습니다. 풀을 0으로 만들려면 전체 잔액을 가져가야 하며, 이는 이 오버플로우가 산출할 수 있는 최대치이므로 공격자가 처음에 체인에서 가장 큰 계정을 대상으로 한 이유입니다.

  • 6단계: 공격 계약은 그 잔액 2,985,651,403.40 TAC를 공격자의 주소로 전송했습니다.

우리의 트랜잭션 수준 분석은 공식 Cosmos 사후 분석과 일치합니다. 두 가지 연결된 취약점을 설명합니다: 위의 언더플로우가 비정상적인 잔액을 생성하고, 공격 분석 5단계의 오버플로우가 이를 풀의 실제 자금으로 전환합니다 [1]. 해당 공식 보고서 이전에 발표된 초기 KiiChain 사후 분석은 최소 3개의 업스트림 결함이 관련되어 있고 언더플로우만 패치되었다고 주장했습니다 [2]. 독립 언론 보도는 얼마나 많은 업스트림 결함이 남아 있는지에 대한 동일한 의견 차이를 요약했습니다 [5].

결론

Cosmos EVM 익스플로잇 시리즈의 근본 원인은 EVM과 Cosmos 레이어가 동일한 잔액을 회계 처리하는 방식의 불일치와 결합된, 경계 검사가 전혀 없는 산술이었습니다. 두 런타임이 하나의 원장을 공유할 때 개별 계정 수준까지 잔액 의미론에 동의해야 하며 모든 잔액 변경은 오버플로우와 언더플로우를 검사해야 합니다. 결함이 단일 체인의 코드가 아닌 공유 모듈에 있었기 때문에 하나의 결함이 이를 실행하는 모든 체인을 노출시켰고, 이것이 단일 버그를 멀티체인 이벤트로 만든 것입니다. 코드 외에도 이 사건은 심각도 평가의 교훈입니다. 취약점은 처음에 프로덕션 자금을 위협하지 않는 것으로 판단되어 수정 사항이 조용한 공개 패치로 출시되었습니다. 그 판단이 수정되었을 때 패치는 이미 공개되었고, 제3자의 익스플로잇 경로 공개는 잘못된 판단을 6개의 익스플로잇된 체인으로 만들었습니다. 실제 자금을 움직일 수 있는 공유 인프라 버그는 처음부터 비공개적이고 조정된 배포가 필요하며, 누구나 해독할 수 있는 조용한 공개 패치가 아닙니다.

Get Started with Phalcon Explorer

Dive into Transactions to Act Wisely

Try now for free

이번 주 기타 사고


Moonwell

2026/08/27, Base의 Moonwell이 담보 회계 인플레이션과 Core Market에 상장된 저유동성 자산인 MAMO의 오라클 가격 조작을 결합하여 약 910만 달러의 익스플로잇을 당했습니다. MAMO 가격을 끌어올리는 것 외에도 공격자는 MAMO를 발행 지분 없이 mMAMO 마켓 계약으로 직접 전송하여 각 지분의 담보 가치를 높이고 가격 상승 위에 담보 가치를 인플레이션시켰습니다. 이중으로 인플레이션된 담보를 바탕으로 공격자는 cbBTC, WETH, USDC, wstETH에 걸쳐 약 1,103만 달러의 총 차입을 했고, 청산 후 약 913만 달러의 잔여 부채를 남겼습니다 [6][7].

취약점 분석

Moonwell의 Core Market은 Compound v2 코드에서 실행되며, 여기서 하나의 마켓 지분(mMAMO)은 (cash + totalBorrows - totalReserves) / totalSupply의 가치를 갖습니다. 두 가지 약점이 여기서 결합됩니다. 첫째, MAMO 공급 상한선은 공식 발행 경로만 확인하므로 MAMOmMAMO 계약으로 직접 전송하면 지분을 발행하지 않고 마켓의 현금에 추가됩니다. 이는 계산된 교환 비율(exchangeRateStored())을 높이고 상한선을 완전히 우회하면서 모든 기존 지분의 담보 가치를 끌어올립니다. 둘째, MAMO는 얇은 유동성에도 불구하고 50% 담보 계수로 담보로 상장되었으므로 적당한 자본으로 오라클 가격을 움직일 수 있습니다. 담보 가치는 지분 곱하기 교환 비율 곱하기 오라클 가격으로 계산되므로 교환 비율과 가격 모두 조작 가능한 표면이며, 함께 인플레이션시키면 훨씬 더 유동적인 자산에 대한 차입력을 배가시킵니다.

공격 분석

다음 분석은 트랜잭션 0x09687d...395593e을 기반으로 합니다. 작업은 약 194만 7천 달러(799 ETH를 USDC로 변환하여 Base로 브리지)로 시작되었습니다. 차입 자산이 재활용되면서 총 MAMO 매수 규모는 약 750만 달러에 달했습니다.

  • 1단계: 공격자는 MAMO를 공식적으로 공급하여 mMAMO를 발행받은 후, Mint 이벤트를 발생시키지 않는 두 건의 트랜잭션으로 53,393,290 MAMOmMAMO 계약으로 직접 전송하여 마켓의 계산된 교환 비율을 약 3.68x 높이고 방금 발행받은 mMAMO 지분과 다른 모든 보유자의 지분을 재평가했습니다.

  • 2단계: 공격자는 유동성이 얇은 동안 DEX 풀에서 MAMO를 매수하여 MAMO/USD 피드를 약 $0.0106에서 약 $0.43까지 끌어올렸습니다.

  • 3단계: 교환 비율과 가격이 모두 인플레이션된 상태에서 공격자는 cbBTC, WETH, USDC, wstETH에 걸쳐 18건의 차입(약 1,103만 달러 총액)을 완료한 다음 수익을 변환하고 통합하여 약 8.729M USDC를 CCTP를 통해 이더리움으로 브리지하고 약 8.728M DAI로 변환했습니다.

결론

근본 원인은 Moonwell이 저유동성 담보 자산을 동시에 두 개의 독립적으로 조작 가능한 표면에서 평가한 것입니다: 얇은 유동성 덕분에 공격자가 적당한 자본으로 움직일 수 있었던 오라클 가격과, 공급 상한선이 공식 발행 경로만 보호했기 때문에 기초 자산의 직접 전송이 인플레이션시킨 수령 토큰 교환 비율입니다. 담보 가치는 두 가지를 곱하므로 둘 다 인플레이션시키면 훨씬 더 유동적인 자산에 대한 차입력이 배가되었습니다. 대출 마켓은 가격과 담보의 지분 회계 모두를 조작 가능한 것으로 취급해야 합니다: 원치 않는 전송을 교환 비율 계산에서 제외하고, 보수적인 담보 계수를 유동성 인지 가격 책정 및 얇은 자산에 대한 엄격한 공급 및 차입 상한선과 결합하십시오.

Ajna

2026/08/28, Ajna는 청산 경로의 비즈니스 로직 결함을 통해 7개의 이더리움 풀에서 약 77만 5천 달러의 익스플로잇을 당했습니다. Ajna는 외부 오라클을 사용하지 않는 대출 프로토콜로, 버킷 유동성에서 파생된 LUP(최저 활용 가격)을 통해 포지션 가격을 책정합니다. 공격자는 먼저 LUP를 조작하여 심각하게 부실한 포지션을 만든 다음, 프로토콜이 여전히 시장보다 훨씬 높게 유지하는 네덜란드 경매 가격으로 통제된 포지션을 청산하도록 했습니다. 그 결과 버킷 예치금 청구권이 부채 상환을 위해 견적 토큰의 액면가로 소비되는 동안 공격자는 실제 담보를 받았습니다 [8].

배경

Ajna는 누구나 공급 및 차입을 위해 쌍을 이루는 토큰 풀을 만들 수 있는 허가 없는 프로토콜로, Uniswap 풀과 유사합니다. 각 풀은 Uniswap V3의 가격 틱과 유사한 버킷으로 나뉘며, 각 틱은 담보 단위당 고정된 견적 토큰 금액을 나타냅니다. 대출자는 선호하는 대출 대비 가치 비율에 따라 버킷을 선택하고 유동성을 제공하여 그 대가로 LP(해당 버킷의 예치금에 대한 청구권)를 받습니다. 외부 오라클이 없기 때문에 LUP는 버킷 예치금 분포와 총 부채에서 직접 파생됩니다.

포지션의 임계 가격(부채를 담보로 나눈 값)이 LUP보다 높아지면 청산 가능 상태가 됩니다. 누구나 본드를 게시하여 이를 청산 상태로 할 수 있으며, 이는 네덜란드 경매를 시작합니다. 경매 가격은 현물 시장과 연결되지 않습니다. 기준 가격의 32배에서 시작하여 매시간 절반으로 줄어들며, 첫 1시간(치유 기간) 동안은 일정하게 유지된 후 감소하기 시작합니다.

경매된 담보는 두 가지 방식으로 취할 수 있습니다. 호출자는 현재 경매 가격으로 견적 토큰을 지불하고 take()로 담보를 가져가거나, 선택한 버킷의 견적 예치금을 사용하여 경매 담보를 사고 차용자를 상환하는 bucketTake()를 호출할 수 있습니다. bucketTake()에서 담보는 해당 버킷에 적립되므로 버킷의 대출자가 이를 획득합니다. 차익 거래 버킷 테이크의 경우 호출자는 청산을 트리거하는 인센티브로 버킷 가격과 경매 가격 사이의 스프레드에 대한 LP 보상(해당 버킷에 대한 청구권)을 추가로 지급받습니다. 설계는 테이커가 경매 가격이 담보 가치 이하로 떨어진 후에만 개입한다고 가정합니다. 오라클이 없으면 이 하강 경매가 Ajna가 공정한 가격을 발견하는 방법이며, 버킷 대출자는 자신의 버킷 가격보다 낮은 담보를 획득하여 이익을 얻습니다. 경매 자체는 차용인의 부채가 청산되거나 대출이 다시 담보화될 때만 종료됩니다. 별도의 정산 경로는 72시간 유예 기간 후 또는 담보가 더 이상 남지 않으면 더 일찍 경매를 해결하여 잔여 부실 채무를 흡수하고 남은 담보를 차용인에게 반환합니다.

이 메커니즘의 세 가지 구현 세부 사항이 테이크가 생성하는 수치를 결정합니다. 첫째, 경매 가격은 시장에서 파생되지 않습니다. 32 * max(kickMomp, neutralPrice) * 2^(-max(elapsedHours - 1, 0))로 감소하므로 첫 1시간 치유 기간 동안 일정하게 유지되고 그 후에야 하락하기 시작하여 해당 기간 직후에도 기준 가격의 약 32배에 가깝게 유지됩니다.

둘째, 킥은 차용인이 패널티 전 부채에서 이미 담보 부족 상태일 때만 허용됩니다. _kick()는 3개월 이자 패널티를 추가하기 전에 _isCollateralized() 검사를 실행하고 BorrowerOk()로 되돌리므로, 해당 패널티는 포지션이 자격을 얻는 데 도움이 되지 않은 채 기록된 포스트 킥 부채를 높입니다.

셋째, 경매에 대한 첫 번째 테이크는 테이크의 상환액과 담보 금액이 계산되기 전에 차용인의 부채에 7% 패널티를 추가하므로 단일 bucketTake()가 자체적으로 부채를 청산할 필요가 없습니다.

취약점 분석

버그가 있는 계약은 Ajna 풀(0xad24...178e)입니다. 여기에 설명된 청산 로직은 해당 검증된 소스에서 가져온 것입니다. bucketTake() 내부에서 프로토콜은 버킷의 예치금 청구권을 견적 토큰의 액면가로 사용하여 경매 차용인의 부채를 상환하고, 차익 거래 버킷 테이크에서는 버킷 가격과 경매 가격 사이의 스프레드를 기준으로 테이커에게 LP 보상을 지급합니다.

예치금 청구권의 실제 회수 가능 가치나 경매 가격이 경제적으로 합리적인지 여부를 확인하지 않습니다. 단순히 청구권이 나중에 액면가로 상환될 수 있다고 가정합니다. 의존하는 두 가격은 독립적으로 계산되며 서로에 대해 검증되지 않습니다. LUP는 버킷 예치금 분포와 풀의 총 부채를 따르는 반면, 경매 가격은 킥 시 고정된 기준 가격에서 순수하게 경과 시간에 따라 감소합니다. 따라서 청구권의 온체인 액면가는 실제 회수 가능한 가치와 달라질 수 있으며, bucketTake()는 여전히 해당 액면가를 기준으로 부채를 정산합니다.

내부적으로 bucketTake()_takeBucket()을 통해 _rewardBucketTake()로 라우팅되며, 차익 거래 테이크에서 테이커의 LP 보상은 취한 담보에 버킷 가격과 경매 가격 사이의 스프레드를 곱한 값으로 계산됩니다.

공격 분석

다음은 7개의 영향 풀 중 하나인 cbETH 풀의 온체인 분석을 기반으로 하며, 트랜잭션 0x8a8793...016e640x12dfde...14e4f5를 사용합니다.

  • 1단계: 설정 트랜잭션에서 공격자는 시장보다 훨씬 높은 가격인 버킷 인덱스 2000에 약 49.343 WETH의 견적 유동성을 추가했습니다. 인플레이션된 버킷을 기반으로 공격자는 약 0.001 cbETH를 담보로 약 49.319 WETH를 차입하여 거의 무담보 포지션을 열었습니다. 이 포지션은 담보가 거의 없으므로 목표물이 아닙니다. 두 가지 설정 목적을 제공합니다. 첫째, 부실 부채로서 시장이 정상으로 돌아오면 LUP를 끌어내립니다. 둘째, 버킷 2000에 예치된 49.343 WETH의 거의 전부가 다시 차입되어 먼지만 남았기 때문에 버킷 2000에는 액면가 약 49.343 WETH의 예치금 청구권이 남게 되며, 이는 실제로 회수할 수 있는 것보다 훨씬 많습니다. 이 손상된 청구권이 공격자가 4단계에서 액면가로 사용하는 탄약입니다.
  • 2단계: 동일한 설정 트랜잭션에서 공격자는 두 번째 통제 주소 0x02d329...6f5f를 사용하여 정상 가격 수준에서 차용자 포지션을 열고 약 48.128 cbETH의 담보를 제공하고 약 49.319 WETH를 차입했습니다. 이로써 시장 가격이 정상 범위로 돌아왔고, 첫 번째 포지션은 심각하게 부실한 반면 두 번째 포지션은 건강 임계값 바로 주변에 놓이게 되었습니다. 실제 담보를 보유한 이 두 번째 포지션이 공격자가 실제로 유출시키려는 대상입니다. 부실한 첫 번째 포지션은 LUP를 움직인 레버에 불과합니다.

  • 3단계: 공격자는 부실한 첫 번째 포지션이 아닌 두 번째 포지션(0x02d329...6f5f가 연 포지션)을 경매로 킥했습니다. 킥은 포지션이 현재 LUP에 대해 패널티 전 부채에서 이미 담보 부족 상태일 때만 허용되며, 부실한 첫 번째 포지션이 LUP를 충분히 낮춰 두 번째 포지션의 발생 부채가 기준을 충족했습니다. 자격 확인 후에만 프로토콜이 3개월 이자 킥 패널티를 추가하므로 기록된 포스트 킥 부채는 약 49.362 WETH입니다. 킥은 포지션의 네덜란드 경매를 열었습니다.

  • 4단계: 공격자는 1시간 치유 기간이 막 지난 후 경매 가격이 감소하기 시작했지만 여전히 기준 가격의 약 32배에 가까울 때까지 기다렸습니다. 이제 손상된 청구권을 보유한 동일한 버킷인 버킷 2000에서 두 번째 포지션에 대해 bucketTake()를 호출하여 여전히 높고 시장보다 훨씬 높은 가격으로 청산했습니다. 이는 경매에 대한 첫 번째 테이크였기 때문에 프로토콜은 테이크의 상환액과 담보 금액을 계산하기 전에 차용인의 부채에 7% 패널티를 추가했습니다. 담보는 경매 가격으로 책정되므로 버킷 2000의 예치금 약 49.34 WETH를 소비하여 부채의 일부만 상환하고 약 1.51 cbETH의 담보만 제거하여 대부분의 담보를 건드리지 않은 채 호출자는 스프레드에 대한 많은 양의 LP 보상을 수집했습니다.
  • 5단계: 공격자는 removeCollateral()을 통해 해당 LP 보상을 상환하여 이익의 일부를 담보로 가져갔습니다.

  • 6단계: 마무리하기 위해 공격자는 Balancer 플래시 론을 받아 Ajna의 take()를 호출하여 bucketTake()가 남긴 잔여 부채를 이번에는 실제 견적 토큰으로 지불하여 차용인의 부채가 0이 되고 경매가 종료될 때까지 진행했습니다. 마지막 repayDebt() 호출은 이제 부담이 없는 담보 약 46.51 cbETHquoteRepaid=0으로 인출했습니다. 반환된 견적 토큰은 없었습니다. 이득은 풀의 비용으로 발생했습니다. 부족분은 사라지지 않고 이동했습니다. 두 번째 포지션이 청산되면서 첫 번째 포지션의 아직 미지급된 거의 무담보 대출이 다른 대출자들이 부담해야 할 부실 채무로 풀에 남았습니다.

결론

근본 원인은 Ajna의 청산 경로가 경매 차용인의 부채를 버킷의 예치금 청구권에 대해 액면가로 정산하면서 실제 회수 가능 가치나 경매 가격의 경제적 합리성을 확인하지 않는다는 것입니다. 이 누락된 검사로 인해 제조된 손상된 청구권이 액면가로 소비되어 실제 담보를 유출시키고 부족분을 부실 채무로 풀에 남길 수 있었습니다. 청산 경로는 예치금 청구권을 액면가가 아닌 실제 회수 가능 금액으로 평가하고, 테이크를 완료하기 전에 경매 가격이 경제적으로 합리적인지 확인하며, 풀의 기존 부실 채무가 이미 손상시킨 청구권을 소비하지 않도록 보호해야 합니다. 영향받은 풀 계약은 불변하고 관리 일시 중지 기능이 없으므로 유출이 시작되면 이를 중단할 방법이 없었습니다. 사용자는 스스로 나가는 것만 가능했습니다.

Rain Card Contract 익스플로잇 시리즈 (Avici에서 추적)

2026/08/28(UTC), Rain의 공유 Solana 카드 담보 프로그램의 오래된 버전이 Ed25519 서명 검증 우회를 통해 익스플로잇되었습니다. 프로그램이 위조된 관리자 승인을 수락하도록 만듦으로써 공격자는 사용자 담보 계정을 장악하고 보유된 토큰 잔액을 유출했습니다. 결함이 Rain 기반 카드 프로그램 전반에 걸쳐 공유되는 프로그램 코드에 있었기 때문에 단일 버그가 동시에 모두를 노출시켰습니다. 익스플로잇은 총 약 110만 달러를 유출시켰으며 Avici와 Tria가 가장 큰 두 곳으로 각각 약 $500,859(1,685명의 사용자) 및 $431,945(636명의 사용자)를 공개했습니다 [9].

아래 분석은 Avici를 작업 예시로 사용합니다.

배경

Solana에서 서명 검사는 트랜잭션 처리의 일부로 네이티브 Ed25519 사전 컴파일에 의해 수행됩니다. 참조된 서명이 유효하지 않으면 전체 트랜잭션이 실패합니다. 비즈니스 프로그램은 해당 결과를 직접 받지 않습니다. 동일한 트랜잭션의 다른 명령(Instructions sysvar를 통해)을 검사하고 검증이 통과했음을 신뢰합니다. 사용자가 Rain 기반 카드(Avici와 같은)를 충전하면 잔액은 공유 Rain 프로그램이 관리하는 사용자별 담보 계정에 보유되며, 해당 계정의 토큰 권한은 프로그램에서 파생되므로 계정의 관리자는 프로그램의 전송 흐름을 통해 해당 계정의 자산을 이동할 수 있습니다.

담보 계정의 관리자를 변경하려면 두 개의 서명이 필요합니다. 하나는 프로토콜 지정 관리자에게서 와야 합니다. 다른 서명자는 특별한 신원 요구 사항이 없습니다. 이 설계는 오프체인 인가에 의존합니다. 프로토콜 관리자가 관리자 변경 메시지에 서명하면 프로그램은 이를 승인된 것으로 취급합니다.

Ed25519 검증 명령은 확인할 서명 수의 1바이트 카운트와 패딩 바이트로 시작한 다음 서명당 하나의 Ed25519SignatureOffsets 구조체가 이어집니다. 각 구조체는 서명, 공개 키, 메시지의 바이트 오프셋뿐만 아니라 각각을 읽어야 하는 명령 인덱스도 지정합니다 [10]. 이러한 인덱스는 의도된 기능입니다. 하나의 검증이 트랜잭션의 모든 인덱스된 명령 데이터에서 입력을 읽을 수 있게 하여, 예를 들어 다른 명령의 데이터에 대한 서명을 복사하지 않고 검증할 수 있습니다. 네이티브 검증기는 오프셋과 명령 인덱스가 가리키는 것을 읽습니다.

취약점 분석

결함은 담보 프로그램(3zVB...yBzDuc)에 있습니다. 검증기가 실제로 확인한 키인지 확인하지 않고 공개 키를 신뢰합니다. 관리자 승인 서명자를 기록하기 위해 검사하는 Ed25519 검증 명령 내부의 고정 위치에서 공개 키를 읽은 다음, 통과된 검증의 존재 자체를 이 키의 보유자가 관리자 변경 메시지에 서명했다는 증거로 받아들입니다.

검증이 실제로 어디를 보았는지는 절대 확인하지 않습니다. 이러한 명령 인덱스 필드는 호출자 제어 가능하고 런타임이 모든 명령의 데이터를 네이티브 검증기에 전달하므로, 검증 명령은 자체 본문에 하나의 공개 키를 보유하면서 명령 인덱스가 검증기를 다른 명령으로 유도하여 별도로 소싱된 메시지에 대해 다른 공개 키로 서명을 검증할 수 있습니다.

따라서 "관리자 키"에 대한 두 가지 읽기가 존재하며 이를 연결하는 것은 없습니다. 프로그램은 검사하는 명령에 있는 키를 신뢰하는 반면, 검증기는 인덱스가 가리키는 것만 확인했습니다. 관리자의 키는 해당 키로 유효한 서명이 검증된 적이 없더라도 데이터로 트랜잭션에 존재할 수 있습니다. 이러한 분리가 취약점이며, 누락된 검사는 검증기가 실제로 검증한 공개 키와 메시지를 프로그램이 신뢰하는 키와 메시지로 강제하는 바인딩입니다.

공격 분석

다음 분석은 트랜잭션 ZmpBgn...mqWL을 기반으로 합니다.

  • 1단계: 공격자는 두 개의 Ed25519 검증 명령과 SubmitSignatures가 뒤따르는 트랜잭션을 구성했습니다. 명령 0은 공격자 자신의 공개 키(cafa…53db)와 관리자 변경 메시지에 대한 실제 서명을 담아 트랜잭션에 하나의 진정으로 유효한 Ed25519 검증을 제공했습니다.
  • 2단계: 명령 1은 프로토콜 관리자의 공개 키(a2fc…959a)를 공개 키 슬롯에 배치했지만 서명 슬롯은 가짜 0x09 바이트로 채워서 자체 데이터가 실제로 검증되면 이 명령이 실패하도록 했습니다.

  • 3단계: 공격자는 명령 1의 Ed25519 헤더를 01003000000010000000700020000000으로 설정했습니다. 리디렉션을 수행하는 것은 세 개의 명령 인덱스 필드뿐이며, 디코딩하면 모두 0이므로 서명, 공개 키, 메시지가 모두 명령 0에서 읽힙니다(바이트 오프셋은 여전히 해당 명령의 데이터 내를 가리킵니다):

    num_signatures      = 1,    padding = 0
    signature_offset    = 48,   signature_instruction_index   = 0
    public_key_offset   = 16,   public_key_instruction_index  = 0
    message_data_offset = 112,  message_data_size = 32,  message_instruction_index = 0

    따라서 두 번째 검증은 명령 0을 다시 읽고 명령 1의 유효하지 않은 0x09 페이로드 대신 공격자 자신의 유효한 서명을 다시 검증했습니다.

  • 4단계: 공격자는 SubmitSignatures를 호출했습니다. 프로그램은 두 개의 성공적인 Ed25519 검증을 보았지만 두 번째 서명자를 기록할 때 명령 1에 포함된 프로토콜 관리자 공개 키를 읽었으므로 해당 관리자 키를 승인된 두 번째 서명자로 수락했습니다.

  • 5단계: 기록된 거짓 승인을 바탕으로 공격자는 관리자 변경 흐름을 사용하여 피해자 담보 계정의 관리자를 공격자로 설정하여 검증 우회를 해당 계정에 대한 직접 통제로 전환했습니다.

  • 6단계: 호출자를 담보 관리자로 검증한 후 프로그램은 전송 흐름을 통해 사용자의 카드 담보 토큰 계정에서 자산을 이동시켰고, 프로그램 파생 주소(PDA)를 해당 토큰 계정의 권한으로 호출하여 전송을 실행했습니다.

결론

Rain Card Contract 익스플로잇 시리즈의 근본 원인은 담보 프로그램이 네이티브 Ed25519 검증이 실행되었음을 확인했지만, 신뢰하는 공개 키와 메시지가 검증기가 실제로 확인한 것인지는 확인하지 않았다는 것입니다. 네이티브 서명 검증에 의존하는 프로그램은 소비하는 인가 데이터를 해당 검증에 바인딩해야 합니다: 현재 명령에서 입력을 읽는 자체 포함 Ed25519 레이아웃을 요구하거나, 참조된 모든 명령을 확인하고 검증된 공개 키와 메시지를 작업 대상 데이터와 바이트 단위로 비교하십시오. 동일한 결함이 많은 Rain 기반 카드 프로그램에서 패치되지 않은 채 실행되었다는 점이 하나의 버그를 멀티 프로그램 이벤트로 만든 이유입니다.

Tectonic

2026/08/30, Cronos의 Compound 스타일 대출 프로토콜인 Tectonic이 저유동성 거버넌스 토큰 TONIC20% 담보 계수로 담보로 허용되었기 때문에 익스플로잇되었습니다. 공격자는 두 가지 표면에서 동시에 TONIC 표시 담보를 인플레이션시켰습니다: tTONIC 교환 비율의 직접 전송 조작과 DEX 매수를 통한 오라클 가격 상승을 결합했습니다. 이중으로 인플레이션된 담보를 바탕으로 공격자는 여러 대출 마켓에서 차입했습니다. 약 629만 달러(2,592 ETH)가 이더리움으로 브리지되어 실현된 손실로 남아 있습니다. 유출의 대부분은 Cronos에 남아 있었고 검증인들이 체인을 익스플로잇 이전 상태로 롤백했을 때 삭제되었습니다. Tectonic과 Cronos 모두 최종 손실 수치를 확정하지 않았습니다 [11].

취약점 분석

근본 원인은 저유동성 거버넌스 토큰인 TONIC20% 담보 계수로 담보로 상장한 것입니다. 거버넌스 역할에도 불구하고 TONIC은 온체인 유동성이 매우 얇아 제한된 자본으로 평가액을 급격히 움직일 수 있었습니다. Moonwell 익스플로잇에서와 같이 두 가지 표면이 동시에 조작 가능했습니다: TONIC/USD 오라클 가격과 tTONIC 수령 토큰 교환 비율로, TONIC을 마켓에 단순 전송하면 해당 부채를 상쇄하지 않고 교환 비율이 상승합니다 [11]. 그런 다음 0이 아닌 담보 계수는 인플레이션된 평가액을 훨씬 더 유동적인 자산에 대한 차입력으로 전환했습니다.

공격 분석

아래 공격 재구성은 온체인 인텔리전스와 상세한 아카이브 노드 재구성을 기반으로 합니다 [11][12].

  • 1단계: 공격자는 약 500만 USDC를 Tectonic에 담보로 공급했습니다. TONIC/USD 오라클이 여전히 토큰을 약 $1.06e-8으로 평가하는 동안, 한 통제 계정이 약 376.54T TONIC를 차입하여 두 번째 계정으로 이동했습니다.

  • 2단계: 두 번째 계정은 약 41.87T TONIC를 정상적으로 공급하여 그 대가로 tTONIC(마켓의 수령 토큰)을 받았습니다.

  • 3단계: 공격자는 원래 TONIC 부채를 상환하지 않고 나머지 차입 TONIC의 대부분을 tTONIC 마켓 계약으로 직접 전송하여 tTONIC 교환 비율을 높이고 두 번째 계정이 보유한 tTONIC의 담보 가치를 끌어올렸습니다.

  • 4단계: 인플레이션된 tTONIC 담보를 사용하여 공격자는 200,000 USDC와 약 696만 CRO를 차입한 다음 TONIC/USDC, TONIC/WCRO, TONIC/VVS 풀에서 약 16.23T 더 많은 TONIC를 매수하여 다시 마켓으로 보냈습니다. 이는 교환 비율을 더 높이고 DEX 현물 가격을 끌어올렸습니다. Tectonic의 오프체인 TONIC/USD 피드는 이후 빠르게 상승하는 일련의 견적(12:19 UTC 약 $1.06e-8에서 12:49 UTC 약 $2.08e-6까지)을 수락했습니다. 이것이 외부 표면입니다.

  • 5단계: 약 331만 USDC와 2,121만 CRO의 추가 차입으로 또 다른 7.68T TONIC를 매수하여 다시 마켓으로 보내 인출 직전에 인플레이션된 교환 비율과 조작된 가격을 모두 강화했습니다.

  • 6단계: 마지막 트랜잭션에서 공격자는 여러 대출 마켓에서 이중으로 인플레이션된 담보를 활용하여 약 5,524만 USDC, 4,565만 USDT, 98 WBTC, 1,895 WETH, 1,675만 CRO 및 기타 자산을 인출했습니다. 검증인들이 체인을 중단하기 전에 약 629만 달러(~2,592 ETH)가 이더리움으로 브리지되었습니다. 블록 생산은 검증인들이 상태를 익스플로잇 이전 블록으로 롤백한 후 2026/08/30 23:49 UTC(08/31 발표)에 재개되어 Cronos 내 잔액을 삭제했습니다. 브리지된 약 629만 달러만이 실현된 손실로 남아 있습니다.

결론

이번 주 초의 Moonwell 익스플로잇과 마찬가지로 이번 사건은 저유동성 토큰을 담보로 수락한 Compound 스타일 마켓에 대한 가격 조작 공격으로, 오라클 가격과 수령 토큰 교환 비율이라는 두 가지 표면에서 동시에 담보 가치를 인플레이션시켰습니다. 대출 프로토콜은 저유동성 자산을 담보로 상장하지 말고, 지원해야 하는 경우 엄격한 공급 및 차입 상한선을 적용하며, 얇은 현물 시장이 오라클을 움직일 수 없도록 시간 가중 또는 유동성 인지 가격 책정을 사용해야 합니다. 교환 비율 표면에는 자체 보호 장치가 필요합니다: 마켓의 담보 회계는 기초 자산의 원치 않는 전송을 제외해야 하므로, 해당 부채가 남아 있는 동안 직접 전송이 수령 토큰 교환 비율을 인플레이션시킬 수 없습니다. 비정상적인 가격 및 차입 활동에 대한 온체인 모니터링은 가치가 브리지되기 전에 대응 시간을 더욱 단축할 수 있습니다.

Get Started with Phalcon Security

Detect every threat, alert what matters, and block attacks.

Try now for free

참고 자료

BlockSec 소개

BlockSec는 풀스택 블록체인 보안 및 암호화폐 컴플라이언스 제공업체입니다. 프로토콜과 플랫폼의 전체 수명 주기에 걸쳐 코드 감사(스마트 계약, 블록체인 및 지갑 포함), 실시간 공격 차단, 사고 분석, 불법 자금 추적, AML/CFT 의무 충족을 위한 제품과 서비스를 구축하고 제공합니다.

BlockSec는 권위 있는 학회에서 여러 블록체인 보안 논문을 발표했으며, DeFi 애플리케이션의 여러 제로데이 공격을 보고하고, 여러 해킹을 차단하여 2천만 달러 이상을 구출했으며, 수십억 달러 상당의 암호화폐를 보호했습니다.

Sign up for the latest updates
Web3 공격 표면: 침투 테스트 개요

Web3 공격 표면: 침투 테스트 개요

암호화폐 기관은 기존 공격 표면에 자금 처리 체인까지 더해집니다. 이 글은 애플리케이션, 승인 및 서명, 블록체인 상호작용, 인프라의 4요소 모델과 각 구성 요소의 책임, 대표 구현, 상속된 공격 표면을 제시합니다. 이어 운영, 서명 의도, 승인·출금 체인, 자금 로직, 온체인 거래 및 배포된 컨트랙트까지 5개 web3 공격 표면 영역으로 정리합니다.

기관용 블록체인 침투 테스트를 위한 교전 규칙 및 프로덕션 안전 수칙

기관용 블록체인 침투 테스트를 위한 교전 규칙 및 프로덕션 안전 수칙

서명, 출금, 원장 시스템을 다루는 침투 테스트는 실행 전에 준비됩니다. 이 글은 참여 생애주기를 다룹니다: 목표·범위·담당자·승인된 접근 권한 설정; 권한, 허용 기법, 운영 한계, 금지 행위, 커뮤니케이션, 증거 처리를 Rules of Engagement 문서에 기록; 측정 가능한 중단 기준, 모니터링, 변경 조율, 지정된 중단 권한으로 라이브 서비스 보호. 마지막으로 결과를 검증된 통제로 전환하는 개선 및 재테스트로 마무리됩니다.

블록체인 침투 테스트란 무엇인가? 정의와 범위
Security Services

블록체인 침투 테스트란 무엇인가? 정의와 범위

블록체인 침투 테스트에 대한 통일된 정의는 없으며, 감사·스캔·버그 바운티와 혼동되곤 한다. 이 글은 합의된 범위와 규칙 하에 실행 중인 시스템을 공격자 관점에서 검증하는 작업 정의를 제시하고, web3가 더하는 온체인·오프체인 연결 격차를 다룬다. 이어 다섯 가지 테스트 가능 역량을 매핑하고 관련 목표를 코드 감사, 지갑 보안 감사, web3 보안 테스트, 스캔, 버그 바운티로 분류한다.

Best Security Auditor for Web3

Validate design, code, and business logic before launch. Aligned with the highest industry security standards.

BlockSec Audit