해킹당한 모든 브릿지들에게
브릿지와 스마트 컨트랙트 한번에 공부하기
서론
이 글은 최근 발생한 브릿지 해킹 사건을 어떤 브릿지가, 어떻게 해킹당하였는지에 초점을 맞춰 소개하며, 해킹당한 브릿지의 메커니즘, 큰 틀에서 해킹이 어떻게 발생하였는지(하이레벨)와 자세한 내막(로우레벨)을 함께 다룬다.
주의사항
- 본인은 솔리디티 언어를 배우고 공부하고 있지만, 스마트 컨트랙트 전문가가 아닐뿐만 아니라 전문 개발자도 아니기 때문에, 로우레벨에서 살펴본 해킹 메커니즘에 대한 설명이 부족하거나, 잘못될 가능성이 있다.
- 2021년 7월달에 발생한 Thorchain 해킹은 해킹 메커니즘을 이해하지 못하여서 다루지 않았는데, 직접 살펴보고 싶은 경우, 이 글을 추천한다.
2021.08 | Poly Network
메커니즘
Poly Network는 Poly Network는 wrapping 메커니즘을 사용하는 브릿지로, Poly chain이라는 독자적인 블록체인을 사용한다. 보통 독자적인 체인을 사용하면, 참여자들이 올바르게 행동할 동기를 제공하기 위하여 네이티브 토큰을 도입하는데, Poly Network는 선택된 참여자만 합류할 수 있는 consortium 형태의 블록체인이라 토큰이 필요없다.
Alice가 ETH를 Poly Network를 이용하여 이더리움 체인에서 BSC 체인으로 옮기려면,
- ETH를 이더리움 체인의 Poly Network 컨트랙트에 락업한다.
- 이더리움 체인의 Poly Network 컨트랙트는 오프 체인의 이더리움 체인 Relayer에게 cross-chain transaction을 요청한다.
- 이더리움 체인 Relayer는 해당 요청이 담긴 블록 헤더와 트랜젝션 증명을 Poly chain에 동기화시킨다.
- Poly Chain의 keeper들은 발생한 cross-chain transaction을 multi-signature scheme을 통하여 BSC 체인 Relayer에게 보낸다.
- BSC 체인 Relayer는 BSC 체인의 Poly Network 컨트랙트로 트랜젝션 정보와 함께 증명을 보낸다.
- BSC 체인의 Poly Network 컨트랙트는 블록 헤더 검증을 거친 후에 원하는 cross-chain transaction을 실행한다.
해킹 | 하이레벨
해킹을 당한 이유는 다음과 같다.
- Keeper에게 주어진 너무 많은 권한
- 스마트 컨트랙트의 허점
위 메커니즘에서 알 수 있듯이, Poly Network을 사용할 때, cross-chain transaction이 유효한지 여부는 keeper들이 전적으로 결정한다. 일정 수 이상의 Keeper들이 트랜젝션을 유효하다고 판단 후, 사인을 하게 되면, destination 체인에서는 사인의 유효성만 확인되면, cross-chain transaction을 실행한다.
따로 트랜젝션이 유효한지 판단하지도 않고, Keeper의 사인의 유효성만 확인하기 때문에, 유저들의 자금은 keeper들에 과의존적이다.
Poly Network의 해커는 여기에 착안하여서, 스마트 컨트랙트의 허점을 파고 들어서, Keeper의 명단을 자기 자신으로 바꾼 후, 가짜 cross-chain transaction을 실행하고, 실제로는 트랜젝션이 invalid하더라도 자기가 keeper로서 사인하였기 때문에, destination chain에서는 사인을 검증하였을 때 유효함으로 자금을 인출해주었다.
해킹 | 로우레벨
배경지식
EthCrossChainManager
(이하, Manager
)
Manger
컨트랙트는 source 체인과 destination 체인에서 cross-chain transaction의 실행과 검증을 담당한다. Manager
컨트랙트의 _executeCrossChainTx()
(이하, execute()
) 는 Destination 체인에 있는 임의의 함수를 실행할 수 있는데, 이 것이 첫번째 허점이다.
EthCrossChainData
(이하, Data
)
Data
컨트랙트는 cross-chain transaction과 관련된 데이터를 보관한다. Data
컨트랙트의 putCurEpochConPubKeyBytes()
(이하, putPubkey()
) 는 Keeper들의 퍼블릭 키를 수정할 수 있는 함수이다. Data
컨트랙트의 owner(주인)은 Manager
컨트랙트인데, 이게 두번째 허점이다.
허점
해커의 목표는 Keeper의 퍼블릭 키를 자신의 퍼블릭 키로 바꾸는 것이다. 이는 putPubkey()
를 통해서만 가능하다. 잠깐 과연 그런가?
execute()
는 다음과 같이 생겼다.
abi.encodePacked(bytes4(keccak256(abi.encodePacked(_method, “(bytes,bytes,uint64)”)))
execute()
의 경우, _method
파라미터에 유저가 원하는 어떠한 값이나 넣을 수 있다. 그래서 해커는 putPubkey()
와 동일한 함수 signature를 도출하는 _method
값을 찾은 뒤에, 마치 putPubkey()
를 호출한 것’처럼’ 보이는 cross-chain transaction을 execute()
를 통해 실행시켰다. 이 방법이 가능했던 또 다른 이유는, execute()
가 포함된 Manger
컨트랙트가 putPubkey()
가 포함된 Data
컨트랙트의 owner(주인) 컨트랙트이기 때문이다.
결국, 해커는 putPubkey()
와 동일한 함수 signature를 가지는 f1121318093
값을 찾아내었다.
이를 이용하여, keeper의 퍼블릭 키를 오직 자신의 퍼블릭 키로 바꾼 뒤에, 원하는 만큼 자금을 빼내었다.
참고 자료
- Poly Network Hack Analysis – Largest Crypto Hack
- The Analysis and Q&A Of Poly Network Being Hacked
- THE POLY NETWORK HACK EXPLAINED
- Analysis of US$600M Poly Network Hack
- Secure The Bridge: Cross-Chain Communication Done Right, Part I
2022.01 | QBridge
메커니즘
QBridge는 Qubit Finance의 내부 브릿지로서, QBridge를 통하여 유저들은 이더리움과 BSC 사이에서 자산을 옮길 수 있고, 이더리움 체인의 자산을 담보로 BSC에서 대출을 할 수도 있었다.
QBridge는 X-Collateral이라는 메커니즘을 사용하는데, 이를 통하여 유저는 이더리움 체인위의 ETH를 담보로, BSC에서 대출을 받을 수 있다. 상세한 과정은 다음과 같다.
- 유저는 이더리움 체인의 Qubit 컨트랙트로 ETH를 보낸다.
- ETH는 락업되고, Qubit Finance는 BSC 체인에서 1 xETH를 유저에게 보내준다.
- 유저는 1 xETH를 담보로 대출을 받을 수 있다.
사실 거창하게 말했지만, 그냥 wrapping 메커니즘이랑 동일하다.
해킹 | 하이레벨
놀랍게도, 해커가 사용한 deposit()
는 새로운 depositETH()
가 개발된 뒤에 개발진이 지웠어야 하는데, 컨트랙트에 남겨놓은 함수이다. 해커는 ETH를 Qubit Finance의 컨트랙트에 예치하지 않았음에도 마치 예치한것'처럼' 속인다음에, BSC 체인에서 많은 양의 xETH를 민팅하였고, 이후 xETH를 BSC로 바꾼 다음에 떠났다.
해킹 | 로우레벨
배경지식
QBridgeHandler
(이하, Handler
)
Handler
컨트랙트는 전반적인 QBridge 메커니즘을 담당하는 컨트랙트로, 기존에 ETH의 예치를 담당하던 deposit()
와 새로 개발된 depositETH()
가 포함되어 있다. 위에서 언급하였듯이, deposit()
은 지웠어야 하는데, 실수로 계속해서 남겨져있었다. 이게 첫번째 허점이다. 그리고, deposit()
와 depositETH()
은 똑같은 이벤트를 발행한다(같은 기능을 하는 함수이기에, 당연한 일이다). 이게 두번째 허점이다.
허점
아래는 Handler
컨트랙트의 deposit()
이다.
함수를 살펴보면, 128번째 줄에서 화이트리스트된 토큰 주소만 에치할 수 있다. 근데, 이 deposit()
에 화이트리스트되어 있던 토큰 주소는 놀랍게도 null(zero) address였다.
이를 이용하여 해커는 아무것도 예치하지 않았는데도, deposit()
를 성공적으로 실행할 수 있었다.
여기서, 두번째 허점인 deposit()
와 depositETH()
이 같은 Deposit
이벤트를 발행한다는 사실을 이용하여, QBridge는 유저가 정상적으로 ETH를 예치한 줄 알고, xETH를 민팅해주었다.
참고 자료
- Protocol Exploit Report
- Bridge - Qubit Docs
- Qubit Bridge Collapse Exploited to the Tune of $80 Million
2022.01 | Multichain
메커니즘
Multichain(구, Anyswap)은 역시 wrapping 메커니즘을 사용하는 브릿지이다. Multichain은 Secure Multi Party Computation(SMPC) 노드들이 있는데, 이 SMPC 노드들은 다음과 같은 기능을 한다.
- 각 노드들은 cross-chain transaction의 유효성을 판단하고, threshold signature scheme을 사용하여 합의된 결과를 도출한다.
- 체인간 토큰 전송이 일어났을 때, source 체인에서 토큰을 SMPC 지갑 주소에 락업하고, destination 체인에서 SMPC 지갑 주소로부터 wrapped 토큰을 민팅한다.
해킹 | 하이레벨
해킹에 사용된 함수는 해킹이 일어난 1월 18일 이전까지 한번도 사용되지 않았는데, 하필 이 함수가 취약점을 가지고 있어서 문제가 되었다.
해킹 | 로우레벨
배경지식
anySwapOutUnderlyingWithPermit()
(이하, WithPermit()
)
이 함수의 기능은 다음과 같다.
- 토큰을 unwrap한다(예를 들어, anyDAI를 DAI로 바꾼다).
- 사인을 검증하여서 만약 유효하면, 유저의 주소로부터 자금을 인출할 수 있는 권한을
permit()
을 통하여 이 라우터 컨트랙트에 부여한다. - 실제로 토큰을 유저로부터 wrapped 토큰 주소로 이동시킨다.
허점
- 문제는 1번 과정에서부터 발생하였는데, 해당 함수에는 인풋인
token
이 실제로 Multichain이 발행한 wrapped 토큰인지 확인하는 과정이 없다. 이를 이용하여서 해커는 unwrap하였을 때, WETH의 토큰 주소가 나오는 자기만의 주소 값을 사용하였다. - 다음 2번 과정에서, 해커가 제대로 된 signature를 인풋으로 제공하지 않아도, WETH 토큰은
permit()
함수가 없기 때문에, fallback 함수가 호출되어서 다음 줄로 정상적으로 넘어간다. - 유저의 예치된 자금을 해커는 원하는대로 옮길 수 있게 되었다.
참고 자료
- Without Permit: Multichain’s exploit explained
- Introducing Anyswap — Fully Decentralized Cross Chain Swap Protocol
2022.02 | Wormhole
메커니즘
Wormhole에는 guardian이라는 존재들이 있는데, guardian들은 연결된 체인의 풀 노드를 운영하면서, Wormhole의 코어 컨트랙트에서 발행된 이벤트를 체크한다. Cross-chain transaction이 발생하면,
각 guardian들은 트랜젝션의 유효성을 검증한 뒤에 유효하다고 판단하면 사인을 한다.
과반수 이상의 guardian들이 사인을 하면, **VAA(Verifiable Action Approval)**이라는 메시지를 guardian들의 사인과 함께 Relayer를 통하여 destination chain으로 보낸다.
VAA를 받은 desitnation chain은 cross-chain transaction을 실행한다.
해킹 | 하이레벨
Wormhole의 컨트랙트에 사용된 특정 솔라나 내장 함수의 문제점이 발견되어서, 솔라나 1.8.0 버전부터 더 이상 사용하지 않기로 변경되었는데, Wormhole 팀이 이를 반영하기 전에 이 함수를 이용하여서 해킹을 해내었다.
해킹 | 로우레벨
배경지식
post_vaa()
앞서 언급한 VAA와 관련된 함수로, cross-chain transaction이 발생하였을 때, guardian들의 사인을 통하여 트랜젝션이 유효한지 판단한다.
verify_signatures()
post_vaa()
이 guardian들의 사인이 유효한지 판단할 때, verify_signatures()
를 사용한다.
허점
위에서 살펴본 verify_signatures()
은 load_current_index()
와load_instruction_at()
함수를 사용한다. 이 두 함수는 sysvar 계정의 주소를 체크하지 않는 문제가 있어서 각각 load_current_index_checked()
와, load_instruction_at_checked()
로 변경되었다. 해커는 이 변경 사항을 Wormhole 팀이 반영하기 전에, 문제가 있는 함수들을 사용하여서 verify_signatures()
와 post_vaa()
를 속인 뒤에, 120k의 whETH를 민팅해갔다.
참고 자료
- Solana’s Wormhole Hack Post-Mortem Analysis
- $320 Million Wormhole Hack Explained
- How did the wormholecrypto exploit work? @samczsun
2022.02 | Meter.io
메커니즘
Meter.io는 Meter Passport라는 cross-chain 라우터를 지원한다. Meter Passport는 기존의 ChainSafe bridge를 포크한 뒤, 살짝 수정한 형태이다. ChainSafe bridge의 cross-chain messaging protocol의 trusted relayer들은 다음과 같은 3가지 역할을 가진다.
Listener
Source chain에서 발생한 이벤트로부터 메시지를 도출해낸다.
Router
Listener에서 Writer로 메시지를 전달한다.
Writer
메시지를 받아서 destination chain에 트랜젝션을 전달한다.
토큰 전달의 경우, Meter Passport 역시 wrapping 메커니즘을 사용한다. 먼저 토큰들은 source 체인의 handler 컨트랙트에 락업되고, 5명의 trusted relayer들의 과반수 이상이 동의하면, destination 체인의 handler 컨트랙트에서 wrapped 토큰이 민팅된다.
해킹 | 하이레벨
QBridge 해킹과 비슷하게 같은 기능을 하는 두 함수(depositETH()
, deposit()
)가 존재하는데, 그 중 하나가 취약점을 가지고 있었다.
해킹 | 로우레벨
배경지식
Meter Passport가 기존의 ChainSafe bridge에 추가한 여러 부분 중에 하나는 네이티브 토큰이 브릿지될 때, wrapping과 unwrapping을 자동으로 수행하는 기능이다. 이로 인하여 기존의 ERC20Handler
컨트랙트의 deposit()
에서 브릿지된 토큰이 wrapped 네이티브 토큰일 때, 락업, 또는 소각을 하지 않는 부분이 추가되었다.
허점
depsitETH()
함수의 경우, 유저가 calldata에 입력한 amount
값이 실제 value
와 동일한지 체크하기 때문에, 문제가 없다.
하지만, deposit()
의 경우, 이 체크하는 부분이 빠져있어서, 해커는 calldata에 임의의 값을 전달하고, 실제 WETH를 예치하지 않고도, 원하는 만큼 토큰을 민팅해나갔다.
참고 자료
- Meter Docs
- ChainBridge Docs
- twitter thread about Meter.io Hack @ishwinder
- EXPLAINED: THE METER.IO HACK (FEBRUARY 2022)
2022.03 | Ronin Bridge
메커니즘
Ronin bridge는 이더리움과 Ronin 사이드체인을 연결하는 브릿지이다. Ronin bridge 역시 wrapping 메커니즘을 사용하는데, 이더리움 체인 상에서 ETH를 Ronin bridge 스마트 컨트랙트에 락업하면, Ronin 사이드체인에서 WETH를 민팅해준다.
트랜젝션의 유효성을 판단하는 것은 9명의 검증자 노드들인데, 토큰을 예치, 혹은 인출하려면, 과반수 이상, 즉 5명 이상의 검증자의 signature가 필요하다.
해킹 | 하이레벨
오늘 다룬, 그리고 여태까지 존재한 브릿지 해킹 중에 Ronin Bridge는 유일하게 스마트 컨트랙트의 허점으로 인하여 해킹이 당하지 않은 사례이다. 대신 Ronin Bridge 해킹의 경우, 해커가 직접 검증자 노드들의 signature를 탈취한 뒤에, 악의적인 트랜젝션을 맘대로 사인하였다.
해킹 | 로우레벨
해커는 총 5개의 signature를 확보하였는데, 그 중 4개가 Sky Mavis(Axie Infinity의 제작사)의 것들이였고, 하나는 Axie DAO 것이였다. 해커는 먼저 Sky Mavis를 해킹하여, 4개의 signature를 확보한 뒤에, gas-free RPC 노드의 백도어를 통하여 Axie DAO 검증자 노드의 signature까지 확보할 수 있었다. 이 것이 가능하였던 이유는 과거 2021년 11월 경에, Sky Mavis는 Axie DAO에게 대신 트랜젝션을 사인해달라고 요청하면서 Sky Mavis가 Axie DAO 노드에게 signature를 요청할 수 있는 API를 만들었기 때문이다. 이 API를 해커가 사용하면서, Axie DAO의 signature까지 확보할 수 있었다.
참고 자료
- Hack Track: Analysis of Ronin Network Exploit
- Community Alert: Ronin Validators Compromised
- Analyzing the Ronin bridge hack
결론
스마트 컨트랙트의 보안성
너무나도 당연하지만, 99%의 해킹은 스마트 컨트랙트의 오류, 허점, 실수로 일어난다. 특히 QBridge나 Multichain의 경우, 없어야 하거나 쓰지도 않는 함수를 컨트랙트에 그대로 둬서 문제가 생겼는데, 이러한 부분은 개발진에서 더 신경을 써야할 필요가 있는 것 같다.
이와 별개로, 앞으로 audit의 중요성은 더더욱 커져만 갈 것 같다. Audit을 받는 것은 당연히 필수이지만, 위 브릿지들의 경우에도 audit을 받은 경우가 여럿있어서, 얼마나 공신력있고, 실력있는 Audit 회사로부터 받느냐도 중요해질 것 같다.
검증자들의 탈중앙화
IBC나 LayerZero가 아닌 경우, 대부분의 브릿지들은 몇명의 검증자들이 multi signature scheme, 또는 threshold signature scheme을 통하여 cross-chain transaction을 허락할건지, 말것인지 판단한다. Ronin bridge의 사태로 알 수 있는 것은 당연히 검증자들의 수가 많은 것도 중요하지만, 하나의 주체가 여러개의 검증자 노드를 운영하는 것은 매우 위험하다는 것이다.