Back to Blog

#5 Yearn Finance 사건: Invariant Solver의 안전하지 않은 산술 연산이 그 이름값을 하다

Code Auditing
February 11, 2026
20 min read

2025년 11월 30일, Yearn Finance의 yETH Weighted Stable Pool이 공격을 받아 9백만 달러 이상의 손실이 발생했습니다 [1]. 근본 원인은 불변식 솔버 _calc_supply()의 안전하지 않은 산술 연산과 초기화 로직으로의 재진입을 허용한 비활성화되지 않은 부트스트랩 경로였습니다. 공식 사후 분석 보고서 [2]는 다섯 가지 항목을 근본 원인으로 나열하고 있으나, 저희는 이를 두 가지 결함(위의 취약점들)과 이러한 결함이 존재할 때만 악용 가능해지는 두 가지 아키텍처적 전제조건으로 재분류합니다. 다른 공개된 분석들은 단계별 공격 트랜잭션 세부 사항에 초점을 맞추고 있습니다. 상위 수준의 요약과 트랜잭션 수준의 세부 사항 사이에는 여전히 간극이 남아 있습니다: 공격이 실제로 왜, 그리고 어떻게 작동했는가? 본 게시물은 Foundry와 Python 시뮬레이션을 사용하여 핵심 값들이 단계별로 어떻게 변화하는지, 그리고 계산이 어디서 무너지는지를 추적함으로써 그 간극을 메웁니다.

본 분석은 주로 다음 세 가지 기여를 합니다:

  1. 취약점별 손실 분석. 두 취약점은 상호 종속적이지 않습니다: 안전하지 않은 산술 연산만으로도 약 810만 달러(전체의 90%)의 손실이 발생했으며, 부트스트랩 경로는 추가로 약 90만 달러의 손실을 가능하게 했습니다. 이는 어느 취약점이 주된 원인이었는지를 명확히 합니다.
  2. 근본 원인의 재분류. 공식 보고서의 다섯 가지 근본 원인은 두 가지 구현 결함(다섯 항목 중 세 항목을 통합)과, 그 결함들과 결합될 때만 악용 가능해지는 두 가지 아키텍처적 전제조건으로 이해하는 것이 더 적절합니다.
  3. 기술적 오해의 정정. "두 번째 반복(iteration)에서의 언더플로우가 곱셈항을 0으로 만든다"는 주장은 성립하지 않습니다: 저희의 시뮬레이션은 곱셈항이 언더플로우가 아니라 나눗셈에서의 반올림(내림)을 통해 0이 됨을 보여주며, 이익을 발생시키는 언더플로우는 전혀 다른 단계에서 발생합니다.

이 게시물의 나머지 부분은 다음과 같이 구성되어 있습니다. 0x1절에서는 yETH의 weighted stable pool과 그 불변식 솔버에 대한 배경 지식을 제공합니다. 0x2절에서는 두 가지 근본 원인과 그 실패 모드를 분석합니다. 0x3절에서는 3단계로 이루어진 공격을 자세히 추적합니다. 0x4절에서는 시뮬레이션 증거를 통해 두 가지 흔한 오해를 정정합니다. 0x5절에서는 권장 사항으로 마무리합니다.

TL;DR

근본 원인: 두 가지 취약점이 악용되었지만, 그 영향은 비대칭적이었습니다:

  1. _calc_supply()의 안전하지 않은 산술 연산 (주 원인, 약 $8.1M). 풀 상태로부터 yETH 공급량을 재계산하는 함수에는 두 가지 산술 오류가 존재합니다: unsafe_div()에서의 내림 반올림이 내부 곱셈항을 0으로 만들 수 있으며, unsafe_sub()에서의 언더플로우가 중간값을 거대한 양의 정수로 랩어라운드시킬 수 있습니다. 이 취약점만으로도 yETH weighted stableswap 풀을 고갈시키기에 충분했습니다.
  2. 비활성화되지 않은 부트스트랩 경로 (부차 원인, 약 $0.9M). prev_supply == 0 초기화 분기는 배포 이후 영구적으로 차단된 적이 없었습니다. 첫 번째 취약점이 공급량을 0으로 고갈시킨 후, 이 경로에 다시 도달할 수 있게 되어 yETH/WETH Curve 풀로부터 추가 이익을 얻을 수 있었습니다.

안전하지 않은 산술 취약점 내에서, Phase 2에서는 내림 반올림 실패(Failure Mode A)만이 사용되었습니다. 언더플로우 실패(Failure Mode B)는 부트스트랩 경로와 상호 종속적이며, 이 둘이 함께 Phase 3을 가능하게 했습니다.

공격자는 다음 3단계 시퀀스를 실행했습니다:

  1. 준비 단계: 반복적인 add/remove 사이클을 통해 풀의 자산 분포를 왜곡시켜, 가상 잔액(virtual balance)에 극단적인 불균형을 만듭니다.
  2. 공급량 조작: _calc_supply()의 내림 반올림을 악용하여 곱셈항을 0으로 붕괴시킨 다음, 일련의 mint/burn 작업을 통해 총 공급량을 0으로 고갈시킵니다. 이후 풀의 모든 LST가 인출되어 WETH로 스왑되었으며, 이로 인해 약 $8.1M의 손실이 발생했습니다.
  3. 이익 추출: 더스트(dust) 예치금으로 부트스트랩 경로(prev_supply == 0)를 트리거하여, _calc_supply()의 언더플로우를 악용해 약 2.35×10⁵⁶ yETH를 발행합니다. 이는 yETH/WETH Curve 풀을 고갈시키는 데 사용되어 약 $0.9M의 손실을 발생시켰습니다.

정정된 두 가지 흔한 오해:

  • "pow_up()pow_down()이 서로 다르게 반올림하기 때문에 불변식이 깨진다." 저희는 Foundry 시뮬레이션에서 pow_up()pow_down()으로 교체하여 이를 검증했습니다: 익스플로잇은 여전히 작동합니다. 반올림 방식의 불일치는 근본 원인이 아닙니다.
  • "두 번째 반복에서의 언더플로우가 중간항을 0으로 붕괴시킨다." 저희의 Foundry 및 Python 시뮬레이션은 두 번째 반복에서 언더플로우가 발생하지 않음을 보여줍니다. 실제 값은 (주장된 ~1.94e18이 아니라) ~1.91e19이며, 이는 정상적인 뺄셈의 합법적인 결과입니다. 곱셈항을 0으로 만드는 것은 이후 나눗셈에서의 내림 반올림이지, 언더플로우가 아닙니다.

0x1 배경

이 사건에서 두 개의 풀이 자산을 잃었습니다: yETH weighted stableswap 풀 (LST를 보유한 Yearn 풀, 약 $8.1M 손실)과 yETH/WETH Curve 풀 (Curve stableswap 풀, 약 $0.9M 손실). 핵심 취약점이 존재하는 곳은 yETH weighted stableswap 풀입니다. 이 절에서는 취약점과 익스플로잇을 이해하는 데 필요한 배경 지식을 제공합니다.

0x1.1 가상 잔액(Virtual Balances)과 불변식(Invariant)

yETH 프로토콜은 이더리움 유동성 스테이킹 토큰(LST)을 위한 자동화된 마켓 메이커(AMM)입니다 [3]. 영향을 받은 yETH weighted stableswap 풀은 여러 LST를 하나의 풀로 통합합니다: 사용자는 LST를 예치하고 풀 지분 토큰으로 yETH를 받습니다.

각 LST는 시간이 지남에 따라 보상이 누적되는 스테이킹된 ETH를 나타내므로, 기본 ETH 대비 그 교환 비율이 변화합니다. 회계를 통일하기 위해 풀은 각 자산에 대해 가상 잔액(virtual balance) xix_i를 정의합니다: 온체인 잔액 × 교환 비율. 이는 모든 자산을 비콘 체인 ETH 단위로 정규화합니다. 모든 가상 잔액의 합은 σ=xi\sigma = \sum x_i로 표기됩니다.

이 풀은 8개의 자산(인덱스 0–7)을 포함하며, 각각 지정된 가중치(weight) wiw_i를 가집니다:

Index Asset Index Asset
0 sfrxETH 4 rETH
1 wstETH 5 apxETH
2 ETHx 6 WOETH
3 cbETH 7 mETH

풀의 상태는 weighted StableSwap 스타일의 불변식 [4]에 의해 관리됩니다:

Afn  σ+D=Afn  D+Dπ(1)\mathit{Af}^{\,n}\;\sigma + D = \mathit{Af}^{\,n}\;D + D \cdot \pi \tag{1}

여기서:

  • **DD**는 불변식 스케일로, 이 풀의 총 yETH 공급량과 직접적으로 동일합니다. 풀이 완벽하게 균형을 이룬 상태일 때, D=σD = \sigma가 됩니다.
  • **π\pi**는 **가중 곱셈항(weighted product term)**으로, π=Dni(wixi)vi\pi = D^n \prod_{i} \left(\frac{w_i}{x_i}\right)^{v_i}로 정의되며, 여기서 wiw_i는 자산 i의 가중치이고 vi=winv_i = w_i \cdot n입니다.
  • **Af\mathit{Af}**는 **증폭 계수(amplification factor)**로, 단일 프로토콜 매개변수입니다 (A×fA \times f가 아닙니다). Afn\mathit{Af}^{\,n}은 이 계수를 nn 제곱한 것을 의미하며, 여기서 nn은 자산의 개수(이 풀에서는 8개)입니다. 이는 상수합(균형 근처)과 상수곱(극단값)의 곡선 형태를 제어합니다.

핵심 속성은: DD가 닫힌 형태의 해를 가지지 않는다는 것입니다. 이는 수치적으로 풀어야만 합니다. 그 솔버인 _calc_supply()가 바로 산술적 취약점이 존재하는 곳입니다.

0x1.2 불변식 솔버

프로토콜은 256회로 제한된 고정점 반복(fixed-point iteration)을 통해 DD를 재계산합니다. 이 알고리즘은 코드 내에서 _calc_supply()로 구현되어 있습니다 (0x2.1절에서 자세히 다룸). 각 라운드는 세 단계를 수행합니다:

1단계: 공급 추정치를 업데이트합니다.

Dm+1=AfnσDmπmAfn1(2)D_{m+1} = \frac{\mathit{Af}^{\,n} \cdot \sigma - D_m \cdot \pi_m}{\mathit{Af}^{\,n} - 1} \tag{2}

2단계: 새로운 공급량에 맞춰 곱셈항을 업데이트합니다.

πm+1=πm(Dm+1Dm)n(3)\pi_{m+1} = \pi_m \cdot \left(\frac{D_{m+1}}{D_m}\right)^n \tag{3}

3단계: 수렴 여부를 확인합니다.

만약 Dm+1Dm<ϵ|D_{m+1} - D_{m}| < \epsilon이면 DmD_{m}을 반환하고, 그렇지 않으면 1단계부터 반복합니다.

초기값 D0D_0, π0\pi_0, σ\sigma는 초기 반복에 영향을 미칩니다; 이론적으로는 최종 수렴과 무관하지만, 유한한 반복 횟수와 고정 정밀도 산술로 인해 실제로는 결과에 영향을 미칩니다.

이 구현은 고정 정밀도 정수 연산을 사용합니다: 나눗셈은 내림 반올림되며, 뺄셈은 언더플로우를 방지하지 않습니다. 정상적인 풀 조건에서는 중간값들이 안전한 범위 내에 머무릅니다. 극단적인 풀 상태에서는 그렇지 않습니다. 0x2.1절에서는 이러한 실패 모드를 자세히 분석합니다.

0x1.3 세 가지 인터페이스와 불변식 솔버

프로토콜은 가중 곱셈항 π\pi(코드 내에서 vb_prod로 저장)를 업데이트함으로써 풀 상태에 영향을 미치는 세 가지 진입점을 노출합니다:

Interface What it does Triggers _calc_supply()?
add_liquidity() 임의의 비율로 자산을 예치함 Yes
update_rates() 외부 교환 비율을 업데이트함 Yes
remove_liquidity() 가중치에 비례하여 자산을 인출함 No (비례 스케일링 사용)

이 비대칭성이 중요합니다: add_liquidity()임의 비율의 예치를 허용하는 반면(풀을 대규모로 왜곡시킬 수 있음), remove_liquidity()는 항상 비례적으로 인출합니다. 따라서 add/remove의 반복적인 사이클은 풀을 점점 더 불균형한 상태로 몰아갈 수 있습니다.

비율(Rate)을 업데이트하는 메커니즘

위에서 논의한 바와 같이, 가상 잔액(xix_i)은 LST의 교환 비율을 기반으로 계산됩니다. 따라서 비율을 업데이트하는 방식을 이해하는 것이 중요합니다.

구체적으로, add_liquidity()update_rates() 함수는 내부 함수 _update_rates()를 통해 비율을 업데이트할 수 있는 반면, remove_liquidity() 함수는 비율 동기화를 수행하지 않습니다.

  • add_liquidity()는 자산 교환 비율이 최신 상태로 동기화되도록 보장하기 위해 중요한 작업을 실행하기 전에 _update_rates()를 호출합니다.
  • update_rates()는 수동 비율 업데이트를 허용합니다.

_update_rates() 함수는 계약 내에 기록된 교환 비율이 외부 비율과 일치하는지 확인합니다. 불일치가 감지되면 가상 잔액의 재계산을 트리거하고 이어서 불변식을 업데이트합니다; 그렇지 않으면 업데이트 과정은 건너뜁니다.

각 인터페이스가 π를 처리하는 방식

이들이 불변식에 미치는 영향에 따라, 이 세 함수는 두 범주로 분류될 수 있습니다. 구체적으로, add_liquidity()update_rates()는 가상 잔액의 비비례적 변화를 허용하므로, 공급량 DD와 곱셈항 π\pi의 반복적 재계산이 필요합니다. 반면 remove_liquidity()는 유동성을 비례적으로 인출하므로 반복 계산이 필요하지 않습니다.

곱셈항을 처음부터 계산하는 기본 공식은 다음과 같습니다:

π=i(Dwixi)nwi(4)\pi = \prod_{i} \left(\frac{D \cdot w_i}{x_i}\right)^{n \cdot w_i} \tag{4}

여기서 DD는 공급량, wiw_i는 자산 ii의 가중치, xix_i는 그 가상 잔액(코드 내에서 vb[i]로 저장)이며, n은 자산의 개수입니다. 이 형태는 0x1.1절의 정의와 대수적으로 동등하며, DnD^n이 곱셈항 안으로 분배된 형태입니다.

  1. **add_liquidity()**에는 두 가지 경로가 있습니다 (코드는 0x2.2절에 표시됨):
  • 부트스트랩 경로 (prev_supply == 0인 경우): 식 (4)를 사용하여 vb_prod를 처음부터 계산합니다. 이 경로가 배포 이후에도 계속 접근 가능하다는 점이 0x2.2절에서 논의하는 상태 관리 취약점입니다.
  • 정상 경로 (prev_supply > 0인 경우): 계산 과정은 두 단계로 나뉩니다:
    • a) 이전 가상 잔액과 새로운 가상 잔액의 비율을 기반으로 한 증분 업데이트를 사용합니다:

      πestimated=πi=0n1(xixi)win(5)\pi_{\text{estimated}} = \pi \cdot \prod_{i=0}^{n-1} \left(\frac{x_i}{x_i'}\right)^{w_i \cdot n} \tag{5}

      여기서 xix_ixix_i'는 예치 전후의 가상 잔액입니다.

    • b) 이 추정치를 입력으로 하여 _calc_supply()를 호출함으로써 정확한 값을 반복적으로 보정하고, 불변식 DDπ\pi의 정확한 값을 재계산합니다.

  1. **update_rates()**는 교환 비율이 변경될 때 트리거되며, 이에 따라 해당 자산의 가상 잔액이 업데이트됩니다. 이후의 계산 흐름은 add_liquidity()의 정상 경로를 따릅니다. 즉, 불변식이 반복적으로 재계산됩니다. 또한 새로 계산된 공급량을 기반으로, 계약은 유동성 공급량이 업데이트된 가상 잔액 상태와 일치하도록 yETH를 mint하거나 burn합니다.

  2. **remove_liquidity()**는 각 가상 잔액을 비례적으로 줄인 후, 항상 식 (4)를 사용하여 vb_prod를 처음부터 계산합니다.


0x2 근본 원인 분석

두 취약점이 악용되었으며, 서로 다른 역할과 영향을 미쳤습니다. 주요 근본 원인은 불변식 솔버 _calc_supply()의 계산 결함이었으며, 여기에는 두 가지 실패 모드가 있었습니다: (A) 내림 반올림이 곱셈항을 0으로 만들 수 있어, 불변식을 상수합 모델로 퇴화시키고 초과 LP 발행(공급량 인플레이션)을 초래할 수 있었고; (B) 언더플로우 조건 또한 공급량 인플레이션을 초래할 수 있었습니다. Failure Mode A만이 Phase 2(~$8.1M)에서 사용되었습니다. Failure Mode B는 부차적 취약점과 상호 종속적이었습니다.

부차적 근본 원인은 상태 관리 결함이었습니다: 풀의 초기화 분기가 계속 도달 가능한 상태로 남아 있었습니다. Phase 2가 공급량을 0으로 몰아넣은 후, Failure Mode B가 부트스트랩 경로와 결합되어 추가로 약 $0.9M의 손실(Phase 3)을 가능하게 했습니다.

0x2.1 _calc_supply()의 안전하지 않은 산술 연산 (주요 원인)

그림 2는 _calc_supply() 구현을 0x1.2절의 수학적 절차에 매핑하며, 아래에서 분석하는 두 가지 산술 실패 지점을 주석으로 표시합니다:

코드 변수는 수학적 항에 다음과 같이 매핑됩니다:

Code variable Mathematical role
s 현재 공급량 추정치 DmD_m
r 곱셈항 πm\pi_m
sp 다음 공급량 추정치 Dm+1D_{m+1}
l 분자 상수: Afnσ\mathit{Af}^{\,n} \cdot \sigma
d 분모 상수: Afn1\mathit{Af}^{\,n} - 1

핵심 표현식은 다음과 같습니다:

sp = unsafe_div(unsafe_sub(l, unsafe_mul(s, r)), d)   # Step 1: D[m+1]
r  = unsafe_div(unsafe_mul(r, sp), s)                 # Step 2: π update (per asset)

이 함수 내부에는 서로 다른 라인을 대상으로 하고 서로 다른 영향을 만들어내는 두 가지 산술 실패 모드가 존재합니다. 둘 다 트리거되려면 풀이 극단적인 상태에 있어야 합니다.

정상적인 조건에서는 반복이 올바르게 작동합니다: l - s * r은 적당한 양의 값이며, 반복은 몇 라운드 만에 수렴합니다.

1. Failure Mode A: 내림 반올림이 곱셈항을 0으로 만듦

2단계에서, 곱셈항은 자산별로 다음과 같이 업데이트됩니다:

r = unsafe_div(unsafe_mul(r, sp), s)   # r = r * sp / s

unsafe_div()는 정수 나눗셈을 수행하므로 항상 내림 반올림됩니다. 풀이 심각하게 불균형하고 sps보다 훨씬 작을 때(조작된 대규모 예치 이후 발생하는 경우), 분자 r * sp는 분모 s보다 작아질 수 있습니다. 그러면 정수 나눗셈은 **r = 0**을 산출합니다.

r이 한 번 0이 되면, 이후의 모든 반복에서 0으로 유지됩니다. 곱셈항 π\pi가 영구적으로 붕괴된 것입니다.

흔한 오해 중 하나는 이 실패가 pow_up()pow_down() 사이의 반올림 불일치에서 비롯된다고 주장합니다. 0x4절은 이것이 잘못되었다는 증거를 제시합니다.

2. Failure Mode B: 언더플로우가 공급량을 인플레이션시킴

1단계에서, 새로운 공급량 추정치는 다음과 같이 계산됩니다:

sp = unsafe_div(unsafe_sub(l, unsafe_mul(s, r)), d)   # sp = (l - s*r) / d

식 2에서의 뺄셈 l - s*rAfnσDmπm\mathit{Af}^{\,n} \cdot \sigma - D_m \cdot \pi_m입니다. 정상 조건에서는 이 값이 양수입니다. 그러나 풀이 공급량 0인 퇴화 상태에 도달하면, add_liquidity()의 초기화 분기(0x2.2절에서 자세히 설명)가 곱셈항을 처음부터 재계산하며, 이때 상대적인 크기가 역전될 수 있습니다.

구체적으로, 공급량이 0인 풀에 대해 더스트 금액으로 add_liquidity()가 호출되면, 초기화 분기는 _calc_vb_prod_sum()을 호출하여 식 (4)(0x1.3절)를 사용해 새로운 값들을 계산합니다. 미세한 예치금으로 인해, vb_sum은 극히 작지만(예: 16), 거의 0에 가까운 잔액으로 나누고 높은 거듭제곱을 취함으로써 곱셈항이 불균형하게 큰 값(예: ~9.13e20)으로 증폭됩니다. s * rl을 초과하면, 뺄셈은 음수인 수학적 결과를 산출합니다.

unsafe_sub()검사되지 않은 uint256 산술로 뺄셈을 수행하므로, 음수 결과는 랩어라운드되어 (22562^{256}에 가까운) 거대한 양의 정수가 됩니다. 이 랩어라운드된 값은 나눗셈과 이후의 반복들을 거쳐 전파되며, 터무니없이 큰 공급량 추정치를 산출하고, 프로토콜은 이를 실제 yETH 토큰으로 발행합니다.

흔한 주장은 이러한 언더플로우가 특정 공급량 조작 단계의 두 번째 반복에서 발생한다고 주장합니다. 0x4절은 이 주장이 잘못되었음을 보여줍니다: 공급량을 인플레이션시키는 실제 언더플로우는 완전히 다른 맥락(공격의 Phase 3)에서 발생합니다.

3. 이러한 실패들이 공격을 가능하게 하는 방식

이 두 실패 모드는 익스플로잇의 서로 다른 단계에서 작동하며, 서로 다른 이익 기여를 합니다:

  • Failure Mode A (Phase 2, ~$8.1M): 공격자가 심각하게 불균형한 풀에 예치할 때, 곱셈항이 0이 되어 _calc_supply()가 인플레이션된 공급량을 반환하게 합니다. 프로토콜은 공격자에게 yETH를 초과 발행합니다. 부트스트랩 경로의 관여 없이 이 실패 모드만으로도 공격자는 yETH weighted stableswap 풀의 LST 자산을 고갈시킬 수 있었습니다.

  • Failure Mode B (Phase 3, ~$0.9M): 공급량이 0으로 고갈된 이후, 부트스트랩 경로가 더스트 예치금으로부터 큰 곱셈항을 재계산하여 뺄셈이 언더플로우되게 합니다. 프로토콜은 천문학적으로 큰 양의 yETH를 발행하며, 공격자는 이를 이용해 별도의 yETH/WETH Curve 풀을 고갈시킵니다.

이 의존성은 단방향입니다: Failure Mode A는 독립적으로 악용 가능하며 손실의 90%를 유발한 반면, Failure Mode B는 Failure Mode A가 먼저 공급량을 0으로 몰아넣는 것을 필요로 합니다.

0x2.2 비활성화되지 않은 부트스트랩 경로 (부차 원인)

add_liquidity() 함수는 풀의 최초 예치를 위한 분기를 포함합니다:

그 로직은 다음과 같이 추상화될 수 있습니다:

if prev_supply == 0:
    # Bootstrap path — compute vb_prod and vb_sum from scratch
    vb_prod, vb_sum = _calc_vb_prod_sum(balances, rates, weights, ...)
    supply = vb_sum
else:
    # Normal path — use stored vb_prod, perform incremental checks
    ...

# Called after both branches, with prev_supply == 0 as a flag
supply, vb_prod = _calc_supply(num_assets, supply, amplification, vb_prod, vb_sum, prev_supply == 0)

prev_supply == 0일 때, 함수는 저장된 상태를 우회하고 _calc_vb_prod_sum()을 통해 식 (4)(0x1.3절)를 사용하여 vb_prodvb_sum을 처음부터 재계산합니다. 이 부트스트랩 분기는 풀 초기화 시 일회성 사용을 의도한 것이었지만, 첫 예치 이후 영구적으로 차단된 적이 없었습니다.

만약 (burn과 인출의 어떤 조합을 통해서든) 총 공급량이 0으로 몰릴 수 있다면, 이 분기는 다시 도달 가능해집니다. 이 경로에 다시 진입하는 공격자는 _calc_supply()에 전달되는 초기 조건을 제어하게 되며, 이는 정상적인 풀 운영 중에는 결코 발생하지 않을 매개변수 하에서 위에서 설명한 산술 실패를 트리거할 수 있습니다.

이는 알려진 취약점 패턴입니다. 2023년 8월, Balancer V2 사건도 유사하게 내부 비율을 재설정하기 위해 공급량을 0으로 몰아넣는 것에 의존했으며, 이를 통해 공격자는 인위적으로 유리한 매개변수로 초기화 로직에 재진입할 수 있었습니다 [6]. 배포된 풀이 초기 상태로 되돌려질 수 있는지, 그리고 그렇게 되었을 때 어떤 불변식이 유지되는지는 프로토콜 설계자가 명시적으로 다뤄야 할 문제입니다.


0x3 공격 분석

이 익스플로잇은 공격 트랜잭션 [5]의 조율된 시퀀스에 걸쳐 진행되며, 3단계로 구성됩니다. 각 단계는 이전 단계에서 구축된 상태 위에 만들어집니다.

0x3.1 Phase 1: 풀 왜곡시키기 (준비)

목표: 자산 전반에 걸친 가상 잔액에 극단적인 불균형을 만듭니다.

아래 그림은 이 단계의 트랜잭션 트레이스를 보여줍니다 (플래시론 단계는 지면 제약으로 생략됨):

공격자는 먼저 Balancer와 Aave로부터 플래시론을 통해 대량의 LST 자산을 빌립니다. 구체적으로 5,500e18 wstETH, 3,100e18 WETH, 1,800e18 rETH, 2,000e18 ETHx, 200e18 cbETH입니다.

다음으로, 공격자는 yETH/WETH Curve 풀에서 약 800e18 WETH를 약 416e18 yETH로 스왑한 후, 획득한 yETH를 사용하여 풀에서 유동성을 인출합니다.

핵심 조작은 0x1절(배경)에서 설명한 인터페이스 비대칭성을 활용합니다: add_liquidity()임의 비율의 예치를 허용하는 반면, remove_liquidity()는 풀 가중치에 따라 비례적으로 자산을 인출합니다(위 그림에서 붉은 사각형으로 강조 표시됨). 선택된 자산만 예치하면서 모든 자산을 비례적으로 인출하는 add → remove 작업을 반복적으로 순환함으로써, 공격자는 풀을 점진적으로 심각한 불균형 상태로 몰아갑니다:

Asset Weight Before After Change
0 (sfrxETH) 20% 628,097,482,908,289,585,170 684,908,495,923,316,419,717 +9.04%
1 (wstETH) 20% 376,569,216,105,249,117,091 684,906,088,027,654,432,883 +81.88%
2 (ETHx) 10% 187,473,530,249,048,974,586 410,441,661,092,336,995,160 +118.93%
3 (cbETH) 10% 267,387,722,745,796,900,349 3,532,430,695,689,175,233 -98.68%
4 (rETH) 10% 201,828,029,369,446,137,136 410,441,659,865,060,509,563 +103.36%
5 (apxETH) 25% 753,792,636,209,697,936,333 549,134,446,963,315,842,411 -27.15%
6 (WOETH) 2.5% 49,640,000,870,620,479,267 655,788,758,768,556,847 -98.68%
7 (mETH) 2.5% 47,667,894,211,903,277,629 629,735,467,970,876,930 -98.68%

자산 3(cbETH), 6(WOETH), 7(mETH)은 98% 이상 고갈되었습니다. 이러한 불균형이 직접적으로 이익을 추출하지는 않습니다. 이는 다음 단계를 위한 수치적 전제조건을 만듭니다.

0x3.2 Phase 2: 공급량을 0으로 붕괴시키기 (~$8.1M)

목표: 불변식 곱셈항을 0으로 만든 다음, yETH 공급량을 0으로 고갈시킵니다. 이 단계는 주요 취약점(안전하지 않은 산술 연산)만을 악용하며, 전체 손실의 약 90%를 유발했습니다.

이 단계는 반복되는 5단계 사이클을 사용하며, 3회 실행됩니다:

  1. add_liquidity()를 통해 곱셈항을 손상시킴;
  2. add_liquidity()를 통해 보정을 위한 전제조건을 확립함;
  3. 0 yETH로 remove_liquidity()를 호출하여 곱셈항을 재설정함;
  4. update_rates()를 통해 공급량을 보정함;
  5. remove_liquidity()를 통해 자산을 인출함.

아래 그림은 트랜잭션 트레이스를 보여주며, 5단계 사이클의 3회 반복이 명확하게 보입니다:

1. add_liquidity()를 통해 곱셈항을 손상시킴

공격자는 각각 현재 가상 잔액의 약 3배에 해당하는, 가중치가 높은 자산들(인덱스 0, 1, 2, 4, 5: sfrxETH, wstETH, ETHx, rETH, apxETH)을 대량으로 예치합니다.

add_liquidity()는 0x1.3절 식 (5)의 증분 업데이트를 통해 새로운 곱셈항을 추정합니다. 가중치가 높은 자산들에 대해 xixix_i' \gg x_i이므로, 비율 (xi/xi)(x_i / x_i')는 모두 1보다 훨씬 작은 분수이며, 큰 거듭제곱으로 상승됩니다. 이는 πnew\pi_{\text{new}}를 ~42e18에서 ~0.00353e18로, 거의 0에 가까운 추정 곱셈항으로 몰아갑니다.

이 미세한 곱셈항은 _calc_supply()에 입력됩니다. 반복 과정에서, 곱셈항 업데이트 r = r * sp / s는 0x2절(근본 원인 분석)에서 설명한 내림 반올림 조건과 마주칩니다: 분자가 분모보다 작아지고, 정수 나눗셈이 r0으로 내림합니다. 함수는 0의 곱셈항과 인플레이션된 공급량(~vb_sum)을 반환하며, 프로토콜은 yETH를 초과 발행하게 됩니다.

2. add_liquidity()를 통해 보정을 위한 전제조건을 확립함

공격자는 자산 인덱스 3(cbETH, 고갈된 저가중치 자산)에 대해 단일 사이드 유동성을 추가하며, 자산의 현재 풀 잔액의 약 6.5배를 예치합니다. 이는 소수의 yETH 토큰만을 받지만, 다음 반복이 격렬하게 진동하지 않을 만큼 풀을 재조정합니다.

이 단계 없이는, 3단계에서 곱셈항을 0이 아닌 값으로 재설정하더라도, 4단계의 반복은 극단적 불균형으로 인한 격렬한 진동으로 여전히 0의 곱셈항을 산출할 것입니다. 저희의 Foundry 시뮬레이션은 이를 확인해줍니다: 2단계를 건너뛰면 4단계의 보정이 실패합니다.

3. 0 yETH로 remove_liquidity()를 호출하여 곱셈항을 재설정함

공격자는 금액 0으로 remove_liquidity()를 호출합니다. 토큰은 인출되지 않지만, 함수는 0x1.3절의 식 (4)를 사용하여 현재 풀 상태로부터 vb_prod를 재계산합니다. 가상 잔액이 0이 아니므로, 이는 0이 아닌 곱셈항(~9.09e19)을 산출하며, 손상된 0 값을 덮어씁니다.

4. update_rates()를 통해 공급량을 보정함

공격자는 자산 인덱스 6(WOETH) 또는 7(mETH)에 대해 update_rates()를 호출합니다. 마지막 업데이트 이후 교환 비율이 변경되었다면, 함수는 복원된(0이 아닌) 곱셈항으로 _calc_supply()를 트리거합니다. 이번에는 반복이 올바르게 수렴하여 현재의 인플레이션된 값보다 훨씬 낮은 공급량 값을 산출합니다. 그 차이는 yETH 스테이킹 계약으로부터 **소각(burn)**됩니다. 공식 사후 분석 보고서 [2]에 따르면, 이는 프로토콜 소유 유동성(Protocol-Owned Liquidity, POL)에 해당하며, 이는 소각이 공격자의 보유량이 아니라 프로토콜의 포지션을 줄인다는 것을 의미합니다. 이 비대칭성이 결정적입니다: 각 사이클은 총 공급량을 줄이는 반면 공격자의 yETH 잔액은 그대로 유지됩니다.

비율 불일치 자체는 이익의 원천이 아닙니다; 이는 순전히 트리거 메커니즘으로 작용합니다. 세 가지 풀 인터페이스 중, add_liquidity()update_rates()만이 _calc_supply()를 호출합니다; remove_liquidity()는 비례적 스케일링을 사용하며 호출하지 않습니다. 3단계에서 0이 아닌 곱셈항을 복원한 후, 공격자는 추가 자산을 예치하지 않고 _calc_supply()를 트리거해야 합니다. 오래된 비율로 update_rates()를 호출하는 것은 정확히 이를 달성합니다: 비율 변경이 공격자에게 비용 없이 공급량 재계산을 트리거합니다.

이는 공격의 미묘한 측면을 설명해줍니다: 준비 단계(Phase 1) 동안, 공격자는 의도적으로 WOETH와 mETH에 유동성을 추가하지 않았습니다. 만약 그 비율들이 add_liquidity() 중에 업데이트되었다면, 비율 불일치가 존재하지 않았을 것이고, 이 단계에서의 update_rates()_calc_supply()를 트리거하지 않았을 것입니다.

5. remove_liquidity()를 통해 자산을 인출함

각 사이클의 끝에서, 공격자는 remove_liquidity()를 통해 자산을 인출합니다.

이익이 추출되는 방식

이익 메커니즘은 다음과 같이 작동합니다: 1단계에서 공격자는 LST를 예치하고 (손상된 곱셈항으로 인해) 초과 발행된 yETH를 받습니다. 4단계에서 공급량이 보정될 때, 초과분의 yETH는 공격자가 아니라 POL(스테이킹 계약)로부터 소각됩니다. 5단계에서 공격자는 자신의 yETH 보유량에 비례하여 LST를 인출합니다. POL이 소각을 흡수하고 공격자의 yETH 잔액은 그대로 유지되었기 때문에, 공격자는 결국 예치한 것보다 더 많은 LST를 인출하게 됩니다. 이 차액은 3회의 사이클에 걸쳐 추출되었으며, 총합은 약 $8.1M에 달합니다.

Rebase의 목적

트레이스(첫 번째와 두 번째 사이클 사이)는 또한 OETHVaultProxy.rebase()에 대한 호출을 보여주는데, 이는 OETH rebase를 트리거합니다: WOETH 계약이 보유한 OETH 잔액이 증가하여 WOETH의 실효 교환 비율이 상승합니다. 이 "저장된" 비율 불일치는 두 번째 사이클의 4단계를 다시 가능하게 만드는 것입니다: update_rates()가 결국 호출될 때, 이는 불일치를 감지하고 _calc_supply()를 트리거합니다.

0으로 고갈시키기

이 5단계 사이클을 3회 반복한 후, 공격자는 풀의 총 공급량을 자신이 보유한 yETH 금액 미만으로 줄였습니다. 남은 공급량으로 마지막 remove_liquidity() 호출을 하면 이를 0으로 고갈시킵니다.

이제 풀은 공급량 0, 곱셈항 0, vb_sum 0을 보유합니다. 이 퇴화된 상태는 이전에 예치가 있었던 풀은 결코 초기화되지 않은 상태로 되돌아가지 않는다는 암묵적 설계 가정을 위반합니다.

0x3.3 Phase 3: 제로 공급량을 악용하여 추가 이익을 얻음 (~$0.9M)

목표: 퇴화된 풀 상태로부터 막대한 양의 yETH를 발행한 다음, 이를 실제 자산으로 스왑합니다. 이 단계는 부차적 취약점(비활성화되지 않은 부트스트랩 경로)과 Failure Mode B(언더플로우)의 상호 종속적 조합을 악용하며, 함께 전체 손실의 약 10%에 기여합니다.

1. 언더플로우를 통한 발행(Minting)

총 공급량이 0인 상태에서, 공격자는 더스트 금액(잔액 [1, 1, 1, 1, 1, 1, 1, 9])으로 add_liquidity()를 호출합니다.

prev_supply == 0이므로, 코드는 0x2절(근본 원인 분석)에서 설명한 부트스트랩 경로로 진입합니다: 저장된 상태를 우회하고 _calc_vb_prod_sum()을 통해 vb_prodvb_sum을 처음부터 재계산한 다음, 이 값들을 _calc_supply()에 전달합니다. 이것이 바로 두 번째 취약점이 작동하는 것입니다: 공격자는 풀을 초기화되지 않은 상태로 되돌렸고, 이를 통해 솔버에 공급되는 초기 조건을 제어할 수 있게 되었습니다.

모든 가상 잔액이 더스트 수준(교환 비율이 1e18에 가까움)일 때, 계산된 값들은 다음과 같습니다:

  • vb_sum = 16
  • vb_prod ≈ 9.13e20
  • _supply = vb_sum = 16

_calc_supply() 내부에서, 변수들은 다음과 같이 초기화됩니다:

  • l = _amplification * _vb_sum ≈ 4.5e20 × 16 ≈ 7.2e21
  • d = _amplification - PRECISION4.49e20
  • s = _supply = 16
  • r = _vb_prod9.13e20

이제 뺄셈 l - s * r은:

7.2×102116×9.13×1020=7.2×10211.46×10227.4×10217.2 \times 10^{21} - 16 \times 9.13 \times 10^{20} = 7.2 \times 10^{21} - 1.46 \times 10^{22} \approx -7.4 \times 10^{21}

이것은 음수입니다. 검사되지 않은 uint256 산술에서, unsafe_sub는 이를 대략 22567.4×10212^{256} - 7.4 \times 10^{21}로 랩어라운드시키며, 이는 천문학적으로 큰 값입니다. d(~4.49e20)로 나눈 후, 결과 공급량 추정치는 ~2.35e56이 되며, 프로토콜은 이 전체 금액을 공격자에게 발행합니다. 이 언더플로우는 Phase 2에서 총 공급량이 0으로 몰렸기 때문에만 가능했습니다; 퇴화되지 않은 풀 상태에서는 l > s * r이 유지되며 뺄셈은 안전합니다.

2. 실제 자산으로 스왑

공격자는 초과 발행된 yETH의 일부를 yETH–WETH Curve 풀에서 ~1,097e18 WETH로 스왑하여, 그 풀의 WETH 준비금을 고갈시킵니다. Phase 1에서 소비된 800e18 WETH를 감안하면, 순이익은 약 $0.9M이었습니다.

Phase 2 동안 추출된 약 $8.1M의 LST 자산과 합쳐서, 공격자는 플래시론을 상환한 후 총 약 $9백만 달러의 이익을 얻었습니다.

자금 출처와 목적지 주소를 포함한 상세한 자금 흐름 분석은 다른 공개된 분석(예: [2])에서 다뤄졌으며, 이 글의 범위를 벗어납니다.


0x4 오해 바로잡기

이 사건에 대해 공개된 대부분의 분석은 공격자가 어떻게 전제조건을 설정했는지를 완전히 설명하지 않은 채 산술적 증상에만 초점을 맞추고 있습니다. 특히 두 가지 주장은 정정할 필요가 있습니다.

0x4.1 주장: "pow_up()pow_down() 사이의 반올림 불일치가 불변식을 손상시킨다"

흔한 해석은 근본 원인을 일부 코드 경로에서는 pow_up()을, 다른 경로에서는 pow_down()을 사용하는 데 돌리며, 방향성 불일치가 악용 가능한 불일치를 초래한다고 주장합니다.

저희는 이를 직접 테스트했습니다: (모든 pow_up() 호출을 대체하여) pow_down()을 균일하게 사용하도록 계약을 수정하고 Foundry에서 전체 공격 시뮬레이션을 다시 실행했습니다. 익스플로잇은 동일하게 성공했습니다. 곱셈항은 여전히 0으로 붕괴하고, 공급량은 여전히 고갈되며, 언더플로우는 여전히 인플레이션된 발행을 만들어냅니다.

제로 곱셈항 상태를 가능하게 하는 반올림은 반복문 내부의 r = unsafe_div(unsafe_mul(r, sp), s)에서의 내림 나눗셈이지, 초기 곱셈항 값 추정에 사용되는 거듭제곱 함수의 반올림 방향이 아닙니다.

0x4.2 주장: "두 번째 반복에서의 언더플로우가 중간항을 0으로 만든다"

널리 인용되는 설명은 _calc_supply()의 두 번째 반복 동안 unsafe_sub에서의 언더플로우가 sp ≈ 1.94e18을 산출하며, 이것이 이후 r을 0으로 내림 반올림시킨다고 주장합니다.

저희는 Foundry(온체인 재현)와 Python(수학적 검증) 모두를 사용하여 정확한 중간값들을 재현했습니다. Foundry 시뮬레이션은 _calc_supply()를 반복별로 추적합니다:

======= _calc_supply iteration 0 =======
  l = 4905875511098192451202650000000000000000
  s = 2514373972590845290489        ← initial supply
  r = 3538247433646816               ← initial product (very small)
  d = 4490000000000000000000

  sp = (l - s*r) / d ≈ 1.093e22     ← new supply jumps ~4x
  new r ≈ 4.49e22                    ← product inflates dramatically

======= _calc_supply iteration 1 =======
  s = 10926206313726454855296        ← from previous sp
  r = 44892226765713223838396        ← from previous inner loop

  sp = 19113493328251743069          ← ≈ 1.91e19, legitimately small
  new r = 0                          ← rounds to zero!

핵심 관찰은: 반복 1에서, sp는 ~1.91e19로 평가됩니다. 이는 언더플로우 아티팩트가 아니라 정당하게 작은 양의 값입니다. 뺄셈 l - s*r은 이 반복 시점에서 증폭 가중합 l과 공급-곱셈항 s*r이 크기 면에서 서로 가깝기 때문에 작은 양의 결과를 산출합니다.

곱셈항을 0으로 만드는 것은 그 다음에 일어나는 일입니다: 내부 루프가 r = r * sp / s를 계산하는데, 여기서 sp(~1.91e19)는 s(~1.09e22)보다 훨씬 작습니다. 분자 r * sp가 분모 s보다 작아지고, 정수 나눗셈이 결과를 0으로 내림합니다.

저희는 이를 임의 정밀도 정수를 사용하여 동일한 값들을 계산하는 Python으로 독립적으로 검증했으며, 뺄셈이 언더플로우되지 않음을 확인했습니다:

곱셈항은 뺄셈에서의 언더플로우가 아니라 나눗셈에서의 반올림을 통해 0이 됩니다. 공급량을 인플레이션시키는 unsafe_sub 언더플로우는 완전히 다른 맥락에서 발생합니다: 공급량이 0으로 고갈된 풀에 더스트 유동성이 추가되는 공격의 Phase 3입니다.


0x5 결론

yETH 익스플로잇은 비대칭적인 영향을 가진 두 가지 취약점을 수반했습니다. _calc_supply()안전하지 않은 산술 연산이 주요 근본 원인이었습니다: 그 내림 반올림 실패(Failure Mode A)는 Phase 2만으로 독립적으로 약 $8.1M의 손실을 가능하게 했습니다. 비활성화되지 않은 부트스트랩 경로는 부차적 취약점이었습니다; 언더플로우 실패(Failure Mode B)와 결합되어, Phase 3에서 추가로 약 $0.9M의 손실을 가능하게 했지만, 이는 Phase 2가 이미 공급량을 0으로 고갈시킨 이후에만 가능했습니다. 이러한 손실 분석은 Phase 2와 Phase 3의 이익을 구분하지 않는 다른 공개된 보고서들과 본 분석을 구별짓습니다.

공식 사후 분석 보고서 [2]는 다섯 가지 근본 원인을 식별합니다. 저희는 이를 두 가지 결함(공식 #1과 #5를 통합한 안전하지 않은 산술 연산; 비활성화되지 않은 부트스트랩 경로로서의 #4)과 두 가지 아키텍처적 전제조건(비대칭적 Π 처리에 관한 #2; POL로 가능해진 제로 공급 상태에 관한 #3)으로 재분류합니다. 그 구분은 다음과 같습니다: 결함은 설계 의도를 위반하는 구현 버그(솔버가 제로 곱셈항이나 언더플로우를 산출해서는 안 됨)인 반면, 전제조건은 의도한 대로 기능하지만 결함과 결합될 때 악용 가능한 공격 표면을 만들어내는 설계 선택입니다.

권장 사항

  • 불변식 솔버에서의 검사된 산술 연산. 언더플로우/오버플로우 시 명시적으로 되돌리는(revert) safe_divsafe_sub를 사용하십시오, 가스 효율성을 다소 희생하더라도 말입니다. 솔버는 최대 256회 반복되며, 가스 오버헤드는 보안 위험에 비해 무시할 만합니다.
  • 중간값에 대한 범위 검사. 곱셈항이 반복 사이에 온전한 범위 내에 유지되는지 검증하십시오. 곱셈항이 0으로 떨어지거나 공급량 추정치가 반복 사이에 몇 자릿수 규모로 증가하는 것은 퇴화 상태를 나타내는 신호입니다.
  • 불균형 한도. 임의 자산의 가상 잔액과 그 목표 가중치 비례 잔액 사이의 최대 편차를 강제하십시오. 이는 Phase 1이 전제조건을 만들지 못하게 막을 것입니다.
  • 불변식 단조성 검사. _calc_supply()가 반환된 후, 새로운 공급량이 변화의 방향과 일관성이 있는지 검증하십시오 (유동성 추가가 공급량을 감소시켜서는 안 되고, 비율 업데이트가 10배 규모의 변화를 산출해서는 안 되는 등).
  • 초기화 경로를 영구적으로 비활성화. 풀의 첫 예치 이후, prev_supply == 0 부트스트랩 분기를 재진입할 수 없도록 차단하십시오. 이는 Phase 3을 완전히 방지할 것입니다.
  • 제로 공급 상태를 방지. 프로토콜 수준의 소각(POL 또는 스테이킹 계약으로부터)이 풀이 0이 아닌 잔액을 보유하는 동안 총 공급량을 0으로 줄일 수 없도록 보장하십시오. 최소 공급량 하한선은 부트스트랩 재진입을 가능하게 하는 퇴화 상태로의 전환을 차단할 것입니다.
  • 실시간 이상 탐지. 비정상적인 상태 전이(예: 곱셈항이 0으로 떨어지거나, 공급량이 몇 자릿수 규모로 변화하거나, 짧은 시간 내에 반복적인 add/remove 사이클이 발생하는 등)를 모니터링하고, 손실이 누적되기 전에 경고나 회로 차단기(circuit breaker)를 트리거하십시오.

References

  1. Yearn Finance incident announcement
  2. Yearn Security post-mortem
  3. yETH documentation
  4. yETH whitepaper: invariant derivation
  5. Attack transaction on Phalcon Explorer
  6. BlockSec: Analysis of the Balancer boosted pool incident (August 2023)

About BlockSec

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

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

Sign up for the latest updates
하모니 크로스샤드 ONE 민팅 + 약 4,700만 달러 키 손실 | BlockSec
Security Insights

하모니 크로스샤드 ONE 민팅 + 약 4,700만 달러 키 손실 | BlockSec

2026년 8월10-16일, 보안사건 5건, 손실 약4700만달러. 핵심은 Harmony L1의 크로스샤드 영수증 재생 결함: 목적지 샤드가 서명된 소스 헤더 대신 미인증 MerkleProof의 ShardID·BlockNum으로 소비마커를 산출, 이미 지급된 영수증 재생으로 약3.01조 ONE이 위조됐으나 시총 초과로 손실 제외. 실손실은 개인키 유출(고래~2500만, Kite~1400만, Coinsbuy~790만)과 Fox 로직결함(~11.7만).

~$160만 손실: Moke 토큰, LpdFi 익스플로잇 | BlockSec 위클리
Security Insights

~$160만 손실: Moke 토큰, LpdFi 익스플로잇 | BlockSec 위클리

2026년 8월 3~9일, BNB 체인에서 2건의 보안 사고가 발생해 총 약 160만 달러의 손실이 발생했으며, 모두 가격 조작이 원인이었습니다. LpdFi(약 69.7만 달러)는 PancakeSwap 유동성 풀을 주문 평가와 이자 정산에 동시 사용해 공격자가 원금을 부풀리고 과도한 이자를 탈취했습니다. Moke Token(약 90.6만 달러)은 조작 가능한 현물 가격과 중복 LP 배당 회계를 결합해 MOKE를 부풀려 BNB 배당을 반복 수령했습니다.

COLDCARD 사건: 지갑의 "무작위" 시드가 무작위가 아니었을 때
Security Insights

COLDCARD 사건: 지갑의 "무작위" 시드가 무작위가 아니었을 때

COLDCARD 펌웨어의 빌드 통합 버그로 비트코인 시드 생성이 취약한 소프트웨어 RNG로 라우팅되어 지갑 시드가 오프라인 복구 가능해졌습니다. 시드 자체의 취약점이므로 펌웨어 업데이트로 해결 불가하며, 2026년 8월 7일 기준 확인된 피해는 1,405 BTC(~9,100만 달러), 비공개 추정치는 최대 2,055 BTC입니다.

Best Security Auditor for Web3

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

BlockSec Audit
#5 Yearn Finance 사건: Invariant Solver의 안전하지 않은 산술 연산이 그 이름값을 하다