감사 회사 Certik 의 분석에 따르면, 해커들은 KyberSwap의 일부 유동성 풀에 있는 아주 작은 허점까지도 이용하여 크로스 스왑을 악용하여 유동성 풀에서 막대한 자금을 빼내는 데 성공했습니다.
11월 22일 KyberNetwork는 여러 체인에 대한 플래시론 공격을 받아 약 4,700만 달러의 손실을 입었습니다. Certik에 따르면 이 기술을 사용하면 중앙 집중식 유동성 생성기가 어떻게 작동하는지 이해하는 것이 중요합니다.
자동화된 유동성 공급자는 기본적으로 모든 거래가 이루어지는 곳에 표준 상수 제품 곡선(x*y=k)을 배포합니다.
Certik은 가격 곡선이 더 엄격하게 제한된 풀을 만들면 유동성을 더 잘 활용할 수 있다는 사실을 발견했습니다. 좁은 가격 범위 내에서 많은 유동성을 지원하면 가격 하락 위험이 효과적으로 완화됩니다. 동일한 가격에 영향을 미치려면 거래 규모가 유동성 풀에 비례하여 증가해야 하기 때문입니다.
![](https://static.fwimg.io/img/feed/bb37802f2468daf1b37d586c4808ac15.jpg)
Uniswap v3에 의해 대중화된 중앙 집중식 유동성 공급자 모델을 통해 유동성 공급자(LP)는 선호하는 가격대에 유동성을 추가할 수 있습니다.
Certik은 이 설계의 결과로 풀의 유동성이 비교할 수 없게 되기 때문에 각 유동성 공급 포지션(LP)을 개별적으로 추적해야 한다는 점을 지적합니다. 가격 범위는 별개의 "틱"으로 나눌 수 있으므로 LP는 두 "틱" 사이에 유동성을 제공할 수 있습니다. i로 표시되는 틱 지수는 해당 가격의 로그 로 정의됩니다.
![](https://static.fwimg.io/img/feed/781e2a7697fbddf9183a06c217cc0240.jpg)
유동성 공급자의 순 유동성 및 기타 매개변수는 연결된 목록 데이터 구조에 저장됩니다.
![](https://static.fwimg.io/img/feed/7fbed5db27ec26e9910e0743a9b310e7.jpg)
실제로 이러한 각 포지션은 사용자 정의 가격 곡선을 생성합니다. 모든 다양한 포지션을 단일 가격 곡선으로 집계함으로써 단일 풀이 다양한 유동성 공급자(LP) 선호도를 지원할 수 있습니다.
![](https://static.fwimg.io/img/feed/298b72d5cd52917d38158fc6d4de400e.jpg)
틱 범위에 유동성을 추가하거나 제거할 때 유동성 풀은 이러한 틱이 교차할 때 추가되거나 해제된 가상 유동성의 양과 거래를 트리거하는 데 사용된 토큰 수를 기록해야 합니다. 토큰 0을 토큰 1로 바꾸면 현재 풀 가격과 현재 "틱"이 낮아지고, 토큰 1을 토큰 0으로 바꾸면 현재 풀 가격과 현재 "틱"이 올라갑니다. 스왑을 수행할 때 “틱”이 이 마진을 교차하여 좌우로 이동함에 따라 총 유동성량이 추가되거나 제거되는데, 이것이 공격이 발생하는 KyberSwap의 취약점입니다.
취약점
간단히 말해서, 이 취약점은 KyberSwap Elastic의 ComputeSwapStep() 구현에 존재합니다. 이 함수는 빼거나 더해야 할 트랜잭션의 실제 입력 및 출력 금액, 징수할 스왑 수수료 및 결과 제곱근 값을 sqrtP 함수로 계산하는 역할을 담당합니다.
![](https://static.fwimg.io/img/feed/2db1bdc2cf95123ea4b8e88d4b4ac36f.jpg)
이 함수는 처음에는 calcReachAmount() 함수라고 불리며, 해커의 거래가 틱 마진을 초과하지 않을 것이라고 가정했으나, “calcFinalPrice” 함수를 호출하여 계산한 targetSqrtP 보다 큰 가상 가격을 생성하여 실수가 발생했습니다. 결과적으로 유동성이 제거되지 않고 다음 공격으로 이어진다.
공격 흐름
이 예는 txhash가 0x396a83df7361519416a6dc960d394e689dd0f158095cbc6a6c387640716f5475 인 Ethereum 트랜잭션을 기반으로 합니다.
이 트랜잭션은 모두 동일한 방법을 사용하는 6가지 공격을 나타냅니다. 그래서 Certik은 USDC-ETHX 쌍에 대한 공격을 예로 들었습니다.
1. 먼저, 해커는 Uniswap에서 500 ETHx를 플래시론하고, 과도한 양의 ETHx를 교환하여 2.8 ETHx만 있던 KS2-RT 풀((KyberSwap v2 Reinvestment Token)을 조작했습니다. 해커는 246,754 ETHx를 32389.63 USDC로 교환하여 유출되었습니다. 유동성을 풀고 currentTick을 305,000으로 늘립니다.
![](https://static.fwimg.io/img/feed/fa39124ef0068c55f48bdfd11c90350d.jpg)
스왑 후 풀에는 249.5 ETHX와 13.2 USDC가 남습니다.
![](https://static.fwimg.io/img/feed/58ce6e2235503f9ad9c3695fbf788f58.jpg)
해커는 처음에 500 ETHX를 USDC로 교환하려고 했지만 246,754 ETHx로 32,389.63 USDC를 얻고 currentTick을 305,000으로 늘리기에 충분했습니다. 당시 해커가 교환할 수 있는 305,000틱 마진 이상으로 사용할 수 있는 유동성이 없었음을 의미하며 이는 진공 상태로 간주되었습니다.
![](https://static.fwimg.io/img/feed/1374abc19c34668cf93b834e6e4d185f.jpg)
2. 그런 다음 해커는 KyberSwap: Elastic Anti-Snippingposition Manager 계약에서 mint() 함수를 호출하여 16 USDC 및 5.87e-3 ETHX로 새로운 유동성 풀을 생성합니다. "틱"은 305,000에서 305,408 사이의 좁은 범위에 배치되었습니다. 이는 해커가 "틱"을 305,000에서 추적하기 위해 자신의 유동성 풀을 만들었다는 의미입니다.
![](https://static.fwimg.io/img/feed/5918c15b8f857f3171742ddde1c79058.jpg)
그런 다음 해커는 유동성의 일부를 제거했지만 305,000에서 305,408까지의 틱 범위에 일부 유동성을 남겨 두었습니다.
![](https://static.fwimg.io/img/feed/3f20dfe78a27855fdbe9bd494abf52fc.jpg)
3. 해커는 ETHX에서 USDC로 두 번째 스왑을 수행합니다. 그들은 305,000의 진드기 마진으로 244.08 ETHX를 교환하여 13.6 USDC를 받고 진드기 마진을 305,408로 늘렸습니다.
![](https://static.fwimg.io/img/feed/3bd6f141268f9bf9bffbb9d89e917d11.jpg)
표면적으로는 해커가 305,000에서 305,408 사이의 유동성을 제공하는 유일한 사람이었기 때문에 이는 이상한 스왑처럼 보이지만 이것이 다음 단계의 전제입니다.
4. KyberSwap은 거래가 틱 간격을 초과하는지 확인하기 위해 계산 SwapStep() 함수를 사용합니다. 함수의 일부는 다음과 같습니다.
![](https://static.fwimg.io/img/feed/5f91b1a3084facae9b1e32b990e2c2ee.jpg)
여기서는 calcReachAmount() 함수에 중점을 둡니다. 이 함수는 currentSqrtP (현재 제곱근 가격)가 targetSqrtP 에 도달하는 경우 거래에 필요한 토큰 수를 계산합니다.
Cerik은 이 거래에서 UsedAmount 의 값은 244080034447360000000이고, 해커가 교환을 위해 입력한 ETHx의 양은 244080034447359999999로, UsedAmount 값보다 하나 적다고 밝혔습니다. ComputeSwapStep() 함수는 이 거래가 현재 틱 범위의 유동성을 소진하기에 충분하지 않으며 다른 틱 범위로 변경할 필요가 없다고 판단합니다. nextSqrtP 가 targetSqrtP 로 업데이트되지 않음을 의미합니다.
![](https://static.fwimg.io/img/feed/c0b7b3763f2ef9effb03cbb4b833a65c.jpg)
그런 다음 calcFinalPrice 함수가 호출되어 다음 nextSqrtP 값을 계산합니다. 여기서 중요한 부분은 계산 중에 스왑 수수료가 유동성에 포함된다는 것입니다. nextSqrtP 의 최종 가격은 실제로 초기 예상보다 높으며, 특히 "틱" 범위인 305,408의 가격보다 높습니다.
![](https://static.fwimg.io/img/feed/7ae61994fab8fbb5c4ef524a1785ad61.jpg)
![](https://static.fwimg.io/img/feed/2fb940c33bb2da020995a487101e11d4.jpg)
위의 교환 함수로 돌아가서 Certik은 sqrtP 와 nextSqrtP 의 값을 비교하여 틱 진폭을 교환해야 하는지 결정한다고 말합니다. 여기의 조건은 sqrtP가 nextSqrtP 와 '동일'한지 여부만 결정하고, 일치하는 경우에만 하위 함수 updateLiquidityAndCrossTick()이 호출되어 유동성( swapData.baseL )을 추가하거나 빼고 마진 "틱"을 전달합니다.
이 시점에서 sqrtP 는 이제 nextSqrtP 보다 큽니다. 이는 해커가 현재 가격이 틱 범위 상한선을 넘은 상황을 만들었으나, 유동성을 감소시키는 updateLiquidityAndCrossTick() 함수를 실행하지 않아 가짜 유동성이 존재하게 된 것을 의미한다.
![](https://static.fwimg.io/img/feed/dc0ecc6c54d17c771a2e606edad9d36f.jpg)
![](https://static.fwimg.io/img/feed/8b523a0b2a5e08719aa2a768240dc118.jpg)
5. 마지막으로 해커는 역거래를 수행하여 USDC를 ETHx로 교환하여 "틱" 범위인 305,408보다 약간 높은 가격을 인하했습니다. 이는 해커가 제공한 유동성 범위의 상한(305,000에서 305,408까지)을 틱 범위보다 약간 아래로 낮췄습니다. 가격은 "틱" 범위인 304,982입니다.
![](https://static.fwimg.io/img/feed/9ca737de10d3de7db7b81ac944f4489a.jpg)
가격이 해커가 유동성을 제공한 305,000 – 305,408의 "틱" 범위에 진입했을 때 305,408보다 약간 높은 수준 에서 updateLiquidityAndCrossTick() 함수가 호출되었습니다. 305,000~305,408 '틱' 범위 내의 유동성에 가짜 유동성을 더해 실제 유동성 대비 '틱' 범위 값 사이의 유동성을 높인다. 그런 다음 해커는 이 가격대 내에서 493,638 ETHx를 27,517 USDC로 전환했습니다(305,000 – 305,408틱 범위에 포함되지 않은 약 250 ETHx 포함). 그 결과, 이전 비용은 복구되었고 동시에 유동성 풀은 모든 USDC를 잃었다고 Certik은 결론지었습니다.
![](https://static.fwimg.io/img/feed/1ee7c2d066012cb8f9b0b6f87a2a2740.jpg)
6. 대출금을 빠르게 반납하고 공격을 완료하세요.
![](https://static.fwimg.io/img/feed/31f3ca3e2b67e197e5569147f3eb6ebd.jpg)
이 프로세스는 여러 블록체인에 걸쳐 KyberSwap 프로토콜의 여러 거래 쌍에 대해 반복되었으며 결과적으로 다음과 같은 손실이 발생했습니다.
폴리: 1,180,097 USD; ETH: 7,486,868 USD; 영업이익: 15,504,542 USD; 기본: 318,413 USD; ARB: 16,833,861USD; AVAX: 23,526달러.
Certik이 확인한 공격은 세 가지 다른 이더리움 주소(EOA)에서 발생했으며 주소 0x502가 대부분의 자산을 차지합니다. 처음에는 주소 0x502에서 프로젝트에 연락하여 일정 기간 휴식 후 협상하겠다고 알렸습니다. KyberSwap 팀은 11월 25일 오전 6시(UTC)까지 10% 보상을 제안하기 위해 연락했습니다.
![](https://static.fwimg.io/img/feed/3c93e17cdf583570c3e84b07ec2d1ac4.jpg)