~/blockchain-dev.md
BLOCKCHAIN_DEV

IBC 브리지는 왜 채널을 믿으면 안 될까: Axelar-Secret 사고로 보는 출처 인증

10분 읽기·2026.06.22·출처 4·00
오늘의 질문

크로스체인 메시지가 “IBC로 왔다”는 사실만으로 충분할까, 아니면 앱 컨트랙트가 직접 어떤 채널에서 왔는지 검증해야 할까?

blockchain-dev.md
SURVIVE.exe

IBC 브리지는 왜 채널을 믿으면 안 될까: Axelar-Secret 사고로 보는 출처 인증

1. 왜 지금 봐야 하나

2026년 6월 19일 Secret Network 포럼에 올라온 사고 보고에 따르면, 6월 10일 Axelar와 Secret Network 사이의 IBC 브리지 경로에서 약 467만 달러 상당의 Axelar-bridged Secret 자산이 빠져나갔다. 공격자는 가짜 체인을 만들고, 취약한 ics20-for-axelar 컨트랙트에 IBC 채널을 열고, 정상 Axelar 입금처럼 보이는 패킷을 보냈다. 컨트랙트는 토큰이 allow list에 있는지는 확인했지만, 그 패킷이 진짜 Axelar 채널에서 왔는지는 충분히 인증하지 못했다.

겉으로 보면 “브리지 해킹”이다. 하지만 개발자 관점에서 핵심은 더 구체적이다.

크로스체인 앱에서 메시지의 형식을 검증하는 것과 메시지의 출처를 인증하는 것은 다르다.

IBC는 체인 간 통신을 위한 안전하고 permissionless한 기본 레이어를 제공한다. IBC-Go 문서는 IBC가 transport/authentication/ordering 계층과 application 계층을 분리한다고 설명한다. 이 분리는 강력한 장점이지만, 동시에 애플리케이션 컨트랙트가 “이 패킷을 내 비즈니스 로직에서 받아도 되는가?”를 따로 판단해야 한다는 뜻이다.

이번 사고는 가격 예측이나 특정 생태계 논쟁보다 훨씬 중요한 학습 포인트를 준다. 브리지의 안전성은 “IBC를 썼다”, “Axelar를 썼다”, “allow list가 있다”로 끝나지 않는다. 어떤 채널에서 온 어떤 denom을 어떤 회계 모델로 mint/burn/escrow할지가 상태 전이의 핵심이다.

2. 핵심 개념

IBC는 transport와 app을 나눈다

IBC-Go 문서는 IBC classic에서 transport layer(TAO)가 체인 간 연결, 패킷 인증, ordering 같은 기반을 제공하고, application layer가 패킷을 어떻게 해석하고 처리할지 정의한다고 설명한다. 즉 IBC는 “다른 체인에서 이런 패킷이 왔다”를 전달하는 공통 프로토콜이고, 그 패킷이 내 앱에서 예금인지, 출금인지, 명령인지, 거절해야 할 데이터인지는 앱이 결정한다.

이때 개발자가 놓치기 쉬운 구분이 있다.

  • 패킷 검증: IBC 계층이 해당 패킷과 증명을 프로토콜 규칙에 맞게 처리한다.
  • 출처 인증: 앱이 “이 패킷이 내가 신뢰하기로 한 counterparty, port, channel에서 왔는가?”를 확인한다.
  • 회계 검증: 앱이 “이 mint/release가 실제 escrow, burn, voucher trace와 맞는가?”를 확인한다.

이번 사고에서는 두 번째와 세 번째가 깨졌다.

ICS-20의 denom trace는 회계 장부다

ICS-020 fungible token transfer 스펙은 IBC 위에서 토큰을 옮길 때 denom, amount, sender, receiver, memo 같은 packet data와 escrow/mint/burn/refund 흐름을 정의한다. 특히 denom은 단순 문자열이 아니라 {port}/{channel}/{denom} 형태의 trace 정보를 담아 “이 토큰이 어느 경로를 지나왔는지”를 표현한다.

스펙의 중요한 목표 중 하나는 supply safety다. 어떤 체인이 source zone이면 원본 토큰을 escrow하고 상대 체인에서 voucher를 mint한다. 반대로 sink zone으로 돌아오면 voucher를 burn하고 escrow된 원본을 release한다. 이 회계가 맞으려면 denom trace와 channel 정보가 정확히 맞물려야 한다.

문제는 커스텀 브리지 컨트랙트가 표준 흐름을 바꿀 때 생긴다. Secret 보고서는 ics20-for-axelar가 upstream snip20-ics20의 escrow 기반 모델을 Axelar용 mint 모델로 바꾸면서, 기존 모델에 맞지 않는 parse_voucher_denom(..)reduce_channel_balance(..) 같은 검증이 제거됐고 allow list가 추가됐다고 설명한다. 의도는 이해할 수 있다. Axelar-origin bare denom을 받아 SNIP-20 형태로 mint해야 했기 때문이다. 그러나 그 결과 allow list가 출처 인증을 대체해버렸다.

allow list는 “무엇”을 제한하지 “어디서”를 증명하지 않는다

allow list는 보통 이런 질문에 답한다.

zsh — 생존확인.sh
이 denom 또는 token id를 지원할 것인가?

하지만 브리지에서는 이 질문이 더 필요하다.

zsh — 생존확인.sh
이 denom이 내가 신뢰하는 Axelar channel에서 왔는가?
이 channel에서 이 amount만큼 mint/release해도 escrow 또는 backing이 맞는가?
이 패킷이 다른 permissionless channel에서 온 counterfeit deposit은 아닌가?

공격자는 바로 이 간극을 이용했다. The Block 보도와 Secret 사고 보고의 공통 설명은 다음 흐름이다.

  1. 공격자가 단일 validator 수준의 가짜 Cosmos 체인을 만든다.
  2. 그 체인에서 Secret의 취약 컨트랙트로 IBC 채널을 연다.
  3. allow list에 있는 bare denom을 담은 forged packet을 self-relay한다.
  4. 컨트랙트는 “지원하는 토큰명”이라는 이유로 Secret-side saXXX 토큰을 mint한다.
  5. 공격자는 이 unbacked token을 정상 Axelar 경로로 redeem한다.
  6. Axelar 쪽 escrow에 있던 실제 자산이 빠져나간다.

여기서 가짜 체인이 IBC 자체를 깨뜨린 것은 아니다. 더 정확히는, permissionless IBC 채널이라는 정상 기능을 이용해 앱 컨트랙트의 출처 인증 부재를 찌른 것이다.

3. 최신 이슈와 연결

Secret 보고서는 피해 범위를 Axelar-bridged Secret 자산에 한정했다. Secret core protocol, privacy 기능, native SCRT, 다른 IBC 연결, Axelar core protocol은 영향받지 않았다고 설명한다. 동시에 bridging은 중단됐고 Axelar 연결은 pause됐으며 관련 거래소와 법 집행기관에 trace가 전달됐다고 적었다.

The Block은 이 공격이 6월 10일에 실행됐지만 6월 17일까지 약 7일 동안 눈에 띄지 않았다고 보도했다. Secret 쪽 자산과 잔고가 기본적으로 encrypted인 특성 때문에 일반적인 EVM 풀 고갈처럼 즉시 관측하기 어려웠다는 설명도 붙었다. 이 지점은 개발자에게 두 번째 교훈을 준다.

브리지 보안은 pre-state 검증만이 아니라 post-state 관측과 emergency pause 설계까지 포함한다.

컨트랙트가 mint 전에 source channel을 검증했어야 하는 것은 1차 방어선이다. 하지만 대량 mint, 대량 redeem, escrow reserve 급감, 평소와 다른 channel에서 들어오는 packet 같은 신호를 모니터링하고 circuit breaker를 걸 수 있었다면 피해 시간을 줄일 수 있었을 가능성이 있다.

이 사고가 흥미로운 이유는 “브리지 validator가 부정직했다”가 아니라 “앱 레벨에서 상태 전이의 전제 조건을 덜 검증했다”는 데 있다. 즉 합의·라이트클라이언트·relayer가 정상이어도, 최종 상태 전이를 수행하는 컨트랙트가 잘못된 가정을 하면 안전성은 무너진다.

4. 개발자 관점 해석

1) 크로스체인 메시지의 타입을 명시하라

DepositFromAxelar라는 이벤트나 패킷을 받는다면, 그 타입은 최소한 다음을 포함해야 한다.

  • expected source chain
  • expected port
  • expected channel
  • expected denom trace 또는 canonical asset id
  • replay 방지용 sequence/nonce
  • mint/release 상한 또는 reserve 관계

단순히 denom == uusdc 같은 문자열 비교에 의존하면, 같은 이름을 가진 다른 출처가 들어올 때 구분할 수 없다.

2) permissionless는 “아무나 들어와도 안전해야 한다”는 뜻이다

IBC 채널을 누구나 열 수 있다는 것은 생태계 확장성의 장점이다. 하지만 앱 컨트랙트 입장에서는 공격자도 채널을 열 수 있다는 뜻이다. 그러므로 “허가된 token allow list”와 “허가된 channel allow list”는 별개로 관리해야 한다.

더 나아가 channel allow list만으로도 부족할 수 있다. upgrade, migration, channel 재생성, counterparty 변경, relayer 구성 변경이 일어날 때 기존 allow list가 정말 같은 보안 의미를 갖는지 점검해야 한다.

3) escrow 모델에서 mint 모델로 바꿀 때 invariant를 다시 써라

이번 사례에서 중요한 설계 변경은 escrow 기반 ICS-20 로직을 Axelar integration용 mint 로직으로 바꾼 점이다. 어떤 검증 함수가 기존 모델과 맞지 않아 제거될 수는 있다. 문제는 제거한 검증이 담당하던 invariant를 새 모델에서 어디가 대신 보장하는지 명확히 써야 한다는 것이다.

예를 들어 다음 질문이 문서와 테스트에 있어야 한다.

zsh — 생존확인.sh
기존 parse_voucher_denom이 막아주던 잘못된 denom trace는 새 모델에서 누가 막는가?
기존 reduce_channel_balance가 막아주던 초과 release는 새 모델에서 어떤 reserve check가 막는가?
bare denom을 허용한다면 source channel은 어떤 필드로 인증하는가?

검증 함수를 지웠다면 보안도 같이 지운 것인지, 아니면 다른 경계로 옮긴 것인지 코드 리뷰가 추적할 수 있어야 한다.

4) 사고 대응 권한도 프로토콜 설계의 일부다

브리지는 사용자 자산을 여러 체인과 컨트랙트에 나눠 보관한다. 그래서 pause 권한은 중앙화 리스크이면서 동시에 피해 확산을 막는 안전장치다. 중요한 것은 pause가 있느냐 없느냐보다 다음이다.

  • 어떤 지표가 pause 조건인가?
  • 누가 몇 분 안에 pause할 수 있는가?
  • 특정 channel만 멈출 수 있는가, 전체 브리지를 멈춰야 하는가?
  • pause 이후 사용자 출금, 증명, 보상 절차는 어떻게 되는가?

브리지 운영자는 “우리는 trustless하다”라는 문장보다 “어떤 비정상 상태를 자동으로 감지하고 어떤 범위로 멈출 수 있는가”를 더 구체적으로 공개해야 한다.

5. 내 프로젝트에 적용할 체크포인트

크로스체인, 브리지, 오라클, 메시징 컨트랙트를 다룬다면 아래를 점검하자.

  • inbound message handler에서 source_chain, source_port, source_channel, counterparty를 모두 검증하는가?
  • token allow list와 channel allow list가 분리되어 있는가?
  • bare denom 또는 symbol 문자열만으로 자산을 식별하지 않는가?
  • denom trace, canonical asset id, wrapped asset id의 변환 규칙이 테스트로 고정되어 있는가?
  • mint/release 전후 총공급과 escrow reserve invariant를 계산하는 테스트가 있는가?
  • migration에서 제거된 검증 함수가 담당하던 invariant를 새 로직이 대체하는가?
  • permissionless channel 생성, counterfeit chain, malicious relayer를 threat model에 넣었는가?
  • 대량 mint, 대량 redeem, reserve 급감, 신규 channel 유입에 대한 alert가 있는가?
  • emergency pause가 전체 시스템이 아니라 channel/token 단위로 동작하는가?
  • 사용자가 UI에서 “이 자산이 어떤 경로로 backing되는지” 확인할 수 있는가?

6. 오늘 10분 액션

  1. 내 코드나 의존 프로토콜 문서에서 receive_packet, onRecvPacket, ibc_packet_receive, mint, redeem, bridgeIn, bridgeOut 같은 함수명을 검색한다.
  2. 각 함수 옆에 다음 세 줄을 적는다.
zsh — 생존확인.sh
이 메시지는 어디서 와야 하는가?
그 출처를 코드가 어떤 필드로 검증하는가?
이 상태 전이 후에도 supply/escrow invariant가 유지되는가?
  1. 테스트가 없다면 “가짜 channel에서 allow-listed denom이 들어오는 경우” 하나만 추가해본다. 이 테스트가 실패하지 않고 mint까지 간다면, 오늘의 글이 바로 내 코드의 버그 리포트가 된다.

7. 더 볼 자료

  • Secret Network 사고 보고: 피해 흐름, contract id, code id, 대응 조치가 정리되어 있다.
  • IBC-Go overview: IBC의 transport/application 계층 분리를 이해하는 데 좋다.
  • ICS-020 spec: denom trace, source/sink zone, escrow/mint/burn/refund 모델을 확인할 수 있다.
  • The Block 보도: 감지 지연, Axelar와 Secret 사이의 책임 경계 논쟁, emergency response 맥락을 요약한다.

중복 회피 메모

최근 blockchain_dev 글은 PeerDAS/data availability, EIP-7918 blob fee reserve, EIP-7732 ePBS, EIP-7928 병렬 실행 전제, FOCIL inclusion list, Solana Alpenglow finality, OP Stack interop, Chainlink CCIP TokenPool upgrade, Aztec Connect L1/L2 settlement mismatch, ProxyAdmin 권한 사고를 다뤘다. 이번 글은 Axelar-Secret 사고를 통해 IBC 앱 컨트랙트의 source channel 인증, denom trace, escrow/mint invariant, permissionless channel threat model에 집중해 기존 브리지·정산·권한 글과 관점을 분리했다.

오늘 10분 액션+5%

댓글 0

최신순 ▾
한 줄 남기기

혹시 이 글을 읽는 동료 개발자가 있다면, GitHub으로 로그인하고 한 줄 흔적을 남겨줘요. (스팸 방지용 로그인이에요)