바이낸스 64억 달러 탈취 해부: EIP-7702 위임과 서명 시스템 장악, 세탁 경로, 그리고 서버·클라이언트 방어법

아프로·2026년 11월 1일

바이낸스 탈취 해부 표지

2026년 10월 30일 새벽 5시(한국 시각), 바이낸스의 XRP 지갑 다섯 개가 동시에 잔액을 비웠습니다. 20분 안에 비트코인, 트론, 솔라나 지갑이 뒤따랐고, 이더리움 핫월릿은 "위임 승인" 한 건으로 통째로 넘어갔습니다. 세 시간 동안 11개 체인에서 약 64억 달러, 우리 돈 약 9조 원이 빠져나갔습니다. 암호화폐 역사상 가장 큰 탈취입니다. 그런데 이번에도 블록체인의 암호는 하나도 깨지지 않았습니다. 넘어간 것은 "서명하는 쪽", 곧 바이낸스 자신의 서명 시스템이었습니다.

이 시리즈의 공격을 실제 쇼핑몰 코드에서 직접 재연하고 막아 보는 인프런 강의가 열렸습니다. 실제 해킹 사고로 배우는 Spring Boot·Express 시큐어 코딩 · 이 글과 이어지는 수업: 13-1, 13-2, 13-3, 13-4

이 글의 범위. 공격이 어떻게 이어졌는지를 단계별로 자세히 풀지만, 그대로 따라 할 수 있는 실행 코드(쓸어 가기 컨트랙트, 위임 서명을 만드는 스크립트, 세탁 절차)는 싣지 않습니다. 코드는 모두 방어 쪽 코드이고 값은 가짜입니다. 바이낸스는 아직 최종 원인 보고서를 내지 않았습니다. 사실관계는 바이낸스 발표, FBI 발표, 블록체인 분석 회사 TRM Labs·Elliptic의 분석(2026-11-01 기준)을 따르고, 분석 회사의 판단은 판단이라고 밝힙니다. 수치는 출처마다 조금씩 다릅니다.

한눈에 보기

항목내용
발생2026-10-29 20:05~23:56 UTC (한국 10-30 05:05~08:56). 주요 유출은 약 20분
규모약 63억 6천만 달러(TRM, 10개 체인) · 약 64억 3천만 달러(Elliptic, NEAR 포함 11개 체인) · 약 63억 달러(바이낸스)
가장 큰 덩어리이더리움 약 38억 달러(ETH 20만여 개 + 토큰 185종), 비트코인 18,270 BTC(약 14억 7천만 달러)
공격 주체FBI가 10-31 북한 TraderTraitor(라자루스 하위 조직)로 확인. TRM·Elliptic도 같은 판단
최초 침투바이낸스 1차 발표: DevOps 엔지니어 노트북 침해. 이후 "추가 침해 지점" 발견(세부 미공개)
핵심 기법바이낸스 서명 시스템이 공격자가 고른 거래에 서명(TRM 판단). 이더리움에서는 EIP-7702 위임 승인 50건으로 핫월릿을 쓸어 가기 컨트랙트에 넘김
개인키 유출바이낸스: "개인키 유출 증거는 없다"
동결·회수약 28억 5천만 달러, 44%(Elliptic) · 약 27억 달러(TRM). 대부분 스테이블코인
고객 피해바이낸스가 SAFU 기금과 회사 자금으로 전액 보전하겠다고 발표. 11-01부터 출금 재개

무슨 일이 있었나: 세 시간의 타임라인

한국 시각 기준 바이낸스 탈취 타임라인. 준비, 체인별 출금, 위임, 쓸어 가기, 동결, FBI 확인 순서

흐름을 세 덩어리로 나누면 이해가 쉽습니다.

  1. 준비(전날~당일 오후). 공격자는 하루 전 토네이도 캐시(거래 흔적을 섞는 믹서)에서 0.1 ETH씩 세 번 받아 새 지갑을 만들었습니다. 이 지갑이 수수료를 내고 컨트랙트를 배포하는 "운영 지갑"입니다. 당일에는 쓸어 가기 컨트랙트를 다른 지갑 두 곳에서 시험하고, 세 번째 지갑에서 최종판을 리허설한 뒤, 약 네 시간 뒤 실전에 썼습니다(TRM).
  2. 동시다발 출금(20여 분). XRP 레저, 솔라나, 옵티미즘, 비트코인, 트론 지갑에서 평범한 서명 출금이 잇달아 나갔습니다. 비트코인 지갑은 18,270 BTC를 한 번에 보냈고, 공격자는 같은 블록에서 이를 1,000 BTC·100 BTC 조각으로 나눴습니다.
  3. 이더리움 위임과 쓸어 가기(3시간). 같은 시각 운영 지갑이 EIP-7702 위임 승인 50건을 거래 하나로 제출했고, 3분 24초 뒤 이더리움 핫월릿 잔액이 전부 빠져나갔습니다. 위임이 남아 있어서 그 뒤 들어온 입금도 여섯 번 더 쓸려 나갔습니다. 밤 11시(UTC)께는 아발란체·폴리곤 지갑에서 2차 출금이 있었습니다.

체인별 탈취액 막대 차트. 이더리움 37.9억 달러, 비트코인 14.7억, 트론 6.3억, XRP 3.0억, 솔라나 0.96억 순

체인마다 키 방식과 서명 방식이 다릅니다. 비트코인과 이더리움, XRP와 솔라나는 서로 다른 곡선과 서명 형식을 씁니다. 그런데 30분 안에 모든 체인에서 유효한 서명이 나왔습니다. 이 점이 이번 사건을 이해하는 열쇠입니다.

공격 기법 상세 분석

공격 체인 다섯 단계. 노트북 침해, 서비스 컨테이너 접근, 서명 시스템이 공격자 거래에 서명, 10개 체인 출금과 이더리움 위임, 세탁과 동결 경쟁

1단계. 사람과 기기: DevOps 노트북

바이낸스의 첫 발표는 "DevOps 엔지니어의 노트북이 침해됐을 수 있다"였습니다. 그 접근으로 서비스 컨테이너에 닿아 승인되지 않은 출금을 넣었다는 설명입니다(보안업체 Aikido가 정리한 바이낸스 발표). 이후 바이낸스는 조사에서 추가 침해 지점이 나왔다고 밝혔지만 내용은 공개하지 않았습니다. 공동 CEO는 영상에서 "한 사람이 한 일이 아니다", "자기 파괴 장치가 도구와 흔적을 지웠다"고 말했습니다.

노트북이 어떻게 뚫렸는지는 아직 발표되지 않았습니다. 다만 같은 조직(TraderTraitor)의 이전 사건에서 시작점은 늘 사람이었습니다.

  • 바이비트(2025-02): 지갑 업체 Safe의 개발자 PC가 장악됐고, 서명 화면 스크립트가 바뀌었습니다.
  • DMM 비트코인(2024-05): 지갑 업체 직원에게 가짜 채용 시험을 보내 악성 파이썬 코드를 실행시켰습니다.
  • 로닌 브리지(2022-03): 링크드인의 가짜 채용 제안 PDF로 침투했습니다.

이 패턴은 국내편 북한의 코인 탈취: 바이낸스·바이비트·업비트와 코인 해킹 12년 연대기에서 자세히 다뤘습니다. 운영 서버에 닿는 사람의 노트북은 서버만큼 중요한 자산이라는 점이 이번에도 확인됐습니다.

2단계. 서명 시스템: 키를 훔치지 않고 "서명을 시켰다"

거래소의 핫월릿(바로 출금할 수 있게 온라인에 둔 지갑)은 보통 키를 하드웨어 보안 모듈(HSM)이나 다자간 계산(MPC) 서명 시스템 안에 둡니다. 키는 밖으로 나오지 않고, 출금 서비스가 "이 거래에 서명해 달라"고 요청하면 서명만 돌려줍니다.

TRM은 이렇게 봅니다. 10개 체인에서 서로 다른 키 방식의 유효한 서명이 반 시간 안에 나왔으니, 피싱으로 서명 몇 개를 얻은 것으로는 설명되지 않는다. 가장 그럴듯한 설명은 공격자가 바이낸스 핫월릿 서명 시스템이 자기가 고른 거래에 서명하도록 만들 수 있었다는 것이다. Elliptic도 "공격자가 바이낸스 자신의 시스템이 대신 서명하게 만든 것으로 보인다"고 썼습니다. 바이낸스 역시 개인키 유출 증거는 없다고 했습니다.

이것이 중요한 이유는 이렇습니다. 키를 HSM에 잘 넣어 두는 것만으로는 부족합니다. 서명 시스템이 "요청이 오면 무엇이든 서명하는" 구조라면, 요청하는 쪽을 장악한 공격자에게 키는 이미 넘어간 것과 같습니다. 국내편 쿠팡 사고가 "서명만 맞으면 통과하는 게이트웨이"였다면, 이번에는 "요청만 맞으면 서명하는 서명기"였습니다.

3단계. EIP-7702 위임: 서명 하나로 지갑을 통째로 넘기기

EIP-7702 위임 전후 비교. 일반 계정은 코드가 없고, 위임 승인 하나로 계정에 0xef0100과 대상 주소가 기록돼 대상 컨트랙트 코드가 계정 권한으로 실행된다

이더리움에서는 다른 체인과 방법이 달랐습니다. 여기서 쓰인 것이 EIP-7702입니다.

EIP-7702는 무엇인가. 2025년 5월 이더리움 펙트라 업그레이드로 들어온 기능입니다. 원래 이더리움의 일반 계정(EOA, 개인키로만 움직이는 계정)은 코드를 가질 수 없었습니다. EIP-7702는 일반 계정이 특정 스마트 컨트랙트의 코드를 "빌려 쓰도록" 위임할 수 있게 합니다. 여러 거래를 한 번에 묶거나 다른 사람이 수수료를 대신 내 주는 기능을 지갑에 붙이려는 것이 원래 목적입니다.

어떻게 동작하나.

  • 계정 주인이 위임 승인(authorization)에 키로 서명합니다. 승인에는 체인 아이디(chain_id), 위임할 컨트랙트 주소, 계정의 논스(nonce, 거래 순번)가 들어갑니다.
  • 이 승인을 유형 4 거래(set code 거래)의 승인 목록에 넣어 제출합니다. 제출하는 사람은 계정 주인이 아니어도 됩니다. 서명된 승인만 있으면 누구든 제출할 수 있습니다.
  • 승인이 처리되면 계정에 0xef0100 + 대상 주소라는 표시가 기록됩니다. 그 뒤로 이 계정을 부르면 대상 컨트랙트의 코드가 이 계정의 권한으로 실행됩니다.

공격자에게 매력적인 성질 세 가지.
1. 한 번이면 계속 남습니다. 위임은 다른 위임으로 바꾸거나 해제할 때까지 유지됩니다. 출금마다 서명을 받을 필요가 없습니다.
2. 제출자가 누구든 됩니다. 서명만 얻으면 공격자의 운영 지갑이 제출하면 됩니다.
3. chain_id를 0으로 쓰면 모든 EVM 체인에서 유효합니다. 이번 사건에서 이 성질이 쓰였다는 발표는 없지만, 방어할 때 반드시 막아야 할 성질입니다.

이번 사건에서 위임 승인 50건은 모두 바이낸스 핫월릿 '바이낸스 14'의 키로 유효하게 서명돼 있었고, 공격자의 쓸어 가기(sweeper) 컨트랙트를 가리켰습니다. 이 컨트랙트는 운영 지갑의 지시만 받도록 만들어져 있었습니다(TRM). 바이낸스 공동 CEO는 이 위임이 "평소의 승인 통제를 거치지 않았다"고 말했습니다. 출금 한 건 한 건을 심사하는 통제가 있었더라도, 위임은 출금이 아니라서 그 통제 밖에 있었던 셈입니다.

4단계. 왜 50건이었나: 움직이는 논스 맞히기

연속된 논스 50개에 미리 서명받은 위임 승인 중 실행 순간 논스와 맞는 하나만 성립하는 원리

위임 승인에는 논스가 들어가고, 실행 순간 계정의 논스와 같아야 유효합니다. 문제는 거래소 핫월릿이 쉬지 않고 출금한다는 점입니다. 출금이 나갈 때마다 논스가 하나씩 오르니, 공격자는 위임이 실행될 순간의 논스를 미리 알 수 없습니다.

그래서 공격자는 연속된 논스 50개에 대한 위임 승인을 미리 받아 두고, 50건을 거래 하나에 담아 제출했습니다. 실행 순간 맞는 하나만 성립하고 나머지는 무시됩니다. TRM의 표현으로는 "하나가 맞았고, 공격자에게는 그거면 충분했다"입니다.

여기서 두 가지를 읽을 수 있습니다.

  • TRM은 미리 서명받은 50건이 접근 시간이 제한돼 있었거나, 서명을 시스템을 통해 요청해야 했다는 뜻일 수 있다고 봅니다. 공격자가 키를 직접 가졌다면 실행 순간에 맞는 논스로 하나만 서명하면 됐을 것입니다.
  • 방어하는 쪽에는 분명한 신호입니다. 같은 키로 여러 논스에 대한 위임 서명을 한꺼번에 요청하는 일은 정상 업무에서 생기지 않습니다. 이 요청 자체가 침해 지표입니다.

5단계. 쓸어 가기와 지속: 위임은 남아 있었다

위임이 유지돼 첫 번째 쓸어 가기 뒤 새 입금까지 여섯 번 더 빠져나간 시점 그림

위임이 성립하고 3분 24초 뒤, 쓸어 가기 컨트랙트가 핫월릿에서 ETH 167,464개와 토큰 63종(USDT 16억 1,500만 개 포함)을 한 번에 빼냈습니다. Elliptic에 따르면 이더리움 손실의 3분의 2(약 25억 3천만 달러)가 거래 하나로 나갔습니다.

끝이 아니었습니다. 위임이 계속 남아 있었기 때문에 이 주소로 새로 들어온 입금이 여섯 번 더 쓸려 나갔습니다. 또 이 핫월릿은 바이낸스의 다른 스마트 계정을 실행할 권한을 가지고 있었고, 공격자는 그 권한으로 8억 2,300만 USDC를 더 빼냈습니다. 위임 하나가 그 계정이 가진 모든 권한을 넘긴 것입니다.

교훈은 분명합니다. 위임을 발견했을 때 출금 서비스만 멈추면 안 됩니다. 그 주소로 들어오는 입금 경로를 바로 끊고, 위임을 해제하는 절차가 있어야 합니다. 위임 해제도 같은 키로 새 위임(빈 주소로의 위임)에 서명해야 하므로, 사고 중에 처음 만들 수 있는 절차가 아닙니다. 미리 승인받아 두어야 합니다.

6단계. 나머지 10개 체인: 평범한 서명 출금

이더리움 밖에서는 특별한 기능이 쓰이지 않았습니다. 서명 시스템이 서명한 평범한 출금 거래였습니다. XRP 지갑 다섯 개는 최소 잔액만 남기고 비워졌고, 트론 지갑은 2분 안에 USDT 6억 1,270만 개와 TRX 4,640만 개를 보냈습니다. 이더리움에서도 다른 핫월릿 하나('바이낸스 16')는 위임 없이 자기 키 서명으로 약 1억 7,400만 달러어치를 보냈습니다(Elliptic).

체인마다 키 방식이 다른데도 모두 뚫렸다는 것은 체인별 서명 인프라와 승인 경로가 충분히 나뉘어 있지 않았다는 뜻으로 읽힙니다. TRM의 권고도 같습니다. "한 행위자가 몇 시간 안에 10개 체인 지갑의 유효한 서명을 얻었다. 서명 인프라와 승인 경로를 체인·지갑 등급별로 나눠라."

자금 세탁: 어디로 흘렀고 어디서 막혔나

세탁 4단계와 각 단계에서 방어하는 쪽이 개입한 지점. 스테이블코인 교환과 발행사 동결, 담보 대출과 프로토콜 동결, 분할과 차단 목록, 현금화와 거래소 입금 심사

훔친 뒤의 움직임은 "동결당하기 전에 바꾸고, 추적당하기 전에 흩는다"로 요약됩니다. 아래는 분석 회사가 관찰한 내용이고, 방법을 안내하려는 것이 아니라 어디서 끊을 수 있는지 보려는 것입니다.

1단계(몇 분 안). 동결될 수 있는 자산부터 바꾼다

USDT·USDC 같은 스테이블코인은 발행사가 특정 주소의 토큰을 동결할 수 있습니다. 그래서 공격자는 이것부터 처분했습니다. 탈중앙 거래소(DEX)와 시장조성자 견적(RFQ, 큰 금액을 정해진 가격에 바꿔 주는 방식)으로 USDT 4억 3,400만 개를 ETH 160,451개로 바꿨는데, 시세보다 12~26% 손해를 감수했습니다(Elliptic). TRM도 3억 2,500만 USDT를 ETH로 바꾸면서 10% 넘게 손해를 본 거래를 확인했습니다. 손해를 보더라도 동결보다 빨리 움직이는 것이 이 단계의 목표입니다.

2단계(수 시간). 담보로 맡기고 다른 자산을 빌린다

다른 지갑은 대출 프로토콜 Aave에 USDT 약 5,990만 개를 담보로 맡기고 ETH 19,762개를 빌렸습니다. 담보로 맡긴 USDT가 나중에 동결되더라도 빌린 ETH는 이미 손에 있습니다. Aave는 해당 시장을 동결해 새 활동을 막았지만, 이미 나간 자금을 되찾지는 못했습니다.

3단계(수일). 쪼개고 흩는다

  • 비트코인: 18,270 BTC를 주소 19개(대부분 1,000 BTC씩)로 나눴고, 11월 1일 기준으로는 아직 서비스로 보내지 않았습니다.
  • 트론: 주요 지갑이 546번 송금했는데 간격의 중간값이 96초였고, 금액은 무작위였으며, 밤새 쉬지 않았습니다. Elliptic은 AI 에이전트인지 일반 스크립트인지는 판단하지 못했다고 밝혔습니다. 자동화가 세탁 속도를 끌어올리고 있다는 점은 분명합니다.
  • 체인 이동: 아발란체의 AVAX를 USDC로 팔고 Circle의 공식 전송 프로토콜로 이더리움에 옮긴 뒤 ETH로 바꾼 흐름도 보였습니다.

4단계(수주~수개월, 예상). 현금화

분석 회사들은 바이비트 때처럼 브리지, 스왑 서비스, 장외(OTC) 중개인을 거쳐 거래소에서 현금화할 것으로 봅니다. Chainalysis CEO는 공격자가 이미 수천 개 주소를 썼고, 몇 주 안에 수만 개로 늘 것으로 예상했습니다.

동결 경쟁: 발행사가 있는 자산만 멈출 수 있었다

탈취액 중 44%가 동결·회수되고 56%는 동결하지 못한 비율 막대와, 멈춘 쪽과 못 멈춘 쪽 자산 비교

  • 빨랐던 곳: Tether는 USDT가 도착하고 37분 만에 주요 지갑을 동결했고, Circle은 그 3분 뒤 8억 2,300만 USDC를 동결했습니다. Ripple, World Liberty Financial, Paxos 같은 발행사와 아비트럼 보안위원회도 움직였습니다. Elliptic 집계로 약 28억 5천만 달러(44%)가 묶였고, 그중 94%가 스테이블코인입니다.
  • 멈출 수 없었던 곳: 비트코인, XRP, 솔라나, NEAR 같은 기본 자산은 동결할 발행사가 없습니다. 스테이블코인을 ETH로 바꾼 몫도 마찬가지입니다.
  • FBI(10-31): 이더리움 주소 600개 이상, 비트코인 약 20개, 솔라나 1개를 공개하고, 거래소·DeFi 서비스·노드 운영자에게 이 주소와 거래하지 말라고 요청했습니다.

세탁을 막는 쪽에서 할 일.

  • 거래소·지갑 서비스: 입금 주소를 FBI·분석 회사 차단 목록과 실시간으로 대조합니다. 차단 목록에 직접 있지 않더라도 몇 단계 안에서 들어온 자금은 출처 추적 점수로 보류합니다.
  • 스왑 서비스·브리지·RFQ 시장조성자: 대량 교환 요청을 주소 평판으로 거릅니다. 이번처럼 큰 손해를 감수하는 대량 교환은 그 자체가 신호입니다.
  • 발행사와의 연락망: 동결은 분 단위 경쟁입니다. 사고가 나면 누구에게 어떤 주소를 보낼지 미리 정해 둡니다.
  • 자산 구성: 같은 금액이라도 동결 가능한 자산 비중이 높으면 사고 뒤 건질 수 있는 몫이 커집니다.

방어 1: 서버에서 (서명 서비스·출금·감시)

이번 사건에서 가장 아픈 지점은 서명 시스템이 "요청이 오면 서명"했다는 것입니다. 서버 쪽 방어는 서명하기 전에 묻는 구조를 만드는 데서 시작합니다. 아래 코드는 거래소가 아니어도 결제·포인트·토큰을 다루는 서비스라면 그대로 옮겨 쓸 수 있는 형태로 썼습니다.

서명 서비스 정책 엔진의 일곱 단계. 요청 도착, 거래 종류 확인, 목적지·함수 허용 목록, 금액·속도 한도, 논스 이상 확인, 별도 경로 2인 승인, HSM·MPC 서명

① 서명 정책 엔진: "무엇에 서명하는지"를 서명하는 쪽이 읽는다

가장 중요한 원칙은 임의의 해시나 메시지에 서명하는 API를 두지 않는 것입니다. EIP-7702 위임 승인은 거래가 아니라 따로 서명하는 메시지(앞에 0x05가 붙은 데이터)입니다. 서명 서비스가 "이 해시에 서명해 줘"라는 요청을 받는 구조라면, 위임 승인인지 출금인지 구분할 수 없습니다. 서명 서비스는 완성된 거래를 받아 직접 해석하고, 정책에 맞는 것만 서명해야 합니다.

# 서명 서비스 앞단의 정책 검사 (방어 예시, 값은 모두 가짜)
ALLOWED_TX_TYPES = {0x02}                 # 일반 EIP-1559 송금만. 유형 4(set code)는 기본 거부
ALLOWED_SELECTORS = {bytes.fromhex("a9059cbb")}   # ERC-20 transfer만
ALLOWLIST = {"hot-tier-1": {"0xFAKE0000000000000000000000000000000000A1"}}

def check(req):
    if req.kind != "transaction":           # 해시·메시지 서명 요청은 받지 않는다
        return deny(req, "raw_sign_not_allowed", alert=True)
    tx = decode(req.payload)                # 서명할 바이트를 서명 쪽에서 직접 해석
    if tx.type not in ALLOWED_TX_TYPES:
        return deny(req, f"tx_type_{tx.type}", alert=True)
    if tx.authorization_list:               # 위임 승인이 들어 있으면 무조건 거부
        return deny(req, "eip7702_authorization", alert=True)
    if tx.to not in ALLOWLIST[req.wallet_tier] and not is_customer_withdrawal(tx, req):
        return deny(req, "destination")
    if tx.data and tx.data[:4] not in ALLOWED_SELECTORS:
        return deny(req, "function")
    if over_rate_limit(req.wallet, tx.value):
        return hold(req, "rate_limit")      # 사람 승인으로 넘김
    return require_second_approval(req)     # 다른 경로의 승인을 받은 뒤 서명
  • 위임 기능이 정말 필요하다면 별도 키, 별도 승인 절차로 분리하고 위임 대상 컨트랙트 주소를 허용 목록으로 묶습니다. chain_id 0(모든 체인 유효) 승인은 예외 없이 거부합니다.
  • 이 검사는 출금 서비스가 아니라 서명 서비스 안(또는 HSM·MPC 정책 기능)에서 돌려야 합니다. 출금 서비스가 장악되면 출금 서비스 안의 검사는 함께 우회되기 때문입니다.

② 승인 경로 분리: 요청하는 쪽과 승인하는 쪽을 다른 손에

이번 위임은 "평소 승인 통제를 거치지 않았다"고 했습니다. 승인 통제가 요청 서비스와 같은 망, 같은 자격증명 안에 있으면 함께 넘어갑니다.

  • 고액·비정상 거래는 다른 망의 다른 사람이 승인합니다. 승인 화면은 요청 서비스가 만든 요약이 아니라 서명할 바이트를 다시 해석한 내용(목적지, 금액, 거래 종류)을 보여 줍니다. 바이비트가 당한 "서명 화면 바꿔치기"를 막는 장치입니다.
  • HSM·MPC가 지원하는 키 쪽 정책(정족수, 거래 종류 제한, 목적지 제한)을 켭니다. 소프트웨어 정책과 키 정책을 겹쳐 둡니다.

③ 이상한 서명 요청을 침해 지표로: 논스 범위·대량 요청

# 같은 키에 서로 다른 nonce 서명 요청이 짧은 시간에 몰리면 경보 (방어 예시)
from collections import defaultdict, deque
import time

WINDOW, LIMIT = 60, 3                       # 60초 안에 서로 다른 nonce 3개 이상이면 이상
seen = defaultdict(deque)                   # key_id -> deque[(time, nonce)]

def on_sign_request(key_id, nonce, kind):
    if kind == "authorization":             # 위임 서명 요청은 한 건만 와도 경보
        alert("eip7702_authorization_request", key_id=key_id, nonce=nonce)
    q, now = seen[key_id], time.time()
    q.append((now, nonce))
    while q and now - q[0][0] > WINDOW:
        q.popleft()
    if len({n for _, n in q}) >= LIMIT:
        alert("nonce_range_signing", key_id=key_id, nonces=sorted({n for _, n in q}))
        freeze_key(key_id)                  # 사람 확인 전까지 이 키의 서명 중지

정상 출금은 논스를 하나씩 순서대로 씁니다. 짧은 시간에 아직 쓰지 않은 논스 여러 개에 대한 서명 요청이 온다면, 그것은 "미래에 쓸 서명을 미리 모으는" 행동입니다.

④ 체인·지갑 등급별 분리: 한 번에 다 넘어가지 않게

  • 체인마다, 그리고 핫월릿 등급(소액 자동 출금용·대량 리필용)마다 서명 인프라와 자격증명을 나눕니다. 한 곳이 뚫려도 다른 체인의 서명은 나오지 않아야 합니다.
  • 핫월릿이 다른 컨트랙트의 소유자·실행자 권한을 갖지 않게 합니다. 이번 사건에서는 그 권한 때문에 8억 2,300만 USDC가 더 나갔습니다.

⑤ 출금 한도와 서킷 브레이커: 몇 분 안에 멈춘다

# 지갑별 10분 유출액이 기준을 넘으면 그 지갑의 서명·출금을 자동 중지 (방어 예시)
import redis, time
r = redis.Redis(host="127.0.0.1", port=6379)

LIMIT_USD = {"hot-tier-1": 2_000_000}       # 예시 값: 10분에 200만 달러

def record_outflow(wallet, tier, usd):
    key = f"outflow:{wallet}"
    now = time.time()
    r.zadd(key, {f"{now}:{usd}": now})
    r.zremrangebyscore(key, 0, now - 600)
    total = sum(float(m.decode().split(":")[1]) for m in r.zrange(key, 0, -1))
    if total > LIMIT_USD[tier]:
        trip_breaker(wallet)                # 서명 서비스에 '이 지갑 중지' 전달 + 당직 호출

이번 탈취는 주요 유출이 20분, 전체가 3시간이었습니다. 지갑별 유출 속도에 자동 중지가 걸려 있었다면 피해 범위는 크게 줄었을 것입니다. TRM과 국내편 분석 회사 의견도 같습니다.

⑥ 온체인 감시: 핫월릿에 "코드"가 생기면 즉시 경보

일반 계정에는 코드가 없어야 합니다. 위임이 성립하면 계정 코드가 0xef0100으로 시작하는 값으로 바뀝니다. 블록마다 확인하면 위임을 몇 초 안에 알아챌 수 있습니다.

# 감시 대상 핫월릿의 코드를 블록마다 확인 (방어 예시, 노드 주소는 가짜)
import requests
RPC = "https://rpc.example.com"
HOT_WALLETS = ["0xFAKE00000000000000000000000000000000B014"]

def get_code(addr):
    body = {"jsonrpc": "2.0", "id": 1, "method": "eth_getCode", "params": [addr, "latest"]}
    return requests.post(RPC, json=body, timeout=5).json()["result"]

def check_wallets():
    for addr in HOT_WALLETS:
        code = get_code(addr)
        if code not in ("0x", "0x0"):
            delegated_to = "0x" + code[8:] if code.startswith("0xef0100") else None
            page("hot_wallet_has_code", wallet=addr, delegated_to=delegated_to)
            trip_breaker(addr)              # 출금 중지 + 입금 주소 안내 중단
  • 함께 감시할 것: 핫월릿을 대상으로 한 유형 4 거래가 멤풀(처리 대기 거래)에 나타나는지, 처음 보는 컨트랙트 배포와 소액 토네이도 캐시 자금으로 시작한 지갑이 우리 주소를 건드리는지. 이번 공격자는 실전 몇 시간 전에 같은 컨트랙트로 리허설을 했습니다.

⑦ 사고 대응 런북: 위임 해제와 입금 차단을 미리 승인

  • 입금 경로 차단: 감염된 주소로 입금 주소를 안내하지 않게 하고, 내부 리필(다른 지갑에서 채워 넣기)을 멈춥니다. 이번에는 위임이 남아 있는 동안 새 입금이 여섯 번 쓸려 나갔습니다.
  • 위임 해제: 같은 키로 빈 주소에 대한 새 위임을 서명해 제출해야 합니다. 어떤 승인을 거쳐 누가 할지 미리 정하고 시험해 둡니다(TRM 권고: 시험을 거친, 미리 승인된 해제 서명 방법).
  • 동결 요청: 발행사(Tether, Circle 등)와 분석 회사 연락처, 보낼 정보 형식을 정해 둡니다.

⑧ 핫월릿은 작게

핫월릿에는 당장 출금에 필요한 만큼만 두고, 리필은 한도와 승인 절차를 거치게 합니다. 이번 이더리움 핫월릿 하나에는 ETH 16만 개가 넘게 있었습니다.

방어 2: 클라이언트에서 (개발자 기기·지갑 앱·프런트엔드)

① 운영에 닿는 개발자·DevOps 노트북

이번 사건의 출발점으로 지목된 곳입니다. 서버를 아무리 잠가도 운영에 닿는 사람의 노트북이 열려 있으면 소용없습니다.

  • 관리형 기기만 운영에 접근: 회사가 관리하는 노트북(EDR 설치, 디스크 암호화, 자동 패치)만 운영망·서명 시스템에 닿게 합니다. 외주·개인 기기도 예외 없이 같은 통제를 받게 합니다.
  • 운영 전용 단말(PAW): 서명 시스템·운영 클러스터에 접근하는 작업은 메일·메신저·브라우징을 하지 않는 별도 단말에서만 합니다.
  • 장기 자격증명 금지: 노트북 디스크에 오래 쓰는 키·토큰을 두지 않습니다. 접근할 때마다 몇 시간짜리 단기 자격증명을 발급합니다. 국내편 티빙 사고도 개발자가 가진 접속키에서 시작했습니다.
  • 피싱에 강한 MFA: 운영 접근은 하드웨어 보안 키(FIDO2) 기반으로 합니다. 문자·앱 승인 알림은 사회공학에 약합니다.
  • 새 패키지·확장 보류: 새로 올라온 패키지와 편집기 확장은 일정 기간(예: 48시간) 보류하고 검사한 뒤 설치합니다. 개발 도구 공급망을 노린 공격은 해외편 EU 집행위원회 사고에서 다뤘습니다.
  • 비정상 사용 경보: 같은 자격증명이 낯선 위치·시각·기기에서 쓰이면 바로 알립니다.

② 사람을 노리는 접근: 가짜 채용과 코딩 과제

TraderTraitor의 단골 수법입니다. 개발자에게 좋은 조건의 채용 제안을 보내고, "과제"라며 저장소를 내려받아 실행하게 합니다.

# 외부에서 받은 과제 저장소는 회사 노트북이 아닌 격리 환경에서, 스크립트 없이 연다 (방어 예시)
docker run --rm -it --network none -v "$PWD/task:/work:ro" node:22-slim bash
# 컨테이너 안에서: 설치 스크립트를 실행하지 않고 의존성만 확인
npm install --ignore-scripts --dry-run
  • 채용 담당자가 보낸 과제는 회사 기기에서 열지 않습니다. 네트워크가 끊긴 컨테이너나 일회용 VM에서 엽니다.
  • postinstall 같은 설치 스크립트, 실행 파일, 난독화된 코드가 있으면 그 자리에서 멈춥니다.
  • 회사 차원에서는 운영 권한을 가진 사람의 공개 프로필(링크드인 등)에 직무 세부를 적지 않게 안내합니다.

③ 지갑 앱·dApp을 만드는 개발자

EIP-7702는 일반 사용자 지갑에도 같은 위험을 만듭니다. 피싱 사이트가 "서명 한 번"으로 사용자 지갑 전체를 위임받을 수 있기 때문입니다.

  • 위임 서명은 별도 경고 화면으로: 일반 서명과 섞지 말고 "이 지갑의 모든 권한을 다음 컨트랙트에 넘깁니다"를 분명히 보여 줍니다. 위임 대상이 검증된 컨트랙트 목록에 없으면 막습니다.
  • chain_id 0 위임은 거부: 모든 체인에서 유효한 위임은 일반 사용자에게 필요하지 않습니다.
  • 현재 위임 상태 표시: 지갑 화면에 "이 계정은 지금 ○○ 컨트랙트에 위임돼 있음"을 늘 보여 주고, 해제 버튼을 둡니다.
  • 서명 화면 무결성: 서명 화면이 외부 스크립트를 불러온다면 버전을 고정하고 SRI(하위 리소스 무결성) 해시를 붙입니다. 바이비트는 클라우드에 올라간 스크립트가 바뀌어 당했습니다(국내편 EP09).
<script src="https://cdn.example.com/wallet-sdk/1.4.2/sdk.min.js"
        integrity="sha384-FAKEFAKEFAKEFAKEFAKEFAKEFAKEFAKEFAKEFAKEFAKEFAKE"
        crossorigin="anonymous"></script>
<meta http-equiv="Content-Security-Policy" content="script-src 'self' https://cdn.example.com">

④ 일반 사용자

  • 모르는 사이트에서 뜬 "위임(delegation)", "계정 업그레이드", "스마트 계정 전환" 서명 요청은 거절합니다.
  • 큰 자산은 위임 기능을 쓰지 않는 하드웨어 지갑에 두고, 화면에 표시된 내용을 기기 화면에서 다시 확인합니다.

방어는 네 겹으로

방어 네 겹. 클라이언트의 사람과 기기, 서버의 서명 서비스, 서버의 출금과 감시, 출구의 거래소와 발행사

이번 사건은 첫 겹(사람과 기기)이 뚫린 뒤 둘째 겹(서명 정책)과 셋째 겹(출금 한도·감시)에서 멈추지 못했습니다. 넷째 겹(발행사 동결·차단 목록)이 사후에 44%를 건졌습니다. 어느 한 겹도 완벽하지 않다는 전제로, 한 겹이 뚫려도 다음 겹에서 멈추도록 설계하는 것이 이 글의 결론입니다.

국내 사고와 이어 보기

점검 체크리스트

  • 서명 서비스가 "임의의 해시·메시지 서명"을 받지 않는다. 완성된 거래만 받아 직접 해석한다
  • 유형 4 거래와 EIP-7702 위임 승인은 기본 거부이고, 요청만 와도 경보가 울린다
  • 목적지·함수·금액 허용 목록과 정책이 출금 서비스가 아니라 서명 쪽(HSM·MPC 정책 포함)에 있다
  • 고액·비정상 거래는 다른 망의 다른 사람이, 다시 해석한 거래 내용을 보고 승인한다
  • 같은 키에 여러 논스 서명 요청이 몰리면 키 사용이 자동으로 멈춘다
  • 체인·지갑 등급마다 서명 인프라와 자격증명이 나뉘어 있다
  • 지갑별 10분 유출액에 자동 중지(서킷 브레이커)가 걸려 있다
  • 핫월릿에 코드가 생기면 몇 초 안에 경보가 오고, 입금 경로 차단·위임 해제 절차가 미리 승인돼 있다
  • 운영에 닿는 노트북은 관리형이고, 운영 접근은 전용 단말·하드웨어 키·단기 자격증명으로만 한다
  • 외부 과제·저장소는 격리 환경에서, 설치 스크립트 없이 연다

정리

  • 이번 탈취에서 깨진 것은 암호가 아니라 서명하는 쪽이었습니다. 키를 HSM에 넣어 둬도, 요청이 오면 무엇이든 서명하는 구조라면 요청하는 쪽을 가진 공격자에게 키는 이미 넘어간 것입니다.
  • EIP-7702 위임은 서명 하나로 계정의 모든 권한을 넘기고, 해제할 때까지 남습니다. 위임 서명 요청, 여러 논스에 대한 서명 요청, 핫월릿에 코드가 생기는 일은 모두 정상 업무에서 생기지 않는 신호입니다.
  • 세탁은 "동결 전에 바꾸고, 추적 전에 흩는다"였고, 동결은 분 단위 경쟁이었습니다. 발행사가 있는 자산만 멈출 수 있었습니다.
  • 방어는 네 겹입니다. 사람과 기기, 서명 정책, 출금 한도와 감시, 출구의 차단과 동결. 한 겹이 뚫려도 다음 겹에서 멈추도록 만듭니다.

더 깊게

  • 인프런 「백엔드 개발자를 위한 서버 보안 실무」 수업
    • 3-6 MFA와 세션 탈취 대응: 피싱에 강한 인증과 토큰 바인딩
    • 5-3 시크릿 관리: .env 유출, 저장소 스캔, 키 회전
    • 6-2 탐지하고 자동으로 막기: 서버와 애플리케이션 이상 징후
    • 6-4 공급망 공격과 방어: SBOM, 서명, GitHub Actions 고정
    • 6-7 클라우드 계정이 진짜 자산이다: IAM 최소 권한과 감사 로그
    • 6-9 침해사고, 첫 한 시간: 격리, 증거 보존, 타임라인
  • 관련 글: 코드를 믿게 하기: 앱 서명과 Sigstore·출처 증명, 해외 보안사고 사례 · 아이순 유출과 미국 기소

보안을 더 알고 싶다면

오늘 본 사고의 원인은 대부분 서버와 코드의 기본기에서 막을 수 있는 것들이었습니다. 직접 손으로 막아 보고 싶다면 이런 강의가 있습니다.

실제 해킹 사고로 배우는 Spring Boot·Express 시큐어 코딩

  • AI로 자동화된 해킹 시대, 오픈소스 쇼핑몰 코드에서 실제 사고 22건을 재연하고 직접 취약점을 찾아 한 건씩 막습니다.
  • OWASP Top 10:2025 전체와 목록 밖 위험 20가지를 102개 수업으로 다루고, 취약점 37개를 모두 막은 최종 소스를 가져갑니다. Spring Boot와 Express 모두 다룹니다.
  • 이 글과 이어지는 수업: 13-1, 13-2, 13-3, 13-4

서버 보안 기초부터 차근차근 시작하고 싶다면 이 강의도 있습니다.

백엔드 개발자를 위한 서버 보안 실무: 해킹 방어부터 정보유출 방지까지

  • 서버를 공격하는 흐름을 로그로 보고, 내 서버와 Spring·Express 코드에서 직접 막아 봅니다.
  • SSH 하드닝, 인젝션, 인증·인가, 시크릿과 개인정보 보호, 침해사고 첫 한 시간까지 8개 섹션 54개 수업입니다.
  • 이 글과 이어지는 수업: 3-6, 6-2, 6-4, 6-9

출처와 확인 날짜

수치와 사실은 2026년 11월 1일 기준 공개 발표·분석입니다. 바이낸스는 최종 원인 보고서를 내지 않았고, 수사·분석이 진행 중이라 바뀔 수 있습니다. 원화는 1달러 1,400원 기준으로 환산했습니다. 이 글은 법률 자문·투자 조언이 아닙니다.


다음 글 두 번 해석되는 요청: 파서 차이, 요청 밀반입, SMTP 밀반입을 서버에서 막는 법
시리즈 전체 목록 해킹 방식 VS 방어 방법 전체 목록

profile
고인물 개발자(OG)

0개의 댓글