Yearn 900만 달러 도난 공격 분석
- 核心观点:Yearn协议遭多阶段组合攻击损失900万美元。
- 关键要素:
- 利用闪电贷撬动初始攻击资金。
- 组合利用精度丢失等三个核心漏洞。
- 最终实现无限铸造LP代币并掏空资金池。
- 市场影响:凸显DeFi协议组合漏洞风险,促进行业安全升级。
- 时效性标注:中期影响
머리말
2025년 12월 1일, Yearn 프로토콜은 정교한 다단계 해킹 공격을 받아 약 900만 달러의 자산 손실을 입었습니다. 이 공격은 단일 공격이 아니었습니다. 공격자들은 플래시 대출을 통해 자금을 차용하고, 프로토콜의 여러 논리적 결함을 악용하여 유동성 풀을 점진적으로 조작했습니다. 궁극적으로 yETH 관련 LP 토큰을 거의 무한대로 발행하여 유동성 풀을 고갈시켰습니다.
이 공격은 플래시론을 초기 금융 레버리지로 활용하여 여러 단계를 거쳐 프로토콜의 방어 체계를 단계별로 돌파했습니다. 핵심 공격 과정은 자금 준비, 상태 조작, 무제한 채굴, 그리고 이익 실현의 네 가지 핵심 단계로 나눌 수 있습니다. 각 단계는 서로 연결되어 있으며 프로토콜 설계의 논리적 취약점을 정밀하게 악용합니다.
텐센트 공격 : https://etherscan.io/tx/0x53fe7ef190c34d810c50fb66f0fc65a1ceedc10309cf4b4013d64042a0331156
기술적 분석
먼저, 우리는 Balancer와 Aave로부터 각각 두 번의 플래시 대출을 통해 wstETH, rETH, WETH와 0xa35b_ETHx, rETH, wstETH, cbETH를 빌립니다.


플래시 대출 콜백 함수에서 빌린 ETH는 Tornado.Cash: 100 ETH에 입금되어 1,100 ETH와 혼합됩니다. 그런 다음 Tornado.Cash: 100 ETHwithdraw 함수를 사용하여 악성 계약 0x3e8e7533dcf69c698Cf806C3DB22f7f10B9B0b97로 100 ETH를 인출하고 해당 계약의 폴백 함수를 트리거합니다.


fallack 함수에서 아래 이미지의 마지막 교환 값이 다음 단계의 remove_liquidity 값과 동일함을 확인할 수 있습니다. 이는 이전 단계들이 모두 후속 공격에 대비하여 exchange 등의 작업을 통해 플래시 대출에서 얻은 자산을 대량의 yETH 가중치 스테이블스왑 풀 LP 토큰으로 교환하는 것이었음을 유추할 수 있습니다.

이 시점에서 핵심 공격 프로세스가 공식적으로 시작됩니다.
1. 먼저, 위 거래소에서 획득한 모든 LP 토큰은 소각되고, yETH 가중 스테이블스왑 풀의 remove_liquidity 함수를 통해 지분 배분에 따라 풀의 8개 기초 자산으로 전환됩니다.
remove_liquidity 함수의 논리는 다음과 같이 이해할 수 있습니다. 풀에 총 10,000개의 LP 토큰이 있고 416.37개의 LP 토큰을 소각한다고 가정하면 비율은 416.37 / 10,000 = 4.16%입니다.
그런 다음 각 자산에 대해 풀에 1,000 wstETH(가상 잔액 prev_vb)가 있다고 가정하면 인출할 수 있는 가상 잔액은 dvb = 1,000 * 416.37 / 10,000 = 41.637 wstETH입니다. 이를 환율로 나누어 실제 토큰 수로 변환합니다.
2. 둘째, `add_liquidity`를 반복적으로 호출하여 단방향 풀을 주입합니다. 총 8개의 `_amounts` 매개변수가 있으며, 각각은 주입될 자산의 양에 해당합니다. 이전 루프에서 index3[rETH 토큰], index6[wOETH 토큰], index7[mETH 토큰]이 모두 0으로 입력되었는데, 이는 유동성이 추가될 때마다 이 세 토큰이 추가되지 않았음을 의미합니다.

위에서 설명한 방식으로 일방적으로 자산을 주입하고 풀에서 토큰을 인출함으로써 풀에 있는 rETH, w0 ETH, mETH 및 기타 토큰 간의 수량 격차가 인위적으로 벌어집니다.
3. 다음으로, 막대한 양의 rETH 토큰이 일방적으로 주입되고, 그 다음에 _liquidity를 제거하는 중요한 단계가 이어지지만 _amount=0입니다.

왜 토큰을 0개만 출금할 수 있나요? 이는 remove_liquidity 의 내부 구현으로 인해 발생합니다.

remove_liquidity 함수는 0 값 거래에 대한 단락 처리를 수행하지 않았습니다. 여전히 전체 vb_prod 계산 루프를 실행하고 위에서 언급한 인위적으로 생성된 풀의 토큰 수량 차이에 따라 글로벌 packed_pool_vb 상태를 계산하고 업데이트했습니다.
4. 그런 다음 update_rates 함수를 호출하여 index6[wOETH]의 풀 비율만 업데이트하고, 마지막으로 remove_liquidity 함수를 호출하여 풀에서 토큰을 추출합니다. 이 시점에서 풀에 있는 W0 ETH의 양은 거의 소진되었습니다.

5. 마찬가지로, index6[w0 ETH]와 index7[mETH]의 지분을 비슷한 방식으로 풀에서 압축합니다. index6을 처음 두 번 업데이트한 후에는 remove_liquidity를 사용하여 토큰을 인출하는 반면, index7[mETH]는 업데이트만 되었을 뿐 아직 인출되지 않았습니다.

단일 측면 풀에 엄청난 양의 토큰을 추가하고 모든 풀에서 지속적으로 토큰을 추출함으로써, 풀에서 W0 ETH와 mETH의 비율은 이제 거의 0에 도달했습니다.
이때 새로운 악성 계약 0xADbE952eBB9b3e247261d2E3b96835f00f721f8E 가 생성되고 모든 토큰이 이 계약으로 전송됩니다. 이전 단계에서 rETH를 일방적으로 추가하여 얻은 LP 토큰은 기본 토큰으로 변환되지 않고 새로운 악성 계약으로 전송되었습니다.

이전 공격 작업으로 update_rates에서 index7[mETH]가 업데이트되었지만, 인출되지 않은 토큰은 remove_liquidity를 호출하여 인출됩니다. 현재 풀에서 index6[w0 ETH]의 점유율은 매우 낮으며, index7[mETH]의 점유율은 더욱 낮습니다.

이 시점에서 풀의 토큰 비율은 심각하게 불균형을 이루었습니다. 공격자는 add_liquidity를 다시 호출하여 유동성을 추가했고, [1, 1, 1, 1, 1, 1, 9]의 비율로 막대한 양의 LP 토큰을 획득했습니다.

이 시점에서 공격자는 대량의 LP 토큰을 확보한 후, 교환 및 상환 등의 방법을 사용하여 수익을 창출하고 플래시 대출 수수료를 상환합니다.

공격 검토
이 공격은 공격자가 pool.vy 계약의 세 가지 핵심 취약점 인 정밀 손실, 수확량 감소, 제로 공급 초기화를 악용한 복잡하고 다단계의 복합 공격이었습니다.
1단계: 극심한 불균형 만들기
- 작업: 공격자는 add_liquidity를 반복적으로 호출하지만 인덱스 3(rETH), 인덱스 6(wOETH), 인덱스 7(mETH)을 의도적으로 피합니다.
- 목적: 풀 내 자산 비율의 불균형을 인위적으로 조성하는 것입니다.
- 핵심 단계: 그런 다음 대량의 rETH를 일방적으로 주입합니다.
- 결과: 이로 인해 rETH와 다른 자산(특히 wOETH와 mETH) 간의 양적 격차가 크게 벌어지고, 정밀도가 저하될 수 있는 수학적 조건이 발생합니다.
2단계: 오류 트리거 및 잠금
- 작업: remove_liquidity(_amount=0)를 호출합니다.
- 원칙:
`remove_liquidity`는 0의 양에 대해 단락 회로를 수행하지 않습니다.
자금을 이체하지 않더라도 계약은 여전히 vb_prod의 전체 계산 루프를 실행합니다.
극심한 가중치 불균형이 있는 경우 _pow_down 함수는 상당한 다운라운딩 오류를 생성합니다.
계약은 vb_prod의 더 작은 오류 값을 글로벌 상태 packed_pool_vb에 기록합니다.
- 기본적으로 이것은 "비용이 없는" 상태 공격으로, 공격자는 아무런 비용도 들이지 않고 풀의 장부 가치를 성공적으로 변경합니다.
3단계: 수익 삭감 및 시장 점유율 추출
- 작동하다:
update_rates([6]) (wOETH 환율을 업데이트합니다).
remove_liquidity(자산 제거).
update_rates([7]) (mETH 환율을 업데이트합니다).
- 원칙:
`update_rates`는 `_update_supply`를 트리거합니다. 이전에 `vb_prod`가 악의적으로 억제되었기 때문에 시스템이 풀 가치가 감소했다고 잘못 판단하여 스테이킹 계약에서 보유한 LP 토큰을 소각하여 계정 균형을 맞췄습니다.
공격자는 환율 업데이트 전후에 차익거래를 위해 remove_liquidity를 악용하여 점차적으로 wOETH와 mETH 풀을 고갈시켰습니다.
- 결과: 다수의 스테이킹 계약이 파괴되었고, 공격자의 LP 점유율이 수동적으로 증가했으며, 풀의 총 공급량은 0으로 밀려났습니다.
4단계: 공급 없음, 무제한 주조
- 기존 상태: 위의 작업 후 풀이 비워지고 총 공급량은 0에 가깝고 wOETH와 mETH 잔액은 매우 낮습니다.
- 작업: add_liquidity, 매개변수는 _amounts=[1, 1, 1, 1, 1, 1, 9]입니다.
- 원칙:
prev_supply ≈ 0인 경우, _calc_supply에 대한 반복 공식은 매우 작은 값(1 wei, 9 wei)을 처리할 때 실패합니다.
해당 계약에서는 천문학적인 수의 LP 토큰이 잘못 계산되었습니다.
- 결과: 공격자는 235,443개의 yETH LP 토큰을 갑자기 획득했습니다.
요약하다
Yearn 공격은 엣지 시나리오에서의 로직 검증, 수치 계산에서의 정밀 제어, 그리고 여러 취약점 조합에 대한 위험 방지와 관련하여 DeFi 프로토콜의 여러 단점을 드러냈습니다. 플래시 대출을 도구로, 취약점 조합 악용을 핵심으로, 그리고 자금 난독화를 은폐 수단으로 사용하는 공격자의 공격 패턴은 DeFi 공격의 전문화 및 복잡성이 심화되는 현재 추세를 보여줍니다. 이 공격에서 얻은 주요 교훈은 다음과 같습니다. 첫째, 프로토콜은 단락 회로 처리 부족으로 인한 상태 변조 위험을 방지하기 위해 "제로 금액" 및 "극심한 불균형"과 같은 엣지 시나리오에 대한 로직 검증을 강화해야 합니다. 둘째, 극단적인 비율에서의 정밀도 손실은 수치 계산에서 해결되어야 하며, `_pow_down`과 같은 주요 함수의 계산 로직을 최적화해야 합니다. Balancer 프로토콜은 이전에 정밀도 손실로 인한 보안 사고를 경험했으며, 이는 경고의 메시지 역할을 합니다. 셋째, 고빈도 일방적 유동성 주입 및 비정상적인 환율 업데이트와 같은 의심스러운 작업에 대한 경고를 발행하는 다차원 위험 모니터링 시스템을 구축해야 합니다. DeFi 산업 전체에 있어 이번 사고는 프로토콜 보안에 개별 취약점을 해결하는 것뿐만 아니라 전체 프로세스 관점에서 여러 취약점을 결합한 공격을 방지하고, 공격자의 자금 흐름에 대한 추적 및 차단을 강화하여 산업의 전반적인 보안 보호 역량을 개선해야 함을 다시 한번 입증했습니다.


