빠르게 변화하는 블록체인 개발 세계, 특히 밈 토큰을 출시하는 경우에는 프록시를 통한 업그레이드 가능한 컨트랙트를 사용하는 것이 유연성을 유지하는 인기 있는 방법입니다. 하지만 Nethermind Security가 최근 분석한 바와 같이, OpenZeppelin 같은 검증된 라이브러리라도 주의 깊게 다루지 않으면 함정에 빠질 수 있습니다. 이번 취약점 사례를 살펴보면서 어디서 잘못됐고 어떻게 해결할 수 있는지 알아보겠습니다.
프록시 컨트랙트와 UUPS 이해하기
먼저, 초보자를 위한 간단한 설명부터 시작하자면: 솔리디티에서 프록시 컨트랙트는 중개자 역할을 합니다. 저장소와 사용자와의 접점을 가진 주소를 보유하면서, 로직은 구현(implementation) 컨트랙트로 위임합니다. 이 구조 덕분에 컨트랙트 주소를 변경하지 않고도 업그레이드할 수 있는데, 이는 커뮤니티 피드백에 따라 빠른 변경이 요구되는 밈 토큰 프로젝트에서 버그 수정이나 기능 추가에 매우 유용합니다.
OpenZeppelin의 UUPS(Universal Upgradeable Proxy Standard)는 이 패턴의 한 형태입니다. ERC-1967 프록시를 기반으로 하지만 업그레이드 로직을 구현 컨트랙트 자체에 두어 가스 효율성을 높였습니다. 주요 구성 요소로는 OwnableUpgradeable
, PausableUpgradeable
, UUPSUpgradeable
같은 컨트랙트가 있습니다.
Nethermind Security의 X 게시물에서 공유된 예제에서는 이러한 특성을 상속받은 VulnerableVault
컨트랙트를 볼 수 있습니다. 얼핏 단순해 보이지만, 숨겨진 위험이 존재합니다.
배포 및 초기화 과정에서 문제점 발견하기
코드를 보면 뭔가 이상한 점을 찾을 수 있나요? 해당 컨트랙트는 초기 vaultBalance
를 설정하는 생성자와, 소유권, 일시 중지 가능성, UUPS 설정을 처리하는 initialize
함수를 가지고 있습니다.
요점은 다음과 같습니다:
생성자에서 초기화 잠금 누락: 업그레이드 가능한 컨트랙트에서는 구현(로직) 컨트랙트를 단독으로 사용할 수 없으며, 프록시를 통해 호출되어야 합니다. 이를 방지하기 위해 OpenZeppelin은 생성자 내에서
_disableInitializers()
호출을 권장합니다. 이를 하지 않으면 공격자가 직접 구현 컨트랙트를 초기화해 소유권을 탈취하거나 혼란을 초래할 수 있습니다. 밈 토큰 개발자라면 토큰의 금고나 업그레이드 권한을 잃는 심각한 문제로 이어질 수 있습니다.생성자에서의 저장소 초기화 문제: 생성자에서
vaultBalance = _initialBalance
를 설정하지만, 프록시 뒤에 배포될 때 생성자는 구현 컨트랙트에 대해 실행됩니다. 즉, 이 잔고는 프록시가 아닌 구현 컨트랙트 저장소에 들어갑니다. 따라서 프록시를 통해 접근하는 거래에서는 이 값을 확인할 수 없어, 금고가 비어 있는 것처럼 잘못 동작할 수 있습니다.
해결 방법은? 이 잔고 설정을 initialize
함수로 옮기는 것입니다. 이 함수는 delegatecall을 통해 프록시 컨텍스트에서 실행되어, 올바른 저장소 위치에 상태 변경이 반영됩니다.
밈 토큰 크리에이터에게 왜 중요한가
밈 토큰은 빠르게 출시되는 경우가 많고, 템플릿이나 라이브러리를 깊이 감사하지 않고 사용하기도 합니다. 업그레이드 가능성은 바이럴 트렌드에 맞춰 프로젝트를 진화시키기에 좋지만, 이러한 세부사항을 소홀히 하면 해킹에 취약해집니다. DeFi 프로토콜에서 유사한 문제가 있었듯, 밈 생태계도 예외가 아닙니다—공격자들은 손쉬운 표적을 좋아합니다.
Nethermind의 인사이트는 항상 구현 컨트랙트를 안전하게 보호하라는 경고입니다. 생성자에 _disableInitializers()
를 호출하고, 상태 초기화는 업그레이드 안전한 함수에 맡겨야 합니다.
프록시 보안 강화를 위한 모범 사례
이런 함정을 피하려면:
상속 구조 점검:
Initializable
,OwnableUpgradeable
등 여러 트레이트를 혼합할 때, 초기화 순서를 올바르게 지정해 재초기화 공격을 방지하세요.프록시 환경에서 테스트: Hardhat이나 Foundry 같은 툴로 프록시 배포를 시뮬레이션하며 저장소 슬롯과 초기화 함수가 제대로 작동하는지 검증하세요.
OpenZeppelin 가이드라인 준수: 최신 패턴은 업그레이드 가능한 컨트랙트 문서에서 확인하세요.
항상 경계를 늦추지 않으면, 더 견고하고 해킹에 강한 밈 토큰을 만들 수 있습니다. 다음 프로젝트에서 솔리디티를 다룰 때 이런 교훈들은 금과도 같습니다. 여러분은 어떤 프록시 함정을 경험하셨나요? 댓글로 공유해 주세요!