Back to Blog

약 940만 달러 손실: Injective, Aquifer 익스플로잇 | BlockSec Weekly

Code Auditing
2026년 9월 11일
23 min read
Key Insights
  • 이번 주 발생한 네 건의 사건으로 Injective, Solana, Ethereum, Flow EVM 전반에서 약 940만 달러의 손실이 발생했습니다.

  • Injective, Aquifer, Notional Finance 익스플로잇은 가격 조작이나 플래시 론이 전혀 필요하지 않았습니다. 각 사건은 단순히 프로토콜 자체의 회계 처리가 실제로는 존재하지 않는 숫자를 만들어내도록 한 것뿐입니다. Injective에서는 식별자가 한 마켓의 것과 충돌한 보험 기금이 조작된 12,744 USDC 부족분을 검증되지 않은 다른 토큰의 잔액(1센트의 몇 분의 1 가치)으로 정산했고, Notional Finance에서는 정확히 -2^128의 부채가 0으로 평가되었습니다.

  • 네 건 중 세 건에서는 프로토콜이 이미 올바른 검사 로직을 작성해 두었지만, 정작 중요한 경로에는 적용되지 않았습니다. Aquifer는 Token Program 허용 목록을 배포했지만 스왑 진입점에서 이를 전혀 호출하지 않았고, Ankr FLOW는 하나의 스테이킹 진입점에는 일시정지(pause) 수정자를 적용했지만 그와 짝을 이루는 진입점은 열어둔 채 두었으며, Notional Finance는 한 함수에서 한 변환에는 검증된 캐스트를 사용했지만 바로 위의 변환은 원시 캐스트로 남겨두었습니다.

지난 한 주(2026/08/31 - 2026/09/06) 동안 4건의 보안 사고가 관찰되었으며, 총 예상 손실액은 약 $9.4M입니다.

Date Incident Type Estimated Loss
2026/08/31 Ankr FLOW Flawed State Validation ~$410K
2026/08/31 Aquifer Flawed Input Validation ~$2.47M
2026/08/31 Injective Missing Denomination Validation ~$4.8M
2026/09/03 Notional Finance Unsafe Cast ~$1.73M

웹3를 위한 최고의 보안 감사자

출시 전에 설계, 코드, 비즈니스 로직을 검증하세요

이번 주의 주요 사건: Injective

이 사고는 공격 체인의 복잡성과 손실 규모로 인해 이번 주의 하이라이트입니다: 단 한 번의 정산이 지급되기 전에 두 개의 별개 결함이 나란히 맞춰져야 했습니다. 두 결함 모두 애플리케이션 컨트랙트가 아닌 체인 자체의 거래소 로직 안에 있었습니다. 이는 구분자 없이 연결된 필드로부터 조합된 식별자가 원래 서로 관련이 없었어야 할 두 개의 객체를 조용히 병합할 수 있다는 것을 보여주며, 정산 경로가 펀드가 지원하는 시장의 통화 단위를 보유하고 있는지 전혀 확인하지 않을 때 그 병합이 얼마나 큰 비용을 초래하는지를 보여줍니다.

2026/08/31에, Injective의 exchange 모듈 내부의 바이너리 옵션 로직이 악용되어 USDC로 약 $4.8M의 손실이 발생했습니다. Injective는 오더북 거래소를 체인 자체에 내장한 레이어-1 체인이므로, 영향을 받은 코드는 누군가가 배포한 컨트랙트가 아니라 노드 소프트웨어의 일부입니다. Injective의 네이티브 토큰인 INJ를 보유한 보험 펀드가 두 식별자가 충돌했기 때문에 USDC로 표시된 바이너리 옵션 시장에 결국 묶이게 되었으며, 보험 펀드에서 자금을 끌어오는 정산 경로는 이 둘을 전혀 비교하지 않았습니다. 공격자는 그러한 시장 내에서 자신의 서브계정들끼리 거래하여 부족분을 인위적으로 만들어냈고, 프로토콜은 이를 1센트의 일부에 불과한 가치의 INJ 잔액으로 충당했습니다. 모든 포지션이 전액 환불되었고 공격자는 예치한 것보다 훨씬 많은 금액을 인출했습니다.

배경

Injective의 exchange 모듈은 예/아니오 결과에 대해 완전히 담보된 베팅인 바이너리 옵션 시장을 목록화합니다. 누구나 상장 수수료를 지불하고, 이를 해결할 오라클과 만료 및 정산 타임스탬프를 선택하여 시장을 상장할 수 있습니다. 오라클은 제공자(provider)와 심볼(symbol)의 조합으로 지정됩니다: 제공자가 되려면 거버넌스 투표가 필요하지만, 심볼은 상장자가 제공하는 임의의 문자열입니다. 트레이더는 먼저 USDC와 같은 견적 토큰을 서브계정에 예치한 다음, 주문을 하여 한쪽 편을 취합니다. BUY는 사건이 발생할 것이라는 베팅이며 P * Q를 마진으로 잠그고; SELL은 발생하지 않을 것이라는 베팅이며 (1 - P) * Q를 잠그는데, 여기서 Q는 계약 수량이고 P[0, 1] 범위의 진입 가격입니다. 따라서 더 가능성이 높은 결과에 베팅하는 쪽이 더 큰 마진을 게시합니다. 체인이 매 블록 종료 시 실행하는 훅인 EndBlocker는 같은 가격에서 BUY와 SELL을 매칭하여 LONG과 SHORT 포지션으로 만듭니다. 두 잠금액이 항상 정확히 Q로 합산되기 때문에, 방금 매칭된 오더북은 구조상 완전히 자금이 조달된 상태입니다.

만료 시점에 오라클은 [0, 1] 범위의 정산 가격 S를 발표하며, 각 포지션은 마켓 풀에서 margin ± (S - entry) * Q를 지급받습니다. 참여자 간 지급은 완전히 제로섬이며, 각 지급은 0에서 하한이 설정되므로, 포지션은 절대 음수가 될 수 없고 청산이 필요하지 않습니다. 포지션은 또한 margin = 0으로 반대 주문을 내어 조기에 마감할 수 있으며, 이는 마진과 실현된 이익을 함께 방출하는데, 이는 새로 진입하는 사람이 잠그는 마진에서 지급됩니다. 각 포지션의 마진은 진입 시 잠근 값 그대로 유지되므로, 조기 마감 후에는 오더북 상의 마진 합계가 더 이상 풀과 일치하지 않을 수 있습니다.

어떤 제공자도 발표하지 않는 심볼을 가진 시장은 가격이 전혀 없이 정산 시점에 도달합니다. 이 모듈은 이 경우를 위한 대체 경로를 갖고 있습니다: 정산은 시장을 해결하는 대신 되돌리는 getBinaryOptionsSocializedLossDataWithRefundFlag()로 구현된 환불 경로로 넘어갑니다. 이 경로가 각 포지션을 자신의 진입 가격으로 마감하기 때문에, 오더북이 포지션들이 주장하는 금액을 여전히 보유하고 있는 한, 이익이나 손실을 기록하지 않고 모든 포지션이 단순히 마진을 환불받습니다. 환불은 시장 잔액에서 자금을 조달하며, 부족분이 있는 경우에는 해당 시장과 연결된 보험 펀드에서 조달합니다.

시장과 보험 펀드는 각각 자체 메시지로 생성되고 각각 통화 단위를 지니는 별개의 객체입니다: 시장은 하나의 토큰으로 표시되고, 펀드는 생성 시 지정된 토큰을 보유합니다. 이들은 신원(identity)에 의해 페어링됩니다: 펀드는 자신의 ID와 동일한 ID를 가진 시장을 지원합니다. 두 ID 모두 신원 필드들의 keccak256 다이제스트입니다. exchange 모듈 자체는 시장별, 펀드별 잔액이 원시 정수 기록으로 관리되는 단일 통합 은행 계정입니다.

취약점 분석

버그가 있는 구성 요소는 injective-coreexchange 모듈 내 바이너리 옵션 처리 로직으로, 커밋 b994d6b6 [1]에서 수정되었습니다. 두 개의 연쇄된 결함으로 인해 한 통화 단위를 보유한 펀드가 다른 통화 단위로 표시된 시장을 지원할 수 있게 되었습니다.

결함 1: 식별자가 구분자 없는 연결(concatenation)에서 파생됩니다. 시장이 출시될 때 그 ID를 계산하는 NewBinaryOptionsMarketID()와, 새 펀드가 지원하려는 시장의 ID를 계산하는 CreateInsuranceFund()(expiry = BinaryOptionsExpiryFlag = -2인 경우)는 모두 동일한 표현식에서 그 ID를 도출합니다:

return crypto.Keccak256Hash([]byte((BINARY_OPTIONS_MARKET_ID_PREFIX +
    oracleType.String() + ticker + quoteDenom + oracleSymbol + oracleProvider)))

필드 구분자도 길이 프리픽스도 없으므로, 필드 간의 경계는 해시된 바이트에 아무런 흔적을 남기지 않습니다. CreateInsuranceFund()는 보험 펀드 자체의 필드들을 이 슬롯들에 매핑하며, oracle_baseoracleSymbol 슬롯을, oracle_quoteoracleProvider 슬롯을 차지합니다. 따라서 펀드 튜플과 시장 튜플은 필드에 걸쳐 바이트를 다르게 분할하면서도 바이트 단위로 동일한 프리이미지를 생성할 수 있으며, 이 경우 두 객체는 하나의 ID를 공유하게 됩니다. 등록 절차는 이 공유된 ID를 두 객체 간의 연결 고리로 받아들이므로, 펀드는 자신이 보유하지 않은 quoteDenom을 가진 시장의 보험 펀드가 될 수 있습니다.

결함 2: 지급액이 시장의 통화 단위와 결코 대조 확인되지 않습니다. PayDeficitFromInsuranceFund()는 펀드가 보유한 통화 단위로 원시 코인을 펀드에서 이동시킨 다음, insuranceFund.DepositDenom을 시장의 견적 통화 단위와 비교하지 않고 동일한 원시 정수를 시장 잔액에 반영합니다. 모듈의 부기(bookkeeping)가 단순 정수이기 때문에, INJ의 원시 단위 1개와 USDC의 원시 단위 1개는 이 경로에서 구별되지 않으며, 이는 같은 정수가 약 10^11 배 차이가 나는 값을 나타냄에도 그렇습니다. 수정 커밋은 유입과 유출 양쪽 모두에 이 경로에 빠져 있던 확인 절차를 추가하여, 펀드가 자신이 보유한 통화 단위로 표시된 시장만 지원할 수 있도록 합니다:

같은 커밋은 또한 Injective 메인넷에서 바이너리 옵션 거래 및 정산을 완전히 비활성화하여, 환불 경로를 공격 표면에서 제거했습니다.

공격 분석

모든 단계는 단일 지갑이 자신의 서브계정 3개(...037c, ...037d, ...037e)를 통해 Q = 15,930으로 실행했습니다.

충돌하는 쌍은 연결된 바이트를 동일하게 유지하면서 필드 경계가 떨어지는 위치를 옮김으로써 구성되었습니다. 펀드의 tickerquoteDenom(Xinj)은 시장의 티커 Xinj를 이루며, 펀드의 oracle_base(컨트랙트 주소와 오라클 심볼이 붙어 있는 것)는 시장의 quoteDenom과 그 뒤를 잇는 oracleSymbol을 이룹니다:

Slot in the concatenation Fund (MsgCreateInsuranceFund) Market (MsgInstantBinaryOptionsMarketLaunch)
prefix -BINARY-OPTIONS-MARKET- -BINARY-OPTIONS-MARKET-
oracleType.String() Provider Provider
ticker X Xinj
quoteDenom inj erc20:0xa00C...235a
oracleSymbol erc20:0xa00C...235aNO_PRICE_FOR_REFUND...297, 펀드의 oracle_base에서 두 부분이 하나의 필드에 함께 채워짐 NO_PRICE_FOR_REFUND...297
oracleProvider Frontrunner, 펀드의 oracle_quote에서 채워짐 Frontrunner

둘 다 0x9793a39f82993cdedb0c82c614102d5aba2451f29709dc9a6f7df191020b4efc로 결정(resolve)됩니다. NO_PRICE_FOR_REFUND라는 이름의 오라클은 절대 가격을 발표하지 않도록 설정되어, 정산이 강제로 환불 경로로 넘어가게 됩니다.

다음 분석은 트랜잭션 0x6ae9cb...51dcf8를 기반으로 합니다.

  • 1단계: 블록 181024772의 단일 원자적 트랜잭션에서, 공격자는 충돌하는 INJ 표시 보험 펀드와 USDC 표시 바이너리 옵션 시장을 생성하고, 펀드에 12,744,000,000 원시 단위 INJ(약 $0.000000063 상당)를 채웠으며, 세 서브계정에 걸쳐 30,267.02 USDC를 예치했습니다. 이 소액(dust) 예치는 원시 정수가 공격자가 계획한 부족분과 일치하도록 정해졌습니다. 각 서브계정은 이후 필요한 마진을 정확히 받았습니다:
Subaccount Deposit (USDC) Margin it funds
037d 1,593.001593 BUY of 15,930 at 0.10, locking 1,593 (Step 2)
037c 14,337.001593 SELL of 15,930 at 0.10, locking 14,337 (Step 2)
037e 14,337.014337 BUY of 15,930 at 0.90, locking 14,337 (Step 3)
Total 30,267.017523 -
  • 2단계: 같은 트랜잭션에서, 037d0.1015,930의 BUY를 냈고 037c0.1015,930의 SELL을 냈습니다. EndBlocker는 이 둘을 매칭하여 1,593의 마진을 지닌 037d의 LONG과 14,337의 마진을 지닌 037c의 SHORT로 만들었습니다. 오더북 상의 총 마진은 1.0Q = 15,930으로, 시장 풀이 보유한 것과 정확히 일치하여, 이 오더북은 완전히 담보된 정상적인 시장과 구별할 수 없습니다.

  • 3단계: 2블록과 1.1초 후, 블록 181024774의 트랜잭션 0x012c17...2af694에서, 037dmargin = 0으로 0.90에 롱 포지션을 마감하여 1,593 + (0.90 - 0.10) * 15,930 = 14,337을 받았습니다. 이 지급액은 새로 진입한 037e가 잠근 마진에서 나온 것으로, 037e0.9015,930의 BUY를 내고 14,337을 잠갔습니다. 0.8Q = 12,744의 실현 이익은 이제 마켓 풀 밖의 037d의 사용 가능한 잔액에 있으며, 남은 두 포지션의 마진은 여전히 오더북에 남아 있습니다: 부채는 1.8Q = 28,674이지만 풀은 여전히 1.0Q만 보유하고 있습니다.

  • 4단계: 정산은 트랜잭션이 아니라 시장 자체의 시계에 의해 진행됩니다. 매 블록 시작 시, 이 모듈은 정산 타임스탬프가 지난 시장을 골라내어 포지션별이 아니라 전체적으로 정산합니다. 이 시장은 두 포지션이 여전히 오더북에 남아 있는 상태로 시장 생성 18초 후에 발동되었습니다: 037c의 SHORT와 037e의 LONG, 각각 14,337의 마진입니다. 오라클이 침묵을 지켰으므로, 환불 경로는 1.0Q = 15,930의 이론적 자산 대비 1.8Q = 28,674의 부채를 계산하여 0.8Q = 12,744의 부족분을 보고했습니다. 이 부족분은 환불 경로의 회계 내부에만 존재합니다. 단일 가격 S 하에서는, 각 쪽의 지급액은 진입 위치와 무관하게 오직 S에만 의존합니다: 숏은 (1 - S) * Q를, 롱은 S * Q를 취하며, 이는 정확히 풀에 있는 Q로 합산됩니다. 환불 경로는 대신 각 쪽의 진입 가격으로 지급하기 때문에, 진입 위치가 서로 상쇄되지 않습니다: 숏은 (1 - 0.10) * Q를, 롱은 0.90 * Q를 지급받아, 각각 14,337입니다. 숏은 가격이 0.10에서 전혀 움직이지 않은 것처럼 마진 전액을 받았는데, 이는 공격자가 3단계에서 이미 인출한 것과 같은 0.8Q입니다.

  • 5단계: PayDeficitFromInsuranceFund()는 충돌하는 펀드에서 12,744,000,000 원시 단위 INJ를 이동시키고 마켓 풀에 12,744 USDC를 반영하여 부족분을 해소했습니다. 부족분이 충당된 것으로 보고되면서, 남은 포지션들에 대한 사회화 손실(socialized-loss) 삭감이 생략되었고 모든 포지션이 전액 마진을 환불받았습니다. 부족분 자체는 누구에게도 비용이 들지 않습니다: 이는 시장의 보험 펀드에서, 그렇지 않으면 삭감을 통해 충당됩니다. 이번 사건을 수익성 있게 만든 것은 시장에 묶인 펀드가 USDC가 아니라 INJ 소액을 보유하고 있었다는 점입니다.

  • 6단계: 블록 181024803의 트랜잭션 0xcb33ad...152eff에서, 공격자는 예치했던 30,267.02 USDC에 대해 43,010,985,663 원시 단위 USDC, 즉 43,010.99 USDC를 인출했습니다. 순수익은 12,743.97 USDC이며, 첫 번째 트랜잭션부터 마지막까지 약 21초가 걸렸습니다.

위의 사이클은 하나의 대표적인 라운드입니다. 공격자는 19시간에 걸쳐 생성된 299개의 단기 바이너리 옵션 시장에서 이를 반복했으며, 각각은 절대 가격을 게시하지 않도록 설정된 오라클과 연결되어 있었고, 만료 및 정산 타임스탬프가 몇 초 차이로 설정되어 있었습니다 [2]. 이 라운드들의 순수익을 합산하면 이 사고에서 손실된 약 $4.8M에 달합니다.

결론

이 사고는 식별자 충돌과 지급 경로에서의 통화 단위 확인 부재가 결합된 사례입니다. 펀드는 자신의 ID와 일치하는 시장을 지원하도록 되어 있습니다. 그러나 신원 필드들을 끝에서 끝까지 이어 붙여 만든 ID는 한 필드가 끝나고 다음 필드가 시작되는 위치를 더 이상 기록하지 않으므로, 서로 다른 두 필드 집합이 동일한 ID를 낳을 수 있으며, 이 페어링은 펀드를 실제로 일치하지 않는 시장에 묶어 버립니다. 보험 펀드에서 시장 부족분을 충당하는 경로가 금액만 비교하고 통화 단위는 결코 비교하지 않기 때문에, 이후 단계에서는 이러한 불일치를 전혀 포착하지 못합니다. 공격자는 이 두 결함을 함께 사용하여 자신이 통제하는 시장 내부에 가상의 부족분을 인위적으로 만들고, 1센트의 일부에 불과한 펀드 잔액으로 이를 정산한 뒤, 풀이 실제로는 보유한 적 없던 마진을 전액 환불받아 빠져나갔습니다.

더 일반적으로, 의미를 담고 있는 식별자는 모든 가변 길이 필드에 명시적인 구분자나 길이 프리픽스를 두어 구조를 보존하는 인코딩에서 파생되어야 하며, 그래야 서로 다른 두 필드 튜플이 동일한 다이제스트로 매핑되지 않습니다.

Phalcon Explorer로 시작하기

트랜잭션을 깊이 파헤쳐 현명하게 대응하세요

지금 무료로 사용해보기

이번 주의 추가 사고

Ankr FLOW

2026/08/31에, Flow EVM 상의 Ankr의 유동성 스테이킹 서비스가 악용되었습니다. Ankr는 네트워크의 네이티브 토큰인 스테이킹된 FLOW에 대해 두 가지 다른 토큰을 발행하며, 각각은 자체 진입점을 통해 발행됩니다. 그 경로 중 하나는 비활성화되어 있었지만, 그 경로로 들어가는 두 번째 방법이 일시 정지를 강제하는 확인 절차를 건너뛰었으며, 그 사이에 그 경로의 전환 비율이 오래되어 있었습니다(stale). 공격자는 동일한 토큰들을 상환받을 수 있는 가격보다 훨씬 싸게 그 경로를 통해 발행한 다음, Ankr의 상환 버퍼, Uniswap V3 풀, 그리고 MORE Markets 대출 프로토콜을 통해 그 간극을 순환시켰습니다. 당시 약 $410K 상당의 15.5M WFLOW(래핑된 FLOW)가 MORE Markets 준비금에서 유출되었으며, 공격자는 슬리피지 후 약 $246K를 실현했습니다 [3].

배경

Ankr FLOW는 Flow EVM 상의 유동성 스테이킹 서비스입니다. FlowStakingPool은 검증인 스테이킹을 위해 FLOW를 Cadence로 전달하고, 결과로 생성된 포지션을 두 가지 토큰으로 나타냅니다: 리베이싱되지 않는 증서 토큰인 ankrFLOW와, ankrFLOW로 뒷받침되는 리베이싱 예치 토큰인 aFLOWEVMb입니다.

이 두 토큰은 별도의 진입점을 가지고 있습니다. 증서 경로는 stakeCerts()unstakeCerts()를 거쳐 _stakeCerts()_unstakeCertsFor()로 이어집니다; 예치 경로는 stakeBonds()unstakeBonds()를 거쳐 _stakeBonds()_unstakeBondsFor()로 이어집니다. 예치 경로에서 발행에는 두 번째 외부 진입점인 stakeBondsWithCode()가 있는데, 이는 Ankr의 추천 프로그램용 파트너 코드를 받아 동일한 내부 _stakeBonds()를 호출합니다. 각 경로는 FLOW와 각 토큰 간의 전환 비율을 게시하는 컨트랙트인 InternetBondRatioFeed에서 각자의 항목을 읽습니다.

FlowStakingPool은 또한 즉시 상환을 위한 FLOW 버퍼를 보유합니다. 풀 외부에서, ankrFLOW는 Uniswap V3의 ankrFLOW/WFLOW 풀에서 거래되었으며, Aave V3 스타일의 대출 프로토콜인 MORE Markets에서 담보로 인정되었습니다. MORE Markets는 자산별로 설정된 담보 대비 대출 비율(loan-to-value, LTV)로 대출을 제한하며, 함께 움직일 것으로 예상되는 자산 그룹인 e-모드(효율성 모드) 카테고리를 제공하는데, 대출자가 해당 카테고리를 활성화하면 더 높은 LTV가 적용됩니다.

취약점 분석

버그가 있는 컨트랙트는 InternetBondRatioFeed (0x3201...de38f)에서 읽어들인 비율에 따라 증서 토큰 ankrFLOW (0x1b97...14bdb)와 예치 토큰 aFLOWEVMb (0xd6fd...f8d4a)를 발행하는 FlowStakingPool (0xfe81...287a)입니다.

두 가지 결함이 겹쳐 있습니다. 첫째, stakeBondsWithCode()stakeBonds()가 강제하는 bondStakingUnpaused 모디파이어 없이 _stakeBonds()에 도달하므로, 예치 토큰 경로는 비활성화된 후에도 여전히 도달 가능한 상태였습니다. 둘째, 2025년 4월 29일부터 2026년 8월 27일까지의 71건의 주간 비율 업데이트 배치 중, 활성화된 ankrFLOW 항목만 갱신되었으며, aFLOWEVMb 항목은 1.0으로 남아 있었습니다.

따라서 두 비율은 동일한 기초 스테이크를 다르게 가격했습니다:

Direction Functions Ratio Conversion
Mint _stakeCerts() / stakeCerts() 0.833437 1 FLOW to 0.833437 ankrFLOW
Mint _stakeBonds() / stakeBondsWithCode() 1.0 1 FLOW to 1 aFLOWEVMb
Redeem _unstakeCertsFor() / unstakeCerts() 0.833437 1 ankrFLOW to ~1.19985 FLOW
Redeem _unstakeBondsFor() / unstakeBonds() 1.0 1 aFLOWEVMb to 1 FLOW

aFLOWEVMbankrFLOW로 뒷받침되기 때문에, 예치 경로를 통한 발행은 예치된 FLOW 1개당 뒷받침되는 ankrFLOW 1개를 생성했지만, 증서 경로는 0.833437을 생성했습니다. 증서 경로를 통한 상환은 여전히 ankrFLOW 1개당 ~1.19985 FLOW를 지급했습니다.

공격 분석

다음 분석은 트랜잭션 0x2b2e6e...3f66c9를 기반으로 합니다.

  • 1단계: 공격자는 두 경로 사이의 한 차례 왕복 거래로 자본을 확보했습니다. Uniswap V3의 ankrFLOW/WFLOW 풀에서 5,000 ankrFLOW를 플래시 대출하고, unstakeCerts()를 통해 이를 상환하여 ~5,999.25 FLOW를 받았으며, stakeBondsWithCode()를 통해 5,000.50 FLOW를 예치하여 동일한 양의 ankrFLOW로 뒷받침되는 5,000.50 aFLOWEVMb를 발행했고, unlockShares()를 호출하여 대출과 그 프리미엄 0.50 ankrFLOW를 상환하기 위해 그 ankrFLOW를 방출했습니다. 약 998.75 FLOW가 운용 자본으로 남았습니다.

  • 2단계: 공격자는 Uniswap V3 스왑을 50회 반복했습니다. 각 사이클은 가격 제한을 두고 ankrFLOWWFLOW로 스왑했으며, 스왑 콜백 내부에서 stakeBondsWithCode()unlockShares()를 통해 FLOW를 라우팅하여 풀에 지급해야 할 ankrFLOW를 발행한 다음, 다음 사이클을 위해 WFLOW 출력을 언랩했습니다. 50개의 사이클에 걸쳐 풀은 ~38,634,755.38 ankrFLOW를 받고 ~46,265,167.78 WFLOW를 지급했으며, 공격자의 잔액은 ~998.75 FLOW에서 ~7,631,411.14 FLOW로 늘어났습니다.

  • 3단계: 공격자는 예치 경로를 통해 ~38,601.95 FLOW를 전환하고 그 결과로 얻은 ankrFLOWunstakeCerts()를 통해 상환하여, FlowStakingPool이 여전히 보유하고 있던 ~46,316.57 FLOW 상환 버퍼를 소진시키고 ~7,714.62 FLOW를 추가로 얻었습니다.

  • 4단계: 공격자는 MORE Markets로 방향을 돌렸습니다. 그들은 ankrFLOWWFLOW를 연관된 FLOW 자산으로 취급하고 ankrFLOW의 LTV를 78.5%에서 97%로 올리는 e-모드 카테고리 1("래핑된 네이티브 토큰")을 활성화했습니다. 그들은 stakeBondsWithCode()를 통해 ~7,639,125.76 FLOW를 예치하고, 그에 상응하는 ankrFLOW를 방출하여 담보로 공급한 뒤 ~5,668,483.10 WFLOW를 대출했습니다. 그 대출을 언랩하여 동일한 경로로 다시 보낸 것(한 차례의 루프 대출)은 동일한 양의 ankrFLOW를 추가하여 담보를 ~13,307,608.86 ankrFLOW로 늘렸고, ~9,819,641.05 WFLOW의 두 번째 대출을 지원하여, ~1.19985 WFLOW의 오라클 비율 기준으로 담보 가치의 약 97%에 해당하는 총 부채 ~15,488,124.15 WFLOW를 만들었습니다.

4단계의 담보는 발행 비용보다 더 많은 가치를 돌려주었습니다: 예치 경로에서 FLOW 1개는 ankrFLOW 1개를 발행했고, 오라클은 그 ankrFLOW~1.19985 WFLOW로 평가했으며, e-모드는 그에 대해 97%까지 대출을 허용했으므로, 공급된 ~13.31M ankrFLOW~15.49M WFLOW의 부채를 지탱했으며, 이는 투입된 FLOW 1개당 약 ~1.16 WFLOW에 해당합니다. 공격자는 두 번째 대출로 인해 WFLOW 준비금이 비게 되었기 때문에 단 한 번만 이를 재순환시켰습니다.

~15.49M WFLOW는 총(gross) 대출액이며, 이것이 MORE Markets 준비금에서 유출된 것으로 보고된 15.5M WFLOW입니다. 그중 약 5.67M WFLOW는 언랩되어 유동성 수익으로 보유되기보다는 추가 담보로 재순환되었으며; 두 번째 대출 ~9,819,641.05 WFLOW가 언랩된 후, 공격자는 최종 인출액으로 ~9,819,641.05 FLOW를 보유했습니다. 자금의 출처별로 귀속시켜 보면, 이 인출액 중 ~7,630,412.39 FLOW는 Uniswap V3 풀에서, ~8,713.37 FLOWFlowStakingPool에서, ~2,180,515.29 FLOW는 MORE Markets에서 나왔습니다.

결론

한 진입점은 보호하지만 그 형제 진입점은 보호하지 않는 상태 확인이 여기서의 근본 원인입니다: 비활성화되었던 발행 경로가 여전히 호출 가능한 상태로 남아 있었고, 그 비율은 16개월에 걸친 주간 업데이트 동안 갱신되지 않았습니다. 그 경로를 통해 라우팅된 모든 FLOW는 따라서 증서 경로가 절대 제공하지 않았을 가격으로 ankrFLOW를 생성했고, 공격자는 이 불일치를 Uniswap V3 풀, 스테이킹 풀의 상환 버퍼, 그리고 대출 시장을 통해 순환시켜, 각 지점에서 싼 ankrFLOWWFLOW 유동성으로 전환했습니다.

일시 정지 보호 장치는 예상되는 호출 지점 하나만이 아니라, 비활성화된 로직에 도달하는 모든 진입점에서 강제되어야 하며, 휴면 경로를 서비스하는 비율 피드는 최신 상태로 유지되거나 되돌리도록(revert) 만들어야 합니다. 대출 프로토콜 또한 발행 가격이 오라클 가격과 독립적으로 설정되는 유동성 스테이킹 토큰에 높은 e-모드 LTV를 부여하는 것을 피해야 합니다; 발행 비용, 상환 가치, 오라클 가격을 함께 모니터링하면 그러한 격차를 드러낼 수 있을 것입니다.


Aquifer

2026/08/31에, Solana 상의 독점적 마켓 메이커 AMM인 Aquifer가 212건의 성공적인 스왑에 걸쳐 약 $2.47M의 손실을 입었으며, 이는 USDC, USDT, HYPE, cbBTC, CASH 및 그 외 13개의 토큰에 분산되어 있었습니다 [4]. 각 스왑은 각 방향으로 하나씩, 두 건의 토큰 전송으로 정산되는데, Aquifer는 호출자가 이를 전혀 확인하지 않고 각 전송을 수행할 프로그램을 선택할 수 있게 했습니다. 공격자는 Aquifer에 지급해야 할 전송에 대해 자신 소유의 프로그램을 지정했고, 이 프로그램은 아무것도 이동시키지 않으면서 성공을 보고했습니다; 반대 방향으로 진행된 전송은 정당한 Token Program을 통해 실행되어 Aquifer의 금고에서 실제 자산을 인출했습니다.

배경

Aquifer는 Prop AMM으로, 이는 전문 마켓 메이커가 자체 재고를 공급하고 매수/매도 호가를 유지한다는 의미이며, Uniswap V2 스타일 풀처럼 일정한 곱(constant product) 곡선을 따라 스왑 가격을 책정하는 것이 아닙니다. Aquifer는 자신의 호가 및 리스크 상태로부터 가격을 도출한 다음, 사용자의 Token 계정과 자신의 금고 사이에서 거래를 정산합니다.

Solana에서 Token Program은 전송, 발행(mint), 소각(burn)과 같은 작업을 구현하는 실행 프로그램입니다. Tokenkeg는 원래의 SPL Token Program이며 Token-2022는 그것의 확장 가능한 후속 버전입니다; 각각은 단일 자산이 아니라 많은 서로 다른 토큰을 관리합니다. Mint 계정은 하나의 토큰 유형을 식별하고 그 공급량, 소수점 자릿수, 권한을 저장하며, Token 계정은 하나의 Mint에 대한 한 보유자의 잔액을 저장합니다; 둘 다 이를 관리하는 Token Program이 소유합니다. Aquifer의 금고는 Aquifer PDA가 제어하는 Token 계정입니다.

트레이더는 Aquifer의 swap 명령어를 호출하고 그것이 다룰 계정들을 전달함으로써 거래하는데, 이는 두 전송 각각에 대해 이를 실행해야 할 Token Program을 포함합니다. Aquifer는 크로스 프로그램 호출(CPI)을 통해 이 토큰들을 이동시킵니다: 전송 명령어를 구성하고 이를 Instruction.program_id가 지정한 프로그램에 전달합니다. 전송 형태의 명령어 데이터는 올바른 Token Program이 이를 실행할 때만 실제 SPL 토큰을 이동시킵니다.

취약점 분석

버그가 있는 프로그램은 Aquifer (AQU1FR...Tz45)입니다. 배포된 바이트코드와 일치하는 공개 소스가 없으므로, 아래 분석은 프로그램의 디스어셈블리로부터 복원된 것입니다: fn_ 이름들은 내부 함수를 코드 오프셋으로 표기하며, swap을 포함해 여기서 사용된 어떤 이름도 개발자로부터 나온 것이 아닙니다. 이 프로그램은 Tokenkeg와 Token-2022만을 허용하는 화이트리스트 함수인 fn_49740()을 포함하지만, swap 경로에서는 이 함수를 호출하지 않습니다. swap을 처리할 때, 프로그램은 하드코딩된 Tokenkeg 상수를 사용하여 fn_45f20()fn_46bf8()를 통해 두 전송 명령어를 모두 구성하는데, 이는 이들의 내부 확인을 필연적으로 통과하며, 그 후 각 명령어를 실행하기 전에 program_id를 호출자가 제공한 Token Program으로 덮어씁니다:

// Semantic reconstruction of the code the program runs for a swap; construct_transfer()
// stands for fn_45f20() and fn_46bf8(), and the allowlist fn_49740() is never reached.
let checked_program = TOKENKEG_ID;

let mut output_instruction = construct_transfer(checked_program, output_accounts);
let mut input_instruction = construct_transfer(checked_program, input_accounts);

// Unchecked caller inputs replace the value that was checked.
output_instruction.program_id = caller.token_program_b.key;
input_instruction.program_id = caller.token_program_a.key;

// Each invoke() hands the transfer instruction to whichever program the caller named.
invoke(output_instruction)?;
invoke(input_instruction)?;

여기서 TOKENKEG_IDTokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA를 나타냅니다. 따라서 검증을 통과하는 값은 실행되는 값이 결코 아닙니다. 이를 더 악화시키는 것은, 이 프로그램이 입력 CPI가 반환된 후 금고가 받은 금액을 확인하지 않는다는 점이며, 이 때문에 아무것도 전송하지 않고 성공하는 CPI가 지급으로 받아들여집니다.

공격 분석

이 사고는 212건의 성공적인 공격 트랜잭션으로 구성됩니다. 다음 분석은 대표적인 한 예시인 트랜잭션 4pBV1G...T3bf를 기반으로 합니다.

  • 1단계: 공격자는 명목 입력 4,957.497101 USDCswap을 호출했고, 이에 대해 Aquifer는 195,849.433667 KMNO의 출력을 계산했습니다. KMNO 쪽 레그(leg)에는 Tokenkeg를 제공했고; USDC 쪽 레그에는 자신의 프로그램 DMBpPM...NRgb68과, USDC Mint, 공격자의 권한(authority), u64::MAX 잔액을 모방하는 165바이트의 가짜 입력 계정 9gsKJc..., 즉 그 프로그램이 소유한 계정을 함께 제공했습니다.

  • 2단계: 출력 CPI는 Tokenkeg에 도달하여 Aquifer의 KMNO 금고에서 서명자 7fTe9p...4gRk7J가 제어하는 공격자의 Token 계정 EUjkGc...195,849.433667 KMNO를 전송했습니다.

  • 3단계: 입력 CPI는 USDC 전송 형태의 데이터와 함께 DMBpPM...NRgb68에 도달했습니다. 그 프로그램은 가짜 소스인 9gsKJc...에서 진짜 Aquifer USDC 금고 7ULN1Y...USDC를 전혀 이동시키지 않고 성공을 반환했습니다.
  • 4단계: Aquifer는 두 CPI 반환값을 모두 받아들여, 스왑이 원자적으로 커밋되었습니다.

이 트랜잭션에 대한 결과적인 잔액 변화는 다음과 같습니다.

Account Before After Change
Aquifer KMNO vault 9BHsZp...FHSqG 604,968.018277 KMNO 409,118.584610 KMNO -195,849.433667 KMNO
Attacker KMNO account EUjkGc... 0 KMNO 195,849.433667 KMNO +195,849.433667 KMNO
Aquifer USDC vault 7ULN1Y... 1,620,342.341679 USDC 1,620,342.341679 USDC 0 USDC

이 숫자들은 이 예시 트랜잭션만을 설명하는 것이며, 사고의 전체 손실을 나타내는 것은 아닙니다.

결론

근본 원인은 가격이나 오라클 조작이 아니라 정산 경로에서의 검증되지 않은 호출자 입력입니다: 스왑 경로는 Token Program 상수를 검증한 다음 명령어를 실행하기 전에 호출자가 제공한 것으로 교체했으므로, Aquifer에 지급해야 할 전송을 실행하는 프로그램은 호출자의 선택이었습니다. 전송 후 금고 잔액 확인이 없었기 때문에, 토큰을 이동시키지 않고 성공을 반환하는 프로그램이 지급 조건을 만족시키는 동안, 반대 방향의 전송은 실제 자산을 전달했습니다.

각 CPI는 호출자가 제공한 계정에서 가져오는 것이 아니라 Mint 계정에서 해석된, 이동되는 Mint를 소유하는 Token Program에 묶여야 하며, 금고의 잔액은 입력 전송 전과 후에 읽혀서 금고가 실제로 견적된 금액을 받지 않는 한 스왑이 되돌려지도록 해야 합니다.


Notional Finance

2026/09/03-09/04(UTC)에, 이더리움 상의 Notional Finance V1이 악용되어 69,257.37 DAI1,658,524.86 USDC로 약 $1.73M이 유출되었습니다. 어떤 계정이 부채를 지도록 허용하기 전에, 프로토콜은 그 계정이 보유하고 빚진 모든 것을 평가하는데, 그 경로에서의 안전하지 않은 숫자 변환이 정확한 크기의 부채를 0으로 붕괴시켰습니다. 따라서 그 확인 절차는 부채가 사라진 것처럼 보이는 계정을 통과시켰고, 그 사이 그 계정이 만들어낸 큰 청구권은 공격자가 통제하는 다른 컨트랙트에 온전히 남아 있었습니다. 공격자는 그 위조된 청구권이 프로토콜의 다음 만기 시점인 자정(UTC)에 만료되도록 시간을 맞췄고, 몇 분 후 이를 정산하여 프로토콜이 여전히 보유하고 있던 DAIUSDC를 인출했습니다.

배경

Notional Finance V1은 이더리움 상의 고정 금리 대출 프로토콜입니다. 이는 사전에 정의된 만기 시점에서의 현금 흐름을 fCash로 나타냅니다: CASH_RECEIVER는 만기 시 자산을 받을 권리가 있는 양의 포지션이고, CASH_PAYER는 이를 지급할 의무가 있는 음의 포지션입니다. 각 계정의 fCash 및 다른 포지션들은 그 계정의 포트폴리오(Portfolio)에 기록되는데, 여기서 자산은 그 캐시 그룹(cash group)과 만기 시점의 조합으로 식별되며; 캐시 그룹은 그것이 정산되는 통화를 고정합니다. 만기 시 정산되는 금액인 단일 자산의 명목가치(notional)는 uint128입니다.

ERC1155Trade.safeTransferFrom()은 두 계정 사이에 fCash 페어를 생성합니다. 이 호출은 ERC-1155 전송처럼 모양을 갖추고 있지만, 실제로는 아무것도 이동하지 않습니다: 이는 Portfolios.mintfCashPair()를 호출하여 두 개의 상쇄되는 포지션, 즉 수신자를 위한 양의 포지션과 이에 상응하는 지급자를 위한 음의 포지션을 생성합니다. 각 쪽은 _upsertAsset()에 의해 해당 포트폴리오에 기록되는데, 이 함수는 캐시 그룹과 만기 시점이 둘 다 일치할 때만 새로운 포지션을 기존 항목에 접어 넣으며, 두 명목가치를 SafeUInt128로 확인된 가산을 통해 더합니다.

지급 능력(solvency)은 freeCollateral()을 통해 지급자에 대해 확인되는데, 이는 계정의 Escrow 현금 잔액을 포트폴리오 평가와 결합하여, 각 항목을 통화별로 서명된 int256으로 합산합니다. 그 후 각 통화 잔액은 정수 나눗셈으로 적용되는 비율과 소수점 스케일링을 사용하여 ETH로 환산되며, 최종 자유 담보(free collateral)는 음수가 아니어야 합니다. 만기 시 fCash 포지션은 Escrow.portfolioSettleCash()를 통해 계정의 현금 잔액으로 정산되며, 이후 양의 현금 잔액은 해당하는 기초 자산으로 Escrow에서 인출될 수 있습니다.

취약점 분석

버그가 있는 컨트랙트는 명목가치 제한 없이 fCash 페어를 발행하는 ERC1155Trade 진입점 (0xbba8...ef08)과, 담보 평가에서 _convertToETH()에 부채를 0으로 평가할 수 있는 두 가지 산술적 결함을 지닌 Escrow (0x9abd...f683)입니다.

첫째, 확인되지 않은 축소 변환(narrowing conversion)이 큰 부채를 0으로 잘라낼(truncate) 수 있습니다. 이 함수는 balance.abs()를 원시 캐스트(raw cast)를 사용해 곧바로 uint128로 변환하며, 그 값이 목표 타입에 맞는지 결코 확인하지 않습니다. 두 타입은 서로 전혀 근접하지 않습니다: 서명된 int2562^255 - 1까지 도달하지만, uint1282^128 - 1에서 멈춥니다. 따라서 정확히 -2^128인 잔액은 int256 내부에 편안히 들어맞고, balance.abs()2^128을 온전히 만들어냅니다. 실패하는 것은 캐스트입니다: 2^128uint128이 담을 수 있는 것을 한 단계 넘어서 랩(wrap)되어 0이 되므로, 뒤이은 평가는 0 잔액에 대해 작동하고 전체 부채가 자유 담보 계산에서 빠져나갑니다. 같은 함수는 계산된 ETH 값의 이후 변환에는 범위를 벗어난 결과에 대해 되돌릴(revert) SafeCast.toUint128()을 사용하지만, 이전 변환은 직접 캐스트로 남아 조용히 잘려나갑니다.

둘째, 정수 나눗셈으로 인해 작은 부채가 0으로 내림될 수 있습니다. er.rateDecimalsbaseDecimals로 나누는 연산이 나머지를 잘라내므로, 충분히 작은 부채 역시 0으로 평가되어 자유 담보에서 제외됩니다.

공격 분석

다음 분석은 트랜잭션 0xe1589a...25d60a를 기반으로 합니다.

  • 1단계: 2026년 9월 3일 23:58:47 UTC에, 공격자는 ERC1155Trade에서 safeTransferFrom()을 호출하여 cashGroupId = 2와 만기 타임스탬프 1788480000(2026년 9월 4일 00:00 UTC)을 사용해 금액 1의 fCash 페어를 발행했는데, 이는 73초 앞선 시점이며 프로토콜이 열어둔 두 만기 중 더 가까운 것이었습니다. 발행 중에, _upsertAsset()은 공격자의 컨트랙트에 음의 fCash 부채를, 수신자 컨트랙트에 양의 청구권을 기록했으며, Portfolios는 즉시 공격자 컨트랙트의 자유 담보를 확인했습니다. 그 컨트랙트는 어떤 통화로도 잔액을 보유하지 않았으므로, 새로운 부채에 대한 양의 평가가 있었다면 그 확인 절차는 실패했을 것입니다. 두 번째 결함이 이를 감춰주었습니다: 현재 환율에서 1의 부채는 두 차례의 정수 나눗셈을 견디지 못하므로, 0으로 평가되어 확인 절차를 통과했습니다.

  • 2단계: 공격자는 다시 safeTransferFrom()을 호출하여 uint128.max(340,282,366,920,938,463,463,374,607,431,768,211,455) 금액의 두 번째 페어를 발행했습니다. 이 페어는 cashGroupId = 2를 재사용했지만 다른 열려 있던 만기인 만기 타임스탬프 1796256000(2026년 12월 3일 00:00 UTC)을 지녔으며, 그 양의 쪽을 다른 수신자 컨트랙트로 보냈습니다. 음의 쪽은 다시 공격자의 컨트랙트에, 1단계에서의 것 옆에 안착했습니다. 단일 발행은 절대 2^128 - 1을 초과할 수 없어, 항상 잘리는 값보다 한 단위 적기 때문에, 그 값에 도달하려면 두 개의 포지션이 필요합니다. 다른 만기는 두 포지션을 별개의 항목으로 유지하여, 병합 시 되돌아갔을 확인된 가산의 범위 밖에 두었습니다; 공유된 캐시 그룹은 여전히 그 둘을 평가 시 동일한 통화 슬롯에 넣었고, 여기서 1단계의 단위가 총액을 정확히 -2^128로 끌어올렸습니다.

  • 3단계: 담보 확인 중에, Escrow 프록시는 convertBalancesToETH()를 호출했습니다. 집계된 음의 잔액은 정확히 -2^128에 도달했고, _convertToETH()에 전달되어 0으로 잘려나갔으며, 그 계정의 ETH 표시 부채는 0으로 보고되었습니다.

  • 4단계: 담보 회계에서 부채가 사라지면서, 두 번째 수신자 컨트랙트는 프로토콜이 사용 가능한 담보로 취급한 큰 양의 fCash 포지션을 보유했습니다. 여전히 같은 트랜잭션 내에서, 이 컨트랙트는 그 담보에 대해 두 개의 추가 fCash 페어를 발행하여 양의 쪽을 두 개의 추가 수신자 컨트랙트에 넘겼습니다: DAI로 정산되는 cashGroupId = 2 아래의 69,257.37과, USDC로 정산되는 cashGroupId = 3 아래의 1,658,524.86입니다. 이 두 수치는 모두 공격자가 트랜잭션 시작 시 Escrow에 대해 수행한 balanceOf 호출에서 나온 것으로, 각 청구권은 Escrow가 실제로 보유한 잔액에 맞춰 정해졌습니다. 각각은 73초 앞선 만기 1788480000을 지녔습니다.

  • 5단계: 2026년 9월 4일 00:01:35 UTC, 그 만기 후 95초 뒤에, 공격자는 트랜잭션 0xc3f3e3...a24efa에서 만기된 두 청구권을 정산하고 Escrow에서 ~69,257.37 DAI~1,658,524.86 USDC를 인출했습니다. 자금은 이후 0x8aaf...3be6로 전달되었습니다.

결론

발행 경로는 명목가치 제한을 두지 않으며, 담보 평가의 두 가지 산술적 결함이 각각 하나의 지급 능력 확인을 통과시켰습니다: 나눗셈은 아무것도 보유하지 않은 계정이 첫 번째 부채를 지도록 허용했고, 이어서 조용한 축소 변환이 정확히 딱 맞는 크기의 부채를 0으로 평가하여, 두 번째 확인이 심각하게 지급 불능 상태인 계정을 통과시켰습니다. 이후 수신자 컨트랙트에 위조된 양의 포지션이 사용 가능한 담보로 취급되었으며, 이는 공격자가 이를 Escrow의 잔액과 일치하는 청구권으로 분할하여 여전히 보유하고 있던 DAIUSDC를 인출할 수 있게 했습니다.

서명된 잔액은 어떤 축소 변환 전에도 범위가 확인되어야 하며, 자신의 입력을 표현할 수 없는 변환은 잘라내는 대신 되돌아가야 합니다. 더 넓게 보면, 지급 능력 확인은 산술 연산이 캐스트에서 잃어버리든 라운딩 단계에서 잃어버리든, 0이 아닌 부채를 결코 0으로 보고해서는 안 됩니다. 이 함수가 이미 더 아래쪽에서 사용하고 있는 확인된 캐스트를 동일하게 적용하거나, 단일 페어 발행이 만들 수 있는 명목가치에 상한을 두는 것 중 어느 하나만으로도 이 공격을 막을 수 있었을 것입니다.

Phalcon Security로 시작하기

모든 위협을 탐지하고, 중요한 것에 대해 알리며, 공격을 차단하세요.

지금 무료로 사용해보기

References

[1] https://github.com/InjectiveFoundation/injective-core/commit/b994d6b603eb53a38312e547f7bbe3c69d40495b

[2] https://mpost.io/injective-exploited-for-4-9m-via-market-id-collision-in-binary-options-settlement-logic/

[3] https://x.com/flow_blockchain/status/2094506622429307061

[4] https://bitquery.io/investigations/aquifer-solana-hack-2-5-million

BlockSec 소개

BlockSec은 풀스택 블록체인 보안 및 암호화폐 컴플라이언스 제공업체입니다. 우리는 프로토콜과 플랫폼의 전체 생애주기에 걸쳐 고객이 코드 감사(스마트 컨트랙트, 블록체인, 지갑 포함)를 수행하고, 실시간으로 공격을 차단하고, 사고를 분석하고, 불법 자금을 추적하고, AML/CFT 의무를 준수할 수 있도록 돕는 제품과 서비스를 구축합니다.

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

Sign up for the latest updates
스마트 컨트랙트를 넘어서: Web3에서의 도메인 및 DNS 운영 보안
Security Insights

스마트 컨트랙트를 넘어서: Web3에서의 도메인 및 DNS 운영 보안

컨트랙트 감사는 컨트랙트에서 끝납니다. DefiLlama TVL 상위 100개 도메인에 SEAL 기반 DNS·등록기관 검사 8가지, 총 800건을 실행했지만 전부 통과한 도메인은 단 하나였습니다. 대부분 프로젝트가 놓친 4가지 통제와 사용자 진입점에서의 중요성을 소개합니다.

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

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

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

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

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

보고 기간(2026/08/22-08/30) 중 5건의 블록체인 보안 사고, 약 2,270만 달러 손실 발생. Tectonic에서 7,400만~1억1,950만 달러 유출, Cronos 롤백으로 대부분 회수. 하이라이트는 TAC Chain에서 추적된 6개 체인 Cosmos EVM 익스플로잇(약 570만 달러)으로 잔액 동기화 버그가 스테이킹 풀을 고갈시킴. Moonwell, Ajna, Rain Card 등 취약점도 분석.

Best Security Auditor for Web3

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

BlockSec Audit