패리티 멀티시그 지갑 해킹 원인 분석

The Parity Wallet Hack Explained(https://blog.zeppelin.solutions/on-the-parity-wallet-multisig-hack-405a8c12e8f7)글을 번역하고 간추렸습니다.

최초 지갑 컨트랙트 생성 시 지갑의 소유권을 지정하는 코드가 anytime, anyone이 실행할 수 있게 작성된 것이 원인. (해당 코드는 컨트랙트 생성시 최초 1회, 지갑 컨트랙트 생성자만 실행할 수 있어야함)

개요

  • 이더리움 네트워크 역사상 두번째로 큰 ETH 절도 사건(7/19) 발생
  • 토큰 세일 때의 펀드를 저장하는 high-profile 멀티시그 컨트랙트에서 153,037 ETH (~330억)도난.
  • 패리티 팀이 직접 취약점 보고.

취약점 설명

해커는 취약점이 발견된 컨트래트에 두개의 트랜잭션 전송.

  • 멀티시그 지갑의 배타적 소유권 획득을 위한 트랜잭션
  • 컨트랙트의 모든 펀드를 이체하는 트랜잭션

첫번째 트랜잭션은 initWallet 함수(WalletLibrary 컨트랙트 216줄) 호출이 목적

// constructor - just pass on the owner array to the multiowned and // the limit to daylimit
function initWallet(address[] _owners, uint _required, uint _daylimit) {
initDaylimit(_daylimit);
initMultiowned(_owners, _required);
}

지갑 생성자 코드를 별도의 라이브러리 컨트랙트로 분리. (일종의 프록시 라이브러리 패턴)

지갑 컨트랙트는 unmatched function calls(지갑 컨트랙트에 존재하지 않는 함수를 호출)을 delegatecall(Wallet 컨트랙트 424줄)을 이용하여 라이브러리 컨트랙트로 포워드함.

function() payable {
// just being sent some cash?
if (msg.value > 0)
Deposit(msg.sender, msg.value);
else if (msg.data.length > 0)
_walletLibrary.delegatecall(msg.data);
}

이로인해 아무나 라이브러의 public 함수를 호출할 수 있게된 것이 원인.
즉, 누구나 initWallet 함수를 호출해서 컨트랙트의 소유자를 변경할 수 있음.
(initWallet 함수는 컨트랙트 최초 deploy 시 컨트랙트의 초기화가 목적인데)컨트랙트가 초기화된 이후에도 호출할 수 있음.
해커는 컨트랙트의 m_owners 상태 변수를 모두 해커의 주소로 변경.

해법
i)지갑의 생성자 코드를 라이브러리에 두지 말고 지갑 컨트랙트 내에 두거나
ii)모든 unmatched function call을 delegatecall을 이용해서 포워드하는 방식을 사용하지 말 것.
지갑 컨트랙트 외부에서 호출 가능한 라이브러리 함수를 명시하는 것을 추천.

라이브러리를 이용한 추상화 기법은 코드 가독성과 gas deployment cost를 줄이는데 유용함.

그러나 이번 취약점은 이더리움 에코시스템에 위와같은 코딩 패턴이 효과적이고 안전하게 구현될 수 있도록 베스트 프랙티스와 스탠다드가 필요함을 분명하게 보여줌.

참조
DelegateCall 사용과 개스비 절감(http://www.chaintalk.io/archive/lecture/723)
Security Alert - Parity (https://blog.parity.io/security-alert-high-2/)
Proxy Libraries in Solidity (https://blog.zeppelin.solutions/proxy-libraries-in-solidity-79fbe4b970fd)
해커 계정 (https://etherscan.io/address/0xb3764761e297d6f121e79c32a65829cd1ddb4d32#internaltx)

H2
H3
H4
3 columns
2 columns
1 column
1 Comment