백만 달러의 화선 제폭: DeFi 자산 권한 취약점 심층 해석

앰버그룹
2021-04-30 14:50:16
수집
당신의 지갑 주소가 다른 사람들이 당신의 자산을 사용할 수 있도록 허용하고 있습니까?

이 글은 Amber Group의 저자 우가지를 기반으로 합니다. 원문 제목: 《Exploiting Primitive FinanceApproval Flaws》。

사건 요약: 2월 24일, Primitive Finance(이더리움 체인상의 파생상품 프로토콜)에 대한 취약점 분석 보고서가 업계의 주목을 받았습니다. 보고서는 세 가지 화이트 해커 공격(보안 취약점 점검을 목적으로 하는 해킹)과 취약점 원리를 설명했습니다. 한 달이 지난 4월 14일, Amber Group 블록체인 보안 전문가 우가지를 대표로 하는 팀은 한 지갑 주소에 100만 달러 이상의 자산(500 WETH)이 위험에 처해 있음을 발견했습니다. 로컬에서 공격을 재현한 후, 팀은 Immunefi(DeFi 취약점 보상 플랫폼)를 통해 Primitive 프로젝트 측과 연락하여 잠재적 피해자가 WETH 권한을 재설정하도록 성공적으로 도왔습니다. 이 글에서는 팀이 어떻게 시뮬레이션 환경에서 이 취약점을 이용했는지, 그리고 블록체인 데이터 분석을 통해 잠재적 피해자 지갑 주소를 찾았는지에 대해 소개합니다.

원리: 스마트 계약의 균열

현재 EVM(이더리움 가상 머신) 및 ERC-20(이더리움 스마트 계약의 프로토콜 표준) 아키텍처에서 사용자가 스마트 계약과 상호작용할 때, 스마트 계약 자체는 코드 수준에서 ERC-20 전송 이벤트를 포착할 수 있는 콜백 메커니즘이 부족합니다. 예를 들어, 앨리스가 밥에게 100개의 XYZ 토큰을 보낼 때, 밥의 XYZ 잔액은 XYZ 계약에서 업데이트됩니다. 그러나 밥은 어떻게 자신의 XYZ가 늘어났는지 알 수 있을까요? 그는 Etherscan(이더리움 블록 탐색기)을 확인하거나 그의 지갑 앱이 이더리움 노드에서 자동으로 가져온 최신 잔액을 확인할 수 있습니다. 만약 앨리스가 100 XYZ를 스마트 계약 찰리에게 보낸다면, 찰리는 어떻게 자신의 XYZ 잔액이 증가했는지 알 수 있을까요?

image

사실 찰리는 100 XYZ를 받은 순간에 자신의 최신 잔액을主动적으로 확인할 수 없습니다. 그 이유는 이 전송이 XYZ 계약에서 발생했기 때문이며, 찰리 계약에서는 발생하지 않았습니다. 스마트 계약이 배포된 후에는 운영 체제처럼 특정 장소에 코드가 존재하며, 호출되어야만 작동합니다. 이 문제를 해결하기 위해 ERC-20 표준에는 널리 사용되는 메커니즘인 approve()/transferFrom()이 있습니다.

image

예를 들어, 앨리스가 찰리에게 100개의 XYZ 토큰을 입금해야 할 경우, 앨리스는 미리 찰리에게 자신의 100 XYZ 한도를 사용할 수 있도록 권한을 부여할 수 있습니다. 이때 찰리의 deposit() 함수는 하나의 거래에서 transferFrom()을 통해 앨리스의 지갑에서 100 XYZ를主动적으로 인출하고 찰리 계약의 상태(예: 앨리스의 XYZ 예치 잔액 cXYZ 증가)를 업데이트할 수 있습니다. 마찰을 줄이기 위해 많은 DApp은 사용자에게 프로젝트 주소에 무한한 XYZ 한도를 부여하도록 하여 이후의 transferFrom() 호출이 직접 성공하도록 하여 여러 번의 권한 부여 클릭 및 수수료를 면제합니다. 이는 찰리를 화이트리스트에 추가하는 것과 같습니다. 이 방식은 위험을 남깁니다. 만약 찰리가 악용하거나 공격을 받게 되면, 앨리스의 자산은 위험에 처하게 됩니다.

2020년 6월 18일에 발생한 이 사건은 제어되거나 문제가 있는 스마트 계약이 어떻게 악용되어 자산 손실을 초래할 수 있는지를 증명했습니다. 아래 코드에서 safeTransferFrom()은 안전한 transferFrom으로 명명되었지만, 우연히 공개 함수로 선언되어 누구나 Bancor 계약의 신원을 사용하여 임의의 사용자(from)의 임의의 수량(value)의 임의 자산(token)을 임의의 주소(to)로 전송할 수 있게 되었습니다.

image

간단히 말해, 만약 앨리스가 Bancor를 사용하고 Bancor에 무한 한도를 부여했다면, 그녀의 지갑에 DAI 잔액이 0보다 클 경우 해커는 즉시 그녀의 DAI를 빼낼 수 있습니다.

진단: 해커는 어떻게 "안전 검사"를 우회했는가?

위의 취약점 분석 보고서에 따르면, 이 외부 함수에는 유사한 취약점이 있지만 Bancor의 취약점처럼 직접적으로 악용될 수는 없습니다. 사실 공격자는 두 개의 ERC20 토큰 계약, 하나의 Uniswap 유동성 풀을 위조하고 Uniswap 플래시 론을 시작하여 아래 그림의 msg.sender == address(this) 검사를 우회해야 합니다. 복잡하게 들리지만, 경험이 있는 해커에게는 그리 어렵지 않습니다.

image

Primitive가 flashMintShortOptionsThenSwap()와 같은 인터페이스를 구현해야 하는 이유는 특정 사용 사례가 있기 때문입니다. openFlashLong() 함수에서 flashMintShortOptionsThenSwap()이 Uniswap의 flash-swap 호출 매개변수에 캡슐화되어 있으며, 1371행에서 flash-swap이 트리거된 후, 콜백 함수 UniswapV2Call()이 호출됩니다. 이때 UniswapV2Call()이 Primitive 계약 내에 있기 때문에 위의 msg.sender == address(this) 검사를 통과할 수 있습니다.

image

주의할 점은 openFlashLong() 함수에서 1360행에 msg.sender가 작성되어 있어 정상적인 경우 Primitive는 호출자의 자금만 사용할 수 있다는 것입니다. 그러나 공격자는 위조된 pair 및 params를 사용하여 1371행과 유사한 방식으로 Primitive 계약의 UniswapV2Call()을 직접 호출하고 flashMintShortOptionsThenSwap()의 검사를 우회할 수 있습니다. 이 경우 params는 완전히 제어할 수 있으므로 1360행의 msg.sender는 Primitive에 권한을 부여한 임의의 지갑 주소로 대체될 수 있으며, flashMintShortOptionsThenSwap() 내의 transferFrom() 호출을 통해 자산을 탈취할 수 있습니다.

image

추적: 가능한 피해자를 찾아라

만약 해커가 우연히 어떤 "대부호"가 문제가 있는 계약에 권한을 부여했다는 것을 알게 된다면, 그는 이 취약점을 쉽게 이용하여 피해자의 많은 자금을 탈취할 수 있습니다. 그러나 이 작업은 블록 탐색기만으로는 매우 어렵습니다. 특히 계약이 이미 오랜 시간 동안 배포되어 있고 많은 사용자 수가 있는 경우에는 더욱 그렇습니다. 분석해야 할 데이터는 수작업으로 Etherscan을 검색하여 얻을 수 있는 것이 아닙니다.

Google Cloud Public Datasets(구글이 BigQuery에 호스팅하는 데이터 세트)는 이때 유용하게 사용될 수 있습니다. 모든 성공적인 approve() 호출은 이더리움에서 Approval() 이벤트를 발생시키므로, 우리는 BigQuery(구글의 클라우드 데이터 웨어하우스 솔루션) 서비스를 통해 모든 이벤트를 찾아내고, _spender가 Primitive 계약인 모든 이벤트를 필터링할 수 있습니다.

image

아래는 GCP에서 잠재적 피해자를 찾기 위해 실제로 사용한 SQL 문입니다. 다섯 번째 줄에서 이더리움 데이터베이스 및 이벤트 기록 테이블을 제한하고, 일곱 번째 줄에서 Approval() 이벤트를 필터링하며, 여덟 번째 줄에서 특정 _spender를 필터링합니다. 또한 여섯 번째 줄에서 블록 높이 범위를 Primitive 계약 배포 이후로 설정하여 BigQuery가 스캔해야 할 데이터 양을 크게 줄입니다. 이러한 SQL 최적화는 GCP 청구서에 직접 반영됩니다.

image

다음으로, 우리는 approve(_spender, 0)를 통해 권한을 재설정한 계정을 목록에서 제외하여 최종 계정 목록을 얻을 수 있습니다. 최종 목록을 확보한 후, 우리는 스크립트를 사용하여 이러한 계정을 모니터링하고, 위험한 계정이 많은 자산을 받을 때 경고를 발송합니다. 이는 심각한 손실을 초래할 가능성이 높기 때문입니다.

어느 수요일 아침, 로봇이 경고를 발송했습니다. 한 잠재적 피해자가 베이징 시간 4월 13일 오전 5시 24분에 거의 500 WETH의 자산을 받았으며, 이는 100만 달러를 초과하는 가치입니다. 공개된 세 번의 화이트 해커 공격과 비교할 때, 이 피해자가 성공적으로 공격당할 경우 손실 금액은 이전 세 가지 사례의 총합보다 클 것입니다.

image

우리는 베이징 시간 9:32에 Primitive 프로젝트의 취약점 보상 프로그램 운영자 Immunefi와 긴급 연락을 취하고, 우리가 어떻게 (재)이용하여 시뮬레이션 환경에서 피해자의 500 WETH를 탈취했는지 보여주었으며, 아래의 스크린샷을 포함한 증거를 제공했습니다.

image

Primitive 팀의 도움으로 잠재적 피해자는 10:03에 WETH 권한을 재설정하여 위기를 해소했습니다.

image

이틀 후, Primitive 팀은 이 발견에 대해 취약점 보상을 제공하고 공개적으로 감사의 말을 전했습니다. 이 보상금은 CryptoRelief(인도의 코로나19 구호를 위한 기금)에 기부되었습니다.

재현: 취약점 이용의 분해

취약점 이용의 첫 번째 단계는 두 개의 ERC20 계약인 Redeem 및 Option을 준비하는 것입니다.

image

여기서 Redeem 계약은 표준 ERC20이며, OpenZeppelin의 구현을 기반으로 mint() 인터페이스를 노출하여 토큰 수량을 제어할 수 있도록 합니다. 아래와 같이 구현됩니다:

image

Option 계약은 상대적으로 복잡합니다. 아래 코드 조각에서 볼 수 있듯이, 우리는 일부 전역 변수(예: redeemToken)와 공개 함수(예: getBaseValue())를 의도적으로 구성해야 합니다. 이러한 것들은 Primitive의 비즈니스 논리에서 사용됩니다. 또한 Option 계약을 초기화하기 위해 세 가지 매개변수를 전달해야 합니다:

· redeemToken: 앞서 구성한 Redeem 계약 주소

· underlyingToken: 공격 대상 계정이 보유한 자산 계약 주소

· beneficiary: 수혜자 주소, 즉 공격 성공 후 피해자 자산이 이전될 목표 주소

image

특히 mintOptions() 함수에 대해 설명할 필요가 있습니다. 위 코드에서 볼 수 있듯이, 이 함수는 모든 underlyingToken을 beneficiary 주소로 직접 전송합니다. 이는 아래의 내부 함수 mintOptionWithUnderlyingBalance()가 flashMintShortOptionsThenSwap() 호출 시 underlyingToken을 Option 토큰 계약으로 전송하고, mintOptions()를 통해 Option 토큰을 발행하기 때문입니다. 따라서 우리는 위조된 Option 계약에서 mintOptions()를 출금 호출로 간주하여 underlyingToken을 beneficiary(즉, 공격을 시작한 주소)로 전송하여 이후 플래시 론 자금을 상환하는 데 사용할 수 있습니다.

image

다음으로, 방금 생성한 Redeem 및 Option 토큰으로 Uniswap의 유동성 풀을 생성할 수 있습니다. 이 풀의 주소는 피해자 지갑에서 전송된 자금을 수신하는 데 사용됩니다. 사실, 각 Uniswap 풀에는 동등한 두 가지 자산이 존재합니다. 예를 들어 WETH 및 Redeem(즉, Option.redeemToken())입니다. 취약점 이용을 완료하기 위해 우리는 풀에 유동성을 주입해야 합니다. Redeem은 우리가 생성한 것이므로 무한량의 토큰을 발행할 수 있지만, WETH는 어떻게 할까요?

image

플래시 론의 도움으로 우리는 기본적으로 무한한 자금을 이용하여 원하는 작업을 수행할 수 있습니다. 단, 하나의 거래 내에서 자금을 상환할 수 있어야 합니다. 이 사례에서는 Aave V2의 플래시 론을 사용하여 피해자의 총 자산의 99.7%에 해당하는 자금을 위의 유동성 풀에 예치했습니다.

image

Aave의 설계에 따라, 우리는 대출 자금을 받은 후의 작업을 수행하기 위해 executeOperation()이라는 콜백 함수를 구현해야 하며(예: Lib.trigger() 호출), 마지막으로 approve() 호출을 통해 Aave 계약이 플래시 론 자산과 수수료를 가져가도록 권한을 부여해야 합니다.

image

결론

EVM 기반 스마트 계약 세계에서 approve()/transferFrom()은 오랫동안 존재해온 고유한 문제입니다. DeFi 사용자에게는 자신의 지갑 주소가 다른 사람이 자신의 자산을 사용할 수 있도록 허용하고 있는지 주의 깊게 살펴보고, 정기적으로 자산 사용 권한을 재설정해야 합니다. 프로젝트 측에서는 출시 전에 다양한 각도에서 코드를 테스트하고 심지어 공격하는 데 더 많은 시간과 노력을 기울여야 합니다. 왜냐하면 여러분이 프로그래밍하고 있는 것은 모든 사용자의 진짜 돈이기 때문입니다.

저자 소개

우가지는 세계적인 암호 금융 스마트 서비스 제공업체 Amber Group에서 블록체인 보안 전문가로 재직 중입니다. 그는 미국 노스캐롤라이나 주립대학교 컴퓨터 전공에서 박사 학위를 받았으며, 안드로이드 보안 분야의 선두주자인 장쉬안 교수에게 배웠습니다. 미국 유학 중 시스템 보안 연구에 종사했으며, 주요 연구 방향은 가상화 보안 및 안드로이드 시스템 보안입니다. 우가지는 전 세계 안드로이드 보안 분야에서 큰 영향을 미치며, 여러 기술 논문을 발표하였고, 안드로이드 시스템 취약점 보안에 대한 경험이 풍부합니다. 그는 2017년부터 블록체인 보안 분야로 전향하였으며, 세계 최초의 분산형 익명 버그 바운티 플랫폼 DVP(Decentralized Vulnerability Platform)의 책임자를 역임하며 전 세계 화이트 해커들과 함께 오픈 소스 기본 코드의 취약점을 찾기 위해 노력하고 있습니다.

관련 태그
체인캐처(ChainCatcher)는 독자들에게 블록체인을 이성적으로 바라보고, 리스크 인식을 실제로 향상시키며, 다양한 가상 토큰 발행 및 조작에 경계해야 함을 상기시킵니다. 사이트 내 모든 콘텐츠는 시장 정보나 관련 당사자의 의견일 뿐이며 어떠한 형태의 투자 조언도 제공하지 않습니다. 만약 사이트 내에서 민감한 정보를 발견하면 “신고하기”를 클릭하여 신속하게 처리할 것입니다.
banner
체인캐처 혁신가들과 함께하는 Web3 세상 구축