2025년 8월 25일, Cantina와 Seal911의 지원을 받아 Panoptic은 화이트햇 구조 작전을 수행하여 위험에 처한 약 $400K 규모의 자금을 확보했습니다 [1]. 근본 원인은 s_positionsHash 구성의 결함으로, 프로토콜이 포지션 ID의 Keccak256 해시를 단일 지문으로 집계하기 위해 XOR을 사용했다는 점입니다. 개별 Keccak256 해시는 충돌 저항성을 유지하지만, XOR의 수학적 선형성으로 인해 복합 지문은 안전하지 않습니다. 공격자는 XOR 집계 해시가 임의의 목표 지문과 일치하는 위조 포지션 ID 집합을 생성할 수 있으며, 이를 통해 프로토콜의 포지션 검증을 우회하여 부채를 상환하지 않고 담보를 인출할 수 있습니다.
배경
Panoptic은 이더리움 위에 구축된 탈중앙화 영구 옵션 거래 프로토콜로, 사용자가 풋 옵션과 콜 옵션을 거래할 수 있도록 합니다.
withdraw() 함수는 tokenId 집합으로 구성된 positionList를 입력 파라미터로 받습니다. 각 tokenId는 하나의 포지션을 나타냅니다. withdraw() 함수는 s_positionsHash를 기반으로 각 포지션의 부채 상태를 확인한 후 사용자의 담보를 회수합니다.
가스를 절약하기 위해 프로토콜은 사용자의 모든 포지션(즉, tokenId)을 저장하지 않습니다. 대신, 사용자의 모든 tokenId를 기반으로 지문을 계산하여 s_positionsHash에 기록합니다. 각 s_positionsHash의 최상위 8비트는 numLegs를 나타내고, 하위 248비트는 사용자 포지션 해시를 나타냅니다.
전달된 각 tokenId에 대해 프로토콜은 해시를 계산하고 하위 248비트를 취한 뒤, 현재 s_positionsHash의 하위 248비트와 비트 단위 XOR 연산을 수행하여 사용자 포지션 해시를 업데이트합니다.
동시에, countLegs는 tokenId의 수치 범위에 따라 조정됩니다: tokenId가 미만일 때는 변화 없이 유지되고, 범위 , , 에 해당할 때 각각 1, 2, 3씩 증가합니다. 이 값은 최종적으로 s_positionsHash의 상위 8비트에 기록되어 numLegs를 업데이트합니다.
취약점 분석
근본 원인은 s_positionsHash를 구성하는 컨트랙트 알고리즘의 결함, 구체적으로는 Keccak256 해시 결과를 집계하기 위해 XOR 연산을 사용한 것에 있습니다. 단일 해시 함수는 여전히 안전하지만, XOR 연산의 수학적 선형성으로 인해 전체 지문 알고리즘(즉, 해시의 XOR 합)은 안전하지 않습니다 [2].
선형성은 공격자가 해시 함수 자체를 깨뜨릴(즉, 해시로부터 tokenId를 역산할) 필요가 없음을 의미합니다. 대신, 조합적 전략을 사용할 수 있습니다: 다수의 무작위 tokenId를 생성하고 Keccak256(tokenId)를 계산한 뒤, 이 해시 값들 중에서 해당 tokenId 해시의 XOR 합이 피해자의 목표 지문과 정확히 일치하는 특정 부분 집합을 선택하는 것입니다.
이를 통해 공격자는 withdraw()를 호출할 때 위조된 tokenId 집합을 전달하여 건강성 검사를 통과하고 s_positionsHash에 해당하는 모든 담보를 회수할 수 있습니다.
이론
사용자의 포지션 지문 s_positionsHash의 사용자 포지션 해시가 이고 numLegs가 이며, 사용자의 tokenId가 이라고 가정합니다. 따라서:
여기서 각 248비트 해시 값은 248차원 벡터로 볼 수 있습니다.
따라서 는 0과 1로 구성된 248차원 공간()에 속합니다. 이 공간에서 XOR 연산은 벡터 덧셈과 동일합니다(부록 I).
공격자의 목표는 사용 가능한 모든 벡터 중에서 XOR 합의 하위 248비트가 와 같아지는 개의 248차원 벡터 를 찾는 것입니다. 따라서 공격자의 목표를 선형 방정식 시스템으로 표현할 수 있습니다:
구체적으로, 를 직접 구성하려 할 필요는 없습니다. 대신 개의 선형 독립적인 해시 벡터 (여기서 은 차원 248과 같음)를 선택하고, 이를 열 벡터로 사용하여 행렬 를 구성합니다:
그러면 문제는 다음을 만족하는 계수 벡터 를 찾는 것으로 변환됩니다:
여기서 이고, 입니다.
선형 대수 이론에 따르면, 행렬 가 완전 계수(full rank)인 한 전체 차원 공간을 생성합니다. 이는 임의의 목표 에 대해 방정식 시스템이 유일한 해를 가짐을 의미합니다. 를 풀고 나면, 인 벡터 만 유지하면 됩니다; 그것들의 XOR 합이 입니다.
예시
이해를 돕기 위해 사례 연구로 이 구성 과정을 시연합니다. 벡터가 3차원이고 각 차원이 0 또는 1로만 구성되며, 목표 해시 값이 101, 즉 라고 가정합니다.
다음으로, 3개의 해시 값을 무작위로 생성합니다:
이 세 벡터를 열로 사용하여 행렬 를 구성하고 방정식 를 설정합니다:
가우스 소거법으로 풀면 를 얻습니다. 즉, 과 를 선택해야 하고(에 해당), 이들의 XOR 합이 목표 와 정확히 일치합니다.
n개의 선형 독립 벡터 선택
앞서 언급한 바와 같이, 를 풀기 위해서는 완전 계수 행렬 를 구성해야 합니다. 매우 큰 벡터 공간()을 다루고 있다는 점을 고려할 때, 핵심 질문은 다음과 같습니다: 이 방대한 공간에서 개의 선형 독립 벡터를 어떻게 빠르게 선택할 수 있을까요?
가장 간단한 방법을 고려해 봅시다: 개의 tokenId를 무작위로 생성하고 그 해시 결과를 벡터로 선택하는 것입니다.
위에서, 무작위로 선택된 개의 차원 벡터가 완전 계수 행렬을 형성할 확률 는:
이 클 때(이 예에서는 ), 이 확률은 상수로 수렴합니다(부록 II):
이는 각 무작위 시도가 약 **28.9%**의 성공 확률을 가짐을 의미합니다. 평균적으로 공격자는 선형 독립 벡터 집합을 찾기 위해 약 3.5번의 시도만 필요합니다. 따라서 계산 비용은 극히 낮으며, 공격자는 조건을 만족하는 행렬 를 빠르게 구성할 수 있습니다.
상위 8비트의 countLegs를 제어하려면, 목표 countLegs에 따라 무작위로 생성되는 tokenId 값의 범위를 조정하기만 하면 됩니다.
예를 들어, 위조된 지문의 numLegs를 0으로 만들고 싶다면, 무작위로 생성되는 개의 tokenId가 모두 보다 작도록 하면 됩니다. 이 범위의 tokenId에 대한 leg 증가분은 0이므로, 가우스 소거 해 가 어떤 벡터를 선택하든 최종 누적 numLegs는 반드시 0이 됩니다.
공격 분석
화이트햇 구조자는 여러 구조 트랜잭션을 시작했습니다. 간결함을 위해 이하 논의는 그 중 하나의 트랜잭션을 기준으로 합니다 [3].
핵심 로직은 다음 5단계로 구성됩니다:
- Aave로부터 플래시 론을 통해 0.23e8 WBTC와 28e18 WETH를 빌립니다.
- 0.23e8 WBTC와 28e18 WETH를 poWBTC 컨트랙트와 0x1f8d_poWETH 컨트랙트에 예치합니다.
- 일반적인
positionIdList로PanopticPool컨트랙트의mintOptions()를 호출하여 자금을 빌리고 레버리지 포지션을 엽니다. - 위조된
tokenId를 전달하여withdraw()를 호출합니다. 이 위조된 포지션들은positionSize가 0이기 때문에 함수는tokenRequired를 0으로 반환하며, 이는 모든 포지션에 필요한 총 담보가 잘못 계산되어 0이 됨을 의미합니다. 한편, 이tokenId들로 생성된s_positionsHash는 3단계에서 생성된 것과 정확히 동일하므로, 구조자는 부채를 상환하지 않고 모든 담보를 회수할 수 있습니다. - 플래시 론을 상환하고 다음 라운드를 실행합니다.
요약
이번 사건은 두 가지 복합적인 결함이 함께 작용하여 무단 담보 인출을 가능하게 했음을 보여줍니다.
- XOR은 해시의 안전한 집계 함수가 아닙니다. Keccak256은 충돌 저항성을 가지지만, XOR의 선형성으로 인해 다수 해시의 XOR 합은 그 특성을 상속하지 않습니다. 해시가 임의의 목표 값으로 XOR되는 입력 집합을 구성하는 것은 위의 선형 방정식 시스템을 푸는 것으로 귀결되며, 이는 계산적으로 trivial합니다. 해시 합성에는 충돌 저항성을 보존하는 연산이 필요하며, 예를 들어 연결(concatenation) 후 재해싱과 같은 방법이 있습니다.
- 포지션 ID 검증 누락. 프로토콜은 전달된
positionId가 유효한 옵션 포지션에 해당하는지 검증하지 않았습니다. 미만의 값은countLegs가 0이고positionSize가 0이므로, 위조된 포지션은 부채를 발생시키지 않습니다. 이로 인해 공격자는 담보 요건이 0인 상태에서 건강성 검사를 통과하면서 목표 지문과 일치시킬 수 있었습니다.
참고문헌
부록
다음 두 부록은 본문의 내용, 즉 XOR 연산이 벡터 덧셈과 동일하다는 것(부록 I)과 무작위 행렬이 완전 계수일 확률(부록 II)에 대한 수학적 설명과 증명을 제공합니다.
부록 I
유한체 에서 덧셈은 모듈로 2 덧셈으로 정의됩니다:
관찰하면 이는 배타적 논리합(XOR, 기호 ) 논리 연산과 동일합니다.
차원 벡터 공간 (이 경우 )에서, 두 벡터 와 의 덧셈은 성분별 모듈로 2 덧셈으로 정의됩니다:
따라서 벡터 공간에서 벡터 덧셈은 벡터 성분에 대한 비트 단위 XOR 연산과 동일합니다.
부록 II
행렬이 완전 계수가 되려면 이 개의 벡터가 선형 독립이어야 합니다.
첫 번째 벡터 은 영벡터를 제외한 의 임의의 벡터가 될 수 있으므로 개의 선택지가 있습니다; 두 번째 벡터 는 이 생성하는 부분 공간(이 공간에는 개의 벡터가 있음)에 속하지 않아야 하므로 개의 선택지가 남습니다; 이 논리에 따라 번째 벡터 는 이전 개의 벡터가 생성하는 부분 공간에 속할 수 없으므로 개의 선택지가 있습니다.
이러한 행렬의 총 수(즉, 의 위수)는:
그리고 가능한 모든 행렬의 총 수는 입니다.
따라서 확률 는:
로 놓으면 이 식을 다음과 같이 다시 쓸 수 있습니다:
일 때, 이 곱은 상수로 수렴합니다:
이는 큰 에 대해 무작위 행렬이 완전 계수일 확률이 약 **28.9%**임을 의미합니다.
BlockSec 소개
BlockSec은 풀스택 블록체인 보안 및 암호화폐 컴플라이언스 제공업체입니다. 저희는 고객이 코드 감사(스마트 컨트랙트, 블록체인 및 지갑 포함)를 수행하고, 실시간으로 공격을 차단하며, 사고를 분석하고, 불법 자금을 추적하며, 프로토콜과 플랫폼의 전체 생명주기에 걸쳐 AML/CFT 의무를 이행할 수 있도록 돕는 제품과 서비스를 구축합니다.
BlockSec은 권위 있는 학술 컨퍼런스에 다수의 블록체인 보안 논문을 발표했으며, DeFi 애플리케이션의 여러 제로데이 공격을 보고했고, 2천만 달러 이상을 구조하기 위해 여러 해킹을 차단했으며, 수십억 달러 규모의 암호화폐를 보호했습니다.
-
공식 웹사이트: https://blocksec.com/
-
공식 트위터 계정: https://twitter.com/BlockSecTeam
-
🔗 BlockSec 감사 서비스 : 요청 제출



