Back to Blog

#10 파놉틱 사건: XOR 선형성이 위치 핑거프린트 체계를 무너뜨리다

Code Auditing
February 13, 2026
7 min read

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 연산을 수행하여 사용자 포지션 해시를 업데이트합니다.

동시에, countLegstokenId의 수치 범위에 따라 조정됩니다: tokenId2642^{64} 미만일 때는 변화 없이 유지되고, 범위 (264,2112)(2^{64}, 2^{112}), (2112,2168)(2^{112}, 2^{168}), (2168,2208)(2^{168}, 2^{208})에 해당할 때 각각 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사용자 포지션 해시TT이고 numLegskk이며, 사용자의 tokenId{t1,t2,,tn}\{t_1, t_2, \dots, t_n\}이라고 가정합니다. 따라서:

i=1k[Hash(ti)(mod2248)]=T\bigoplus_{i=1}^{k} [\text{Hash}(t_i) \pmod{2^{248}}] = T

여기서 각 248비트 해시 값은 248차원 벡터로 볼 수 있습니다.

Hash(ti)(mod2248)=[b0,b1,,b247]T,where bi{0,1}\text{Hash}(t_i) \pmod{2^{248}} = [b_0, b_1, \dots, b_{247}]^T, where\ b_i \in \{0, 1\}

따라서 TT는 0과 1로 구성된 248차원 공간(F2248\mathbb{F}_2^{248})에 속합니다. 이 공간에서 XOR 연산은 벡터 덧셈과 동일합니다(부록 I).

공격자의 목표는 사용 가능한 모든 벡터 중에서 XOR 합의 하위 248비트가 TT와 같아지는 nn개의 248차원 벡터 {v1,v2,,vn}\{v_1, v_2, \dots, v_n\}를 찾는 것입니다. 따라서 공격자의 목표를 선형 방정식 시스템으로 표현할 수 있습니다:

x1v1+x2v2++xnvn=Tx_1 v_1 + x_2 v_2 + \dots + x_n v_n = T

구체적으로, TT를 직접 구성하려 할 필요는 없습니다. 대신 nn개의 선형 독립적인 해시 벡터 {v1,v2,,vn}\{v_1, v_2, \dots, v_n\} (여기서 nn은 차원 248과 같음)를 선택하고, 이를 열 벡터로 사용하여 n×nn \times n 행렬 AA를 구성합니다:

A=[v1,v2,,vn]A = [v_1, v_2, \dots, v_n]

그러면 문제는 다음을 만족하는 계수 벡터 xx를 찾는 것으로 변환됩니다:

Ax=TA \cdot x = T

여기서 x=[x1,x2,,xn]Tx = [x_1, x_2, \dots, x_n]^T이고, xi{0,1}x_i \in \{0, 1\}입니다.

선형 대수 이론에 따르면, 행렬 AA가 완전 계수(full rank)인 한 전체 nn차원 공간을 생성합니다. 이는 임의의 목표 TT에 대해 방정식 시스템이 유일한 해를 가짐을 의미합니다. xx를 풀고 나면, xi=1x_i=1인 벡터 viv_i만 유지하면 됩니다; 그것들의 XOR 합이 TT입니다.

예시

이해를 돕기 위해 사례 연구로 이 구성 과정을 시연합니다. 벡터가 3차원이고 각 차원이 0 또는 1로만 구성되며, 목표 해시 값이 101, 즉 T=[1,0,1]TT = [1, 0, 1]^T라고 가정합니다.

다음으로, 3개의 해시 값을 무작위로 생성합니다:

  • v1=[1,1,0]Tv_1 = [1, 1, 0]^T
  • v2=[0,1,0]Tv_2 = [0, 1, 0]^T
  • v3=[0,1,1]Tv_3 = [0, 1, 1]^T

이 세 벡터를 열로 사용하여 행렬 AA를 구성하고 방정식 Ax=TAx=T를 설정합니다:

[100111001][x1x2x3]=[101]\begin{bmatrix} 1 & 0 & 0 \\ 1 & 1 & 1 \\ 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} x_1 \\ x_2 \\ x_3 \end{bmatrix} = \begin{bmatrix} 1 \\ 0 \\ 1 \end{bmatrix}

가우스 소거법으로 풀면 x=[1,0,1]Tx = [1, 0, 1]^T를 얻습니다. 즉, v1v_1v3v_3를 선택해야 하고(x1=1,x3=1x_1=1, x_3=1에 해당), 이들의 XOR 합이 목표 TT와 정확히 일치합니다.

n개의 선형 독립 벡터 선택

앞서 언급한 바와 같이, Ax=TAx=T를 풀기 위해서는 완전 계수 행렬 AA를 구성해야 합니다. 매우 큰 벡터 공간(m=2248m = 2^{248})을 다루고 있다는 점을 고려할 때, 핵심 질문은 다음과 같습니다: 이 방대한 공간에서 nn개의 선형 독립 벡터를 어떻게 빠르게 선택할 수 있을까요?

가장 간단한 방법을 고려해 봅시다: nn개의 tokenId를 무작위로 생성하고 그 해시 결과를 벡터로 선택하는 것입니다.

F2\mathbb{F}_2 위에서, 무작위로 선택된 nn개의 nn차원 벡터가 완전 계수 행렬을 형성할 확률 PP는:

P(n)=k=0n1(12k2n)P(n) = \prod_{k=0}^{n-1} \left(1 - \frac{2^k}{2^n}\right)

nn이 클 때(이 예에서는 n=248n=248), 이 확률은 상수로 수렴합니다(부록 II):

limnP(n)0.28879\lim_{n \to \infty} P(n) \approx 0.28879

이는 각 무작위 시도가 약 **28.9%**의 성공 확률을 가짐을 의미합니다. 평균적으로 공격자는 선형 독립 벡터 집합을 찾기 위해 약 3.5번의 시도만 필요합니다. 따라서 계산 비용은 극히 낮으며, 공격자는 조건을 만족하는 행렬 AA를 빠르게 구성할 수 있습니다.

상위 8비트의 countLegs를 제어하려면, 목표 countLegs에 따라 무작위로 생성되는 tokenId 값의 범위를 조정하기만 하면 됩니다.

예를 들어, 위조된 지문의 numLegs를 0으로 만들고 싶다면, 무작위로 생성되는 nn개의 tokenId가 모두 2642^{64}보다 작도록 하면 됩니다. 이 범위의 tokenId에 대한 leg 증가분은 0이므로, 가우스 소거 해 xx가 어떤 벡터를 선택하든 최종 누적 numLegs는 반드시 0이 됩니다.

공격 분석

화이트햇 구조자는 여러 구조 트랜잭션을 시작했습니다. 간결함을 위해 이하 논의는 그 중 하나의 트랜잭션을 기준으로 합니다 [3].

핵심 로직은 다음 5단계로 구성됩니다:

  1. Aave로부터 플래시 론을 통해 0.23e8 WBTC와 28e18 WETH를 빌립니다.
  2. 0.23e8 WBTC와 28e18 WETH를 poWBTC 컨트랙트와 0x1f8d_poWETH 컨트랙트에 예치합니다.
  3. 일반적인 positionIdListPanopticPool 컨트랙트의 mintOptions()를 호출하여 자금을 빌리고 레버리지 포지션을 엽니다.
  4. 위조된 tokenId를 전달하여 withdraw()를 호출합니다. 이 위조된 포지션들은 positionSize가 0이기 때문에 함수는 tokenRequired를 0으로 반환하며, 이는 모든 포지션에 필요한 총 담보가 잘못 계산되어 0이 됨을 의미합니다. 한편, 이 tokenId들로 생성된 s_positionsHash는 3단계에서 생성된 것과 정확히 동일하므로, 구조자는 부채를 상환하지 않고 모든 담보를 회수할 수 있습니다.
  5. 플래시 론을 상환하고 다음 라운드를 실행합니다.

요약

이번 사건은 두 가지 복합적인 결함이 함께 작용하여 무단 담보 인출을 가능하게 했음을 보여줍니다.

  • XOR은 해시의 안전한 집계 함수가 아닙니다. Keccak256은 충돌 저항성을 가지지만, XOR의 선형성으로 인해 다수 해시의 XOR 합은 그 특성을 상속하지 않습니다. 해시가 임의의 목표 값으로 XOR되는 입력 집합을 구성하는 것은 F2\mathbb{F}_2 위의 선형 방정식 시스템을 푸는 것으로 귀결되며, 이는 계산적으로 trivial합니다. 해시 합성에는 충돌 저항성을 보존하는 연산이 필요하며, 예를 들어 연결(concatenation) 후 재해싱과 같은 방법이 있습니다.
  • 포지션 ID 검증 누락. 프로토콜은 전달된 positionId가 유효한 옵션 포지션에 해당하는지 검증하지 않았습니다. 2642^{64} 미만의 값은 countLegs가 0이고 positionSize가 0이므로, 위조된 포지션은 부채를 발생시키지 않습니다. 이로 인해 공격자는 담보 요건이 0인 상태에서 건강성 검사를 통과하면서 목표 지문과 일치시킬 수 있었습니다.

참고문헌

  1. https://x.com/Panoptic_xyz/status/1961187739866644524

  2. https://cseweb.ucsd.edu/~mihir/papers/inchash.pdf

  3. https://app.blocksec.com/explorer/tx/eth/0x67a45dfe5ff4b190058674d7c791bbdc48e889f319f937c24fa13a5f9093f088

부록

다음 두 부록은 본문의 내용, 즉 XOR 연산이 벡터 덧셈과 동일하다는 것(부록 I)과 무작위 행렬이 완전 계수일 확률(부록 II)에 대한 수학적 설명과 증명을 제공합니다.

부록 I

유한체 F2={0,1}\mathbb{F}_2 = \{0, 1\}에서 덧셈은 모듈로 2 덧셈으로 정의됩니다:

0+0=00+1=11+0=11+1=0(mod2)\begin{aligned} 0 + 0 &= 0 \\ 0 + 1 &= 1 \\ 1 + 0 &= 1 \\ 1 + 1 &= 0 \pmod 2 \end{aligned}

관찰하면 이는 배타적 논리합(XOR, 기호 \oplus) 논리 연산과 동일합니다.

nn차원 벡터 공간 F2n\mathbb{F}_2^n (이 경우 n=248n=248)에서, 두 벡터 u=[u1,,un]Tu = [u_1, \dots, u_n]^Tv=[v1,,vn]Tv = [v_1, \dots, v_n]^T의 덧셈은 성분별 모듈로 2 덧셈으로 정의됩니다:

u+v=[u1+v1(mod2)un+vn(mod2)]=[u1v1unvn]=uvu + v = \begin{bmatrix} u_1 + v_1 \pmod 2 \\ \vdots \\ u_n + v_n \pmod 2 \end{bmatrix} = \begin{bmatrix} u_1 \oplus v_1 \\ \vdots \\ u_n \oplus v_n \end{bmatrix} = u \oplus v

따라서 F2n\mathbb{F}_2^{n} 벡터 공간에서 벡터 덧셈은 벡터 성분에 대한 비트 단위 XOR 연산과 동일합니다.

부록 II

행렬이 완전 계수가 되려면 이 nn개의 벡터가 선형 독립이어야 합니다.

첫 번째 벡터 v1v_1은 영벡터를 제외한 F2n\mathbb{F}_2^n의 임의의 벡터가 될 수 있으므로 2n12^n - 1개의 선택지가 있습니다; 두 번째 벡터 v2v_2{v1}\{v_1\}이 생성하는 부분 공간(이 공간에는 212^1개의 벡터가 있음)에 속하지 않아야 하므로 2n22^n - 2개의 선택지가 남습니다; 이 논리에 따라 kk번째 벡터 vkv_k는 이전 k1k-1개의 벡터가 생성하는 부분 공간에 속할 수 없으므로 2n2k12^n - 2^{k-1}개의 선택지가 있습니다.

이러한 행렬의 총 수(즉, GL(n,F2)GL(n, \mathbb{F}_2)의 위수)는:

N=k=0n1(2n2k)=(2n1)(2n2)(2n4)(2n2n1)N = \prod_{k=0}^{n-1} (2^n - 2^k) = (2^n - 1)(2^n - 2)(2^n - 4)\cdots(2^n - 2^{n-1})

그리고 가능한 모든 n×nn \times n 행렬의 총 수는 (2n)n=2n2(2^n)^n = 2^{n^2}입니다.

따라서 확률 PP는:

P=k=0n1(2n2k)2n2=k=0n1(2n2k2n)=k=0n1(12k2n)P = \frac{\prod_{k=0}^{n-1} (2^n - 2^k)}{2^{n^2}} = \prod_{k=0}^{n-1} \left( \frac{2^n - 2^k}{2^n} \right) = \prod_{k=0}^{n-1} (1 - \frac{2^k}{2^n})

j=nkj = n - k로 놓으면 이 식을 다음과 같이 다시 쓸 수 있습니다:

P=j=1n(112j)P = \prod_{j=1}^{n} \left(1 - \frac{1}{2^j}\right)

nn \to \infty일 때, 이 곱은 상수로 수렴합니다:

P0.288788P \approx 0.288788

이는 큰 nn에 대해 무작위 행렬이 완전 계수일 확률이 약 **28.9%**임을 의미합니다.


BlockSec 소개

BlockSec은 풀스택 블록체인 보안 및 암호화폐 컴플라이언스 제공업체입니다. 저희는 고객이 코드 감사(스마트 컨트랙트, 블록체인 및 지갑 포함)를 수행하고, 실시간으로 공격을 차단하며, 사고를 분석하고, 불법 자금을 추적하며, 프로토콜과 플랫폼의 전체 생명주기에 걸쳐 AML/CFT 의무를 이행할 수 있도록 돕는 제품과 서비스를 구축합니다.

BlockSec은 권위 있는 학술 컨퍼런스에 다수의 블록체인 보안 논문을 발표했으며, DeFi 애플리케이션의 여러 제로데이 공격을 보고했고, 2천만 달러 이상을 구조하기 위해 여러 해킹을 차단했으며, 수십억 달러 규모의 암호화폐를 보호했습니다.

Sign up for the latest updates
~$410만 손실: Taiko, SecondFi 익스플로잇 | BlockSec 위클리
Security Insights

~$410만 손실: Taiko, SecondFi 익스플로잇 | BlockSec 위클리

이 주간 블록체인 보안 리포트는 2026년 6월 22~28일 발생한 주요 사건 2건을 다루며, 이더리움과 카르다노에서 약 410만 달러의 피해가 확인됐습니다. Taiko 브릿지 공격은 노출된 SGX 서명 키와 디버그 엔클레이브를 거부하지 못한 증명 정책 결함을 이용해 악성 증명자를 등록하고 L2 상태 증명을 위조했습니다. SecondFi 지갑은 Ed25519 논스 도출 시 비밀 입력이 제거되는 결함으로 공개 트랜잭션 데이터만으로 개인 키 복구가 가능했습니다.

~$18M 손실: jaredFromSubway, Aztec 등 | BlockSec 위클리
Security Insights

~$18M 손실: jaredFromSubway, Aztec 등 | BlockSec 위클리

이 주간 블록체인 보안 보고서는 2026년 6월 15일~21일을 다루며, 이더리움과 BNB 체인에서 3건의 주요 사고가 발생해 약 $18.3M의 손실이 발생했습니다. jaredFromSubway 사건은 MEV 봇이 차익거래를 위해 신뢰할 수 없는 제3자 컨트랙트에 자산을 승인한 역방향 승인 공격으로, 가짜 래퍼 토큰과 스왑 풀을 이용해 약 $15M 손실이 발생했습니다. Aztec은 이스케이프 해치 ZK 회로의 제약 누락으로 공격자가 가짜 머클 트리로 온체인 검증을 통과했습니다.

Web3 컴패니언: 오픈소스 보안 에이전틱 지갑

Web3 컴패니언: 오픈소스 보안 에이전틱 지갑

BlockSec가 Web3 Companion을 오픈소스로 공개했습니다. 이 보안 중심의 에이전트 지갑은 자체 AI 에이전트를 신뢰하지 않는 방식으로 설계되었으며, 키 격리, 강력한 정책, Passkey를 활용해 온체인 자산을 보호합니다.

Best Security Auditor for Web3

Validate design, code, and business logic before launch. Aligned with the highest industry security standards.

BlockSec Audit