
Yearn Finance 공격 사건 분석
개요
2023년 4월 13일 Yearn Finance가 해커의 공격을 받아 약 1,000만 달러의 손실이 발생했다. 본문에서는 공격 과정과 취약점 발생 원인을 분석한다.
공격 분석
다음은 공격 거래 예시이다:
https://etherscan.io/tx/0xd55e43c1602b28d4fd4667ee445d570c8f298f5401cf04e62ec329759ecda95d
공격자는 Balancer에서 플래시론을 통해 500만 DAI, 500만 USDC, 200만 USDT를 차입했다:

그 후 Curve에서 공격자는 500만 DAI를 695,000 USDT로 교환하고, 350만 USDC를 151 USDT로 교환했다:

공격자는 IEarnAPRWithPool의 recommend 함수를 호출해 현재 APR을 확인했다. 이 시점에서 Aave의 APR만 0이 아니었다:

다음으로 공격자는 800,000 USDT를 공격용 컨트랙트 0x9fcc1409b56cf235d9cdbbb86b6ad5089fa0eb0f로 전송했다. 해당 컨트랙트 내에서 공격자는 Aave Lending Pool V1의 repay 함수를 여러 번 호출하며 타인의 부채를 상환하여 Aave의 APR을 0으로 만들었다:

공격자는 yUSDT의 deposit 함수를 호출하여 900,000 USDT를 담보로 맡기고 820,000 yUSDT를 획득했다:

이어 공격자는 bZx iUSDC의 mint 함수를 호출해 156,000 USDC를 사용해 152,000 bZx iUSDC를 발행하고 이를 Yearn yUSDT로 전송했다:

공격자는 Yearn yUSDT의 withdraw 함수를 호출해 820,000 yUSDT를 1,030,000 USDT로 환전했다. 이 시점에서 컨트랙트에는 공격자가 전송한 bZx iUSDC만 남아 있었다:

이어 공격자는 Yearn yUSDT의 rebalance 함수를 호출해 bZx iUSDC를 소각했다:

그 다음 공격자는 yUSDT 컨트랙트에 1/e6 수준의 미미한 양의 USDT를 전송한 후 deposit 함수를 호출해 10,000 USDT를 담보로 맡기고 1,252,660,242,850,000 개의 yUSDT를 획득했다:

이후 공격자는 Curve에서 70,000 yUSDT를 5,990,000 yDAI로, 4억 yUSDT를 4,490,000 yUSDC로, 1,240,133,244,352,200 yUSDT를 1,360,000 yTUSD로 각각 교환했다:

그리고 yearn: yDAI와 yearn: yUSDC에서 각각 withdraw 함수를 호출해 678만 DAI와 562만 USDC를 인출한 후 플래시론을 상환했다:

취약점 분석
이번 공격에서 가장 핵심적인 부분은 공격자가 10,000 USDT를 담보로 1,252,660,242,850,000 개의 yUSDT를 발행한 점이다. deposit 함수의 구현을 살펴보면:

share의 수량은 변수 pool과 관련 있으며, pool 값이 작을수록 share 수량이 크게 된다. pool 값은 _calcPoolValueInToken 함수에 의해 결정된다:

공격자가 rebalance 함수를 호출한 후 컨트랙트 내에는 USDC만 존재하지만, _balance()는 USDT 잔액을 조회하므로 USDC 잔액은 포함되지 않는다. 따라서 이 시점의 pool 값은 1(공격자가 전송한 금액)이 된다:



여기서 명백히 프로젝트 팀의 설정 오류가 발생한 것이다. yUSDT 컨트랙트 내에는 모두 USDT 계열 토큰이 있어야 하지만, fulcrum 변수는 USDC 기반의 bZx iUSDC 토큰으로 설정되어 있어, yUSDT 내 USDC 잔액이 balance에 반영되지 않았다:


왜 공격자가 rebalance 함수를 호출해 bZx iUSDC 토큰을 소각할 수 있었을까? rebalance 함수의 구현을 살펴보면:



_withdrawFulcrum() 함수 내에 redeem 및 burn 작업이 존재함을 알 수 있다. 따라서 "newProvider != provider" 조건이 성립해야 하며, recommend() 함수의 구현은 다음과 같다:

공격자는 IIEarnManager(apr).recommend(token)의 반환값을 조작해 모두 0이 되도록 하여 newProvider 값을 조절했다:

모두 0이 되게 하려면, 해당 함수의 반환값은 각 DeFi 프로토콜의 APR 계산 결과에 따라 달라진다. Compound, bZx, dydx에는 풀이 없으므로 Aave (Aave: Lending Pool Core V1)만 제어하면 된다:

반환값을 0으로 만들기 위해 apr.calculateInterestRates 함수의 첫 번째 반환값이 0이 되어야 한다:

즉 currentLiquidityRate가 0이 되어야 하는데, 이 값은 _totalBorrowsStable 및 _totalBorrowsVariable과 관련 있으며, 두 값이 모두 0일 경우 currentLiquidityRate도 0이 된다:


_totalBorrowsVariable이 0이라는 것은 Aave Lending Pool Core V1에 아무도 빚을 지지 않은 상태임을 의미한다. 이를 달성하기 위해 공격자는 풀 내 모든 사용자의 부채를 상환했다:

마지막으로 공격자는 _totalBorrowsVariable을 0으로 만들어 rebalance 함수를 호출해 bZx iUSDC 토큰을 소각할 수 있었다:

요약
이번 Yearn Finance 공격 사건의 근본 원인은 프로젝트 측의 컨트랙트 설정 오류였다. 공격자는 정교한 일련의 조작을 통해 이 취약점을 이용해 약 1,000만 달러의 이익을 얻었다.
TechFlow 공식 커뮤니티에 오신 것을 환영합니다
Telegram 구독 그룹:https://t.me/TechFlowDaily
트위터 공식 계정:https://x.com/TechFlowPost
트위터 영어 계정:https://x.com/BlockFlow_News














