GoPlus Security:Vyper 컴파일러 0.2.15 버전 취약점으로 인한 Curve 공격 분석
배경
최근 Vyper 0.2.15 버전으로 작성된 안정 풀(alETH/msETH/pETH)이 재진입 공격을 받았습니다. 이는 해당 버전 컴파일러의 구현 오류로 인해 재진입 잠금 실패 문제가 발생했기 때문입니다. 이 취약점으로 약 5000만 달러의 손실이 발생했습니다. 본 문서에서는 이 취약점의 근본 원인과 공격 원리를 분석하고, 유사 사건의 재발을 방지하기 위한 최적화 제안을 제공합니다.
근본 원인 분석
취약점의 근원은 컴파일러가 @nonreentrant(\<str>) 수식어의 의미를 잘못 구현한 데 있습니다.
올바른 의미 설명
@nonreentrant(\<str>)는 함수의 재진입을 제한하는 데 사용되며, 그 의미는 다중 스레드의 상호 배제 잠금과 유사합니다:
단일 함수 수식: 하나의 함수가 해당 수식어로 수식될 때, 그 함수의 여러 호출은 동시에 실행될 수 없습니다.
다중 함수 수식: 여러 함수가 동일한 수식어를 사용하고 재진입 제한 키가 동일(문자열 값이 동일)할 경우, 그 함수의 여러 호출도 동시에 실행될 수 없습니다.
올바른 구현은 각 재진입 제한 키에 대해 불리언 값을 설정하는 것입니다. 0은 함수가 실행 중임을 나타내고, 1은 함수가 실행 중이지 않음을 나타냅니다. 여러 개의 해당 키로 제한된 함수는 이 변수를 기반으로 동기화합니다(키를 얻으면 -1, 키를 해제하면 +1, 키를 얻으려면 값이 1인지 확인해야 함).
실제 의미 구현
Vyper 컴파일러 0.2.15 버전 및 이전 버전에서는 이 의미의 구현이 잘못되었습니다. 잘못된 코드는 다음과 같습니다.
분석 코드의 storage_slot은 위에서 언급한 동기화 변수를 나타냅니다. 오류는 그 값의 범위에 있습니다. 이 변수는 처음에 0으로 설정되며, 인터프리터가 함수 선언에서 @nonreentrant를 처리할 때마다 동기화 변수를 +1 합니다. 이는 중복입니다. 코드가 위에서 언급한 다중 함수 수식 상황을 만족할 때, 인터프리터가 여러 함수를 처리하므로 동기화 변수의 값 범위가 더 이상 불리언 값을 만족하지 않으며, 올바른 의미와 일치하지 않습니다(즉, 잠금 실패가 발생함). 사실, 코드의 TODO 주석은 개발자의 우려를 이미 반영하고 있습니다.
수정 패치에서 해당 키가 이미 생성되었는지 확인하고, 그렇다면 더 이상 +1 하지 않도록 수정했습니다.
결과 이러한 계약은 해당 수식어의 실제 구현이 예상과 일치하지 않아 공격을 받았으며, 이 계약들이 이론적으로는 올바른 것이었습니다. 예를 들어, 다음에 소개할 사례가 있습니다.
계약 공격 사례
위에서 언급한 다중 함수 수식 계약을 모두 공격할 수 있습니다. 예를 들어 공격받은 계약은 5개의 함수가 동일한 키 @nonreentrant("lock")로 수식되어 있어 서로 재진입할 수 있습니다.
- add_liquidity
- remove_liquidity
- exchange
- removeliquidityimbalance
- removeliquidityone_coin
해커의 공격은 처음 두 함수를 이용했습니다. 이들은 각각 유동성 풀에 자산을 추가하고 제거하는 데 사용되며, 유동성 제공자는 이를 통해 거래 수수료와 기타 보상을 받을 수 있습니다. 공격 과정은 일반적인 재진입 공격과 유사합니다:
- 첫 번째 호출에서 add_liquidity로 자산을 저장합니다.
- remove_liquidity를 호출하여 이러한 자산을 제거합니다.
여기서 removeliquidity는 외부 호출을 통해 해커에게 ETH를 반환합니다. 이때 해커는 fallback 함수 내에서 addliquidity를 호출하여 재진입을 완료합니다.
addliquidity로 재진입한 후, removeliquidity가 토큰 총량 total_supply를 아직 줄이지 않았기 때문에 해커가 발행한 토큰 수를 계산할 때 원래의 수를 초과하게 됩니다.
remove_liquidity()
add_liquidity()
해결책 및 미래 발전
이번 사건을 통해 우리는 미래의 Web3 보안 개발 솔루션에 대한 몇 가지 생각을 하게 되었습니다:
1. 프로토콜 개발자의 책임과 민감성
프로토콜 개발자는 핵심 공급망의 업데이트와 보안 문제에 적극적으로 관심을 기울이고, 관련 정보를 신속하게 확보하며, 기본 구현에 대한 이해를 높여야 합니다.
2. DevSecOps의 도입
계약 개발 프로세스에서 보안 테스트 및 검증 도구를 더 잘 도입하는 방법, 정적 분석이든 동적 Fuzz든, SDLC(소프트웨어 개발 생명 주기)에 통합하여 문제를 더 일찍 노출하고 해결할 수 있도록 해야 합니다.
3. Software Composition Analysis (SCA) 도구의 도입
Vyper 취약점이 공개된 후, 공격받은 몇몇 프로토콜 팀에게는 약 반나절에서 하루 정도의 시간만이 주어졌습니다. 그러나 안타깝게도 결국 도난 자산을 구하는 데 성공하지 못했습니다. 우리는 이러한 공급망 문제에 대해 완벽한 공급망 관리 플랫폼을 도입해야 한다고 생각합니다. 즉, Web2에서 흔히 말하는 아티팩트 저장소 관리와 같이 공급망에 문제가 발생했을 때 신속하게 경고하고 업그레이드 방안을 제시할 수 있어야 합니다.
4. 거래 기본 패러다임의 혁신
기반 시설 차원에서 실행 시점에 자동화된 방어를 고려해야 합니다. 예를 들어 동적 보안 검사 또는 중요한 작업을 수행할 때 자동으로 방어 조치를 실행하는 것입니다. 우리는 이 분야에서 Uniswap V4와 같은 여러 트렌드를 보게 되어 기쁩니다. Hook을 도입하여 거래 기능의 확장성을 크게 높였으며, 개발자는 거래의 여러 단계에서 보안을 강화하기 위한 방어를 제공할 수 있습니다. 유사하게 Artela는 개발자가 런타임 수준에서 여러 단계의 확장을 구현할 수 있게 하여 재진입 문제를 자연스럽게 해결할 수 있도록 합니다. 구체적인 내용은 https://medium.com/@artela_chinese/ 해당 기사를 참조할 수 있습니다.
결론
스마트 계약 개발의 복잡성과 어려움으로 인해 취약점의 발생은 거의 피할 수 없습니다. 그러나 기존 도구를 더 잘 이해하고 사용하며, 오픈 소스 커뮤니티 활동에 지속적으로 관심을 기울이고 참여하며, 기반 시설을 최적화하고 개선함으로써 우리는 취약점의 위험을 줄이고 전체 시스템의 보안성과 견고성을 높일 수 있습니다. 미래의 블록체인 보안은 컴파일러와 개발자의 공동 노력이 필요할 뿐만 아니라 정적 분석, 동적 Fuzz와 같은 첨단 도구의 지원과 추진에 의존해야 합니다. 또한 혁신가들이 제약을 깨고 더 나은 기본 거래 모델과 솔루션을 제시하여 전체 Web3 세계를 더욱 안전하게 만들 필요가 있습니다.