[블록체인] Delegate Call의 위험성 - Parity Wallet Bug
어제 Delegate Call에 대해 공부하고, 관련된 버그가 발생했었다는 것을 알게 되었다.
2017년에 있었던 일이다.
요약
1. WalletLibray에 버그가 있어서 재배포
- 이 과정에서 initWallet을 호출하지 않아서 WalletLibrary는 주인 없는 Contract가 됨
2. 어떤 해커가 WalletLibrary의 initWallet을 호출, WalletLibrary의 주인이 됨 -> WalletLibrary에 대해 kill을 호출
- 이 결과 WalletLibrary가 블록체인에서 사라지게 됨
- 문제는 WalletLibrary는 multisig wallet의 입금 외 모든 기능을 담당하는 Contract였다는 것
- 따라서 Wallet에 넣어 둔 모든 이더가 동결되었다.
3. 그 후 해당 해커가 올린 깃허브 이슈가 가관이다...
어떻게 가능했을까?
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
참고한 문서입니다