Categories: 블록체인 글

블록체인 보안의 10가지 얼굴 (3부)


합의 레벨 공격과 접근 제어 실패

지금까지는 컨트랙트 레벨(1부)과 경제 레벨(2부)을 다뤘습니다. 3부는 방향이 둘로 나뉩니다. 하나는 블록체인의 토대인 합의 알고리즘 그 자체를 겨냥하는 51% 공격이고, 다른 하나는 컨트랙트가 “누가 이 함수를 부를 수 있는가”를 제대로 정하지 못해 발생하는 접근 제어 실패입니다. 층위는 서로 다르지만, 두 문제 모두 “권한의 경계”가 무너진다는 공통점을 가집니다.


7. 51% 공격 (다수 해시파워/지분 공격)

취약점 개요

작업증명(PoW) 블록체인의 보안은 “정직한 참여자가 전체 해시파워의 과반을 차지한다”는 단 하나의 가정에 기대고 있습니다. 만약 한 주체가 해시파워의 과반을 확보하면, 그는 다른 누구보다 빠르게 블록을 생성할 수 있고, 따라서 자신만의 더 긴 체인을 남몰래 만들 수 있습니다. 네트워크는 가장 긴 체인을 유효한 것으로 채택하는 최장 체인 규칙(longest chain rule)을 따르므로, 이 공격자는 어느 순간 기존 체인을 자신의 체인으로 교체(reorg) 해버릴 수 있습니다.

익스플로잇 흐름 — 이중지불(double spend)

  1. 공격자가 정상 체인에서 거래소로 코인을 보낸다 (트랜잭션 A).
  2. 이와 동시에, 공격자는 이 트랜잭션 A가 존재하지 않는 별도의 체인을 비공개로 채굴한다.
  3. 거래소는 정상 체인에서 트랜잭션 A가 컨펌된 것을 확인하고, 공격자에게 상품이나 다른 코인을 지급한다.
  4. 이제 공격자가 그동안 비공개로 더 길게 채굴해 둔 체인을 공개한다. 네트워크는 최장 체인 규칙에 따라 이 더 긴 체인을 정본으로 채택한다.
  5. 트랜잭션 A가 없는 체인이 정본이 되었으므로, 공격자는 보냈던 코인을 사실상 돌려받는 동시에 이미 받은 상품도 챙긴다. 같은 코인을 두 번 쓴 셈이다.

여기서 핵심은 컨트랙트 버그가 전혀 없다는 점입니다. 프로토콜은 규칙대로 정확히 작동했습니다. 다만 그 규칙의 안전을 떠받치던 가정, 즉 “과반은 정직하다”는 전제가 깨졌을 뿐입니다.

실제 사례

  • 이더리움 클래식(ETC): 2019~2020년에 걸쳐 여러 차례 51% 공격을 당해, 수십만~백만 달러 규모의 이중지불과 체인 재구성이 반복적으로 발생했습니다.
  • Bitcoin Gold: 2018년 51% 공격으로 약 1,800만 달러 규모의 이중지불 피해를 입었습니다.

두 사례의 공통점은 해시파워가 작은 체인이라는 것입니다. 네트워크 전체 해시레이트가 낮을수록 과반을 임대하거나 확보하는 비용이 낮아지고, 그만큼 공격의 경제성이 성립합니다. 반대로 비트코인이나 이더리움 같은 대형 체인은 과반을 확보하는 데 드는 비용이 천문학적이어서 사실상 방어됩니다.

방어 패턴

  • 컨펌 수 상향: 거래 규모가 클수록 더 많은 블록 컨펌을 기다립니다. reorg로 뒤집으려면 그만큼 더 깊은 곳까지 다시 채굴해야 하므로, 공격에 드는 비용이 기하급수적으로 커집니다.
  • PoS 전환: 지분증명 체계에서는 공격을 시도하면 스테이킹된 지분이 슬래싱(몰수)됩니다. 즉 공격이 곧 자기 자산의 파괴로 이어지므로 강력한 경제적 억지력이 작동합니다. 이더리움이 The Merge를 통해 PoS로 전환한 핵심 동기 중 하나입니다.
  • 체크포인팅: 일정 주기로 신뢰된 체크포인트를 고정해, 그 시점보다 과거로 되돌아가는 reorg를 원천적으로 차단합니다.
  • 해시레이트 분산 모니터링: 특정 채굴 풀의 점유율이 위험 수준에 근접하면 조기 경보를 발령해 대응할 시간을 확보합니다.

8. 접근 제어 실패 (Parity 멀티시그, 2017)

취약점 개요

컨트랙트의 민감한 함수 — 자금 인출, 소유권 변경, 자기파괴 등 — 는 반드시 “누가 호출할 수 있는가”를 명시적으로 제한해야 합니다. onlyOwner 같은 제어자나 초기화 가드가 빠지는 순간, 누구나 그 함수를 호출할 수 있게 되어 컨트랙트의 통제권이 통째로 노출됩니다.

익스플로잇 흐름

// 취약: 초기화 함수에 가드가 없어 누구나 재초기화 가능
contract Wallet {
    address public owner;

    // initializer 가드가 없다!
    function initWallet(address _owner) public {
        owner = _owner;   // 공격자가 자신을 owner 로 설정 가능
    }

    function withdraw(uint amount) public {
        require(msg.sender == owner);
        payable(msg.sender).transfer(amount);
    }
}

이미 정상적으로 초기화된 지갑이라도, 공격자가 initWallet(attacker)를 다시 호출하면 소유권이 그대로 공격자에게 넘어갑니다. 재초기화를 막는 가드가 없기 때문입니다. 소유자가 된 공격자는 그 뒤 withdraw를 호출해 자금을 모조리 빼냅니다.

실제 사례 — Parity의 두 사건

1차 (2017년 7월): Parity 멀티시그 지갑의 초기화 함수가 위 예시와 유사하게 보호되지 않아, 공격자가 여러 지갑의 소유권을 재설정하고 약 3천만 달러를 탈취했습니다.

2차 (2017년 11월): 이쪽이 훨씬 더 인상적입니다. Parity 지갑들은 로직을 하나의 공용 라이브러리 컨트랙트에 위임(delegatecall)하는 구조였는데, 이 라이브러리 자체가 초기화되지 않은 상태로 방치되어 있었고, 게다가 kill()(self-destruct) 함수가 아무런 보호 없이 노출되어 있었습니다. devops199라는 사용자가 실수로 이 라이브러리를 자신의 소유로 초기화한 뒤 self-destruct를 호출했습니다. 그러자 이 라이브러리에 로직을 의존하던 모든 멀티시그 지갑이 동시에 벽돌이 되어, 약 5억 달러 이상의 자금이 영구히 동결되었습니다. 누군가 훔쳐간 것이 아니라 아무도 영원히 접근할 수 없게 되었다는 점이 특히 뼈아팠습니다.

이 2차 사건은 사실 1부에서 다룬 delegatecall/스토리지 취약점과 접근 제어 실패가 결합된 사례입니다. 초기화되지 않은 라이브러리 + 보호되지 않은 파괴 함수 = 재앙이라는 공식이 그대로 성립했습니다.

방어 패턴

  • 명시적 접근 제어자: 모든 민감한 함수에 onlyOwner나 역할 기반 접근 제어(OpenZeppelin AccessControl)를 적용해, 호출 권한을 코드에 못박아 둡니다.
  • 초기화 가드: initializer 제어자를 사용해 초기화 함수가 단 한 번만 실행되도록 강제합니다.
  • 배포 즉시 초기화: 구현체나 라이브러리 컨트랙트를 배포 직후 초기화해, “주인 없는” 상태를 단 한 순간도 남기지 않습니다.
  • self-destruct 신중 사용: 파괴 함수는 가능한 한 두지 않고, 꼭 필요하다면 엄격히 보호합니다. (Solidity 자체도 점차 selfdestruct 사용을 권장하지 않는 방향으로 가고 있습니다.)
// 안전: 초기화는 단 한 번, 파괴 함수는 소유자 전용
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";

contract Wallet is Initializable {
    address public owner;

    function initialize(address _owner) public initializer { // 1회만 실행
        owner = _owner;
    }

    modifier onlyOwner() {
        require(msg.sender == owner, "not owner");
        _;
    }
}

3부 요약

#문제무너진 경계대표 사례핵심 방어
751% 공격합의의 정직 과반 가정ETC, Bitcoin Gold컨펌 상향 · PoS
8접근 제어 실패함수 호출 권한Parity (2건)initializer · onlyOwner

51% 공격은 프로토콜의 안전 가정이 무너진 경우이고, 접근 제어 실패는 컨트랙트의 권한 경계가 무너진 경우입니다. 서로 다른 층위처럼 보이지만, 둘 다 “누가 무엇을 할 수 있는가”라는 질문에 대한 답이 흐트러졌을 때 발생한다는 점에서 본질이 같습니다.

마지막 4부에서는 온체인 코드를 완전히 벗어납니다. 서명이라는 암호학적 증거가 맥락을 벗어나 재사용되는 리플레이 공격, 그리고 역대 최대 규모의 해킹인 Ronin 브리지의 오프체인 키 유출 사건을 다룹니다.

이 글은 ChainLab 기술 시리즈의 일부입니다.

Tags: 블록체인보안, 51퍼센트공격, 이중지불, 접근제어, Parity

Recent Posts

블록체인이 진짜 파는 것: “나는 못 고친다”

체인 위의 데이터 — 3부작 중 3편 · ChainLab 앞의 두 편은 배관에 관한 것이었습니다…

2개월 ago

데이터를 소비하는 것이 스마트컨트랙트일 때

체인 위의 데이터 — 3부작 중 2편 · ChainLab 1편에서 우리는 하나의 재정의에 도달했습니다. 블록체인은…

2개월 ago

무료 데이터를 블록체인에 얹어 판다는 것

체인 위의 데이터 — 3부작 중 1편 · ChainLab 솔깃한 아이디어가 하나 있습니다. 부동산 실거래가…

2개월 ago

한국에선 왜 아직? — 규제 이야기, 쉽게 (5/5)

기술은 세계 최고 수준인데, 왜 우리나라에선 AI 결제가 아직 잘 안 굴러갈까요? 답은 '실력'이 아니라…

2개월 ago

에스크로 — 돈을 안전하게 맡기는 금고, 그런데 왜 어려울까 (4/5)

중고거래 안전결제, 다들 써보셨죠? AI 결제에도 이게 필요합니다. 문제는, 생각보다 훨씬 까다롭다는 거예요. 중고거래를 떠올려…

2개월 ago

AP2 & MPP — ‘미리 허락받는’ 결제 vs ‘탭 열어두는’ 결제 (3/5)

앞 편의 x402가 자판기였다면, 이번 두 방식은 '용돈 한도'와 '술집 외상장부'에 가깝습니다. 앞 편 복습:…

2개월 ago