블록체인

[블록체인] Delegate Call의 위험성 - Parity Wallet Bug

실실쟌 2023. 1. 7. 18:24

어제 Delegate Call에 대해 공부하고, 관련된 버그가 발생했었다는 것을 알게 되었다.

2017년에 있었던 일이다.

 

요약

1. WalletLibray에 버그가 있어서 재배포

 - 이 과정에서 initWallet을 호출하지 않아서 WalletLibrary는 주인 없는 Contract가 됨

2. 어떤 해커가 WalletLibrary의 initWallet을 호출, WalletLibrary의 주인이 됨 -> WalletLibrary에 대해 kill을 호출

 - 이 결과 WalletLibrary가 블록체인에서 사라지게 됨

 - 문제는 WalletLibrary는 multisig wallet의 입금 외 모든 기능을 담당하는 Contract였다는 것

 - 따라서 Wallet에 넣어 둔 모든 이더가 동결되었다.

3. 그 후 해당 해커가 올린 깃허브 이슈가 가관이다...

 

I accidentally killed it :)

 

어떻게 가능했을까?

 Multi-sig(다중서명) Wallet이란 주인이 여러 명인 Wallet을 의미한다. 일반적인 Wallet이 key가 하나라면 멀티시그 Wallet은 key가 여러 개여서, 일정 수 이상의 key가 있어야 자금을 운용할 수 있다. 회사나 기금 같은 곳에서 Wallet을 한 사람 마음대로 사용할 수 없도록 하기 위해 사용한다.

 Parity는 Multisig Wallet은 WalletLibaray라고 하는, 대부분의 지갑 기능을 담당하는 라이브러리 컨트랙트와 해당 라이브러리를 delegateCall하여 사용하는 프록시 컨트랙트로 구성되어 있다.

 

WalletLibaray

contract WalletLibrary {
     address owner;

     // called by constructor
     function initWallet(address _owner) {
         owner = _owner;
         // ... more setup ...
     }

     function changeOwner(address _new_owner) external {
         if (msg.sender == owner) {
             owner = _new_owner;
         }
     }

     function () payable {
         // ... receive money, log events, ...
     }

     function withdraw(uint amount) external returns (bool success) {
         if (msg.sender == owner) {
             return owner.send(amount);
         } else {
             return false;
         }
     }

보다시피 Wallet 초기화나 인출 등을 담당한다. 간단히 표현된 코드로, 글 하단에 첨부한 링크 중 하나에서 가져왔다.

 

Wallet

contract Wallet {
    address _walletLibrary;
    address owner;

    function Wallet(address _owner) {
        // replace the following line with “_walletLibrary = new WalletLibrary();”
        // if you want to try to exploit this contract in Remix.
        _walletLibrary = <address of pre-deployed WalletLibrary>;
        _walletLibrary.delegatecall(bytes4(sha3("initWallet(address)")), _owner);
    }

    function withdraw(uint amount) returns (bool success) {
        return _walletLibrary.delegatecall(bytes4(sha3("withdraw(uint)")), amount);
    }

    // fallback function gets called if no other function matches call
    function () payable {
        _walletLibrary.delegatecall(msg.data);
    }
}

입금 이외의 모든 로직을 담당한다. 앞서 DelegateCall을 공부할 때 봤듯이, DelegateCall을 호출하여 대부분의 상태변화를 한다.

 

위험성

위 코드에서 Wallet 컨트랙트의 <address of pre-deployed WalletLibrary>가 사라지면, 다음과 같은 문제가 발생한다.

 

1. 돈이 모두 Wallet에 갇혀 버린다. 입금만 할 수 있다.

2. WalletLibaray에 대한 DelegateCall의 결과값이 모두 참이 된다. 죽어버린 컨트랙트에 대한 호출이기 때문이다.

 

How to exploit, 코드의 문제점

코드의 문제점은 두가지이다.

1. WalletLibrary의 initWallet(address _owner) 함수가 internal이 아니다.

 - internal이 붙으면 외부에서 아예 호출을 못하기 때문이다. 따라서 어쩔 수 없다.

 - 그런데 internal이 아니면 외부에서 delegateCall을 사용하지 않고 직접호출할 수 있게 된다.

 - 직접 호출하면 WalletLibrary의 owner가 변하게 된다.

 - 따라서 initWallet을 직접 호출한 사람이 WalletLibaray의 주인이 된다.

2. <address of pre-deployed WalletLibrary>가 하드코딩 되어 있다.

 - 새로 WalletLibrary를 만들어도 Wallet이 리다이랙트하는 목적지를 바꿀 수가 없다.

 - 따라서 WalletLibrary가 삭제되면 Wallet의 동작을 수정할 방법이 없다.

이러한 문제점을 가진 상황에서, initWallet을 호출하지 않았기 때문에 누구나 WalletLibrary의 주인이 될 수 있었던 것이다.

 

 

 

 
 
 
 
 

 

 

 

https://hackingdistributed.com/2017/07/22/deep-dive-parity-bug/

 

An In-Depth Look at the Parity Multisig Bug

We do a deep-dive into Parity's multisig bug.

hackingdistributed.com

https://www.parity.io/blog/a-postmortem-on-the-parity-multi-sig-library-self-destruct/

 

A Postmortem on the Parity Multi-Sig Library Self-Destruct

On Monday November 6th 2017 02:33:47 PM UTC, a vulnerability in the “library” smart contract code, deployed as a shared component of all…

www.parity.io

참고한 문서입니다

 

이런 티셔츠도 있다 ;; (devops199 : 이슈 올렸을 당시 깃허브 이름)