เมื่อวันที่ 27 มีนาคม 2022 Revest Finance โครงการ DeFi ที่เดิมพันบน Ethereum ถูกแฮ็กและสูญเสียไปประมาณ 2 ล้านเหรียญสหรัฐ ทีม BlockSecTeam เข้าแทรกแซงการวิเคราะห์ทันทีและแบ่งปันผลการวิเคราะห์ของเรากับชุมชนบนทวีตเตอร์ ในความเป็นจริง เมื่อเราแชร์ผลการวิเคราะห์กับชุมชนผ่านทวีตเตอร์ เราพบช่องโหว่ร้ายแรงแบบ Zero-day ในสัญญา TokenVault ของ Revest Finance เมื่อใช้ช่องโหว่นี้ ผู้โจมตีสามารถขโมยทรัพย์สินในโปรโตคอลด้วยวิธีที่ง่ายกว่า ดังนั้นเราจึงติดต่อฝ่ายโครงการ Reves Finance ทันที หลังจากยืนยันว่าช่องโหว่ได้รับการแก้ไขแล้ว เราตัดสินใจแบ่งปันบล็อกนี้กับชุมชน
0. What's the Revest Finance FNFT
ชื่อเรื่องรอง
Revest Finance เป็นโซลูชันสำหรับการเดิมพันในฟิลด์ DeFi การเดิมพัน DeFi ใด ๆ ที่ผู้ใช้มีส่วนร่วมผ่าน Revest Finance สามารถสร้าง NFT ได้โดยตรง ซึ่งก็คือ FNFT (โทเค็นการเงินที่ไม่สามารถเปลี่ยนแปลงได้) ซึ่งแสดงถึงสถานะปัจจุบันและสถานะปัจจุบันของตำแหน่งการเดิมพัน มูลค่าในอนาคต ผู้ใช้สามารถโต้ตอบกับโครงการผ่าน 3 อินเทอร์เฟซที่จัดทำโดย Revest Finance จำนำสินทรัพย์ดิจิทัลของคุณเอง สร้าง FNFT ที่เกี่ยวข้อง
•mintTimeLock : สินทรัพย์ดิจิทัลที่ผู้ใช้จำนำไว้สามารถปลดล็อกได้หลังจากผ่านไประยะหนึ่งเท่านั้น
•mintValueLock : สินทรัพย์ดิจิทัลที่ผู้ใช้จำนำไว้จะสามารถปลดล็อกได้ก็ต่อเมื่อพวกเขาแข็งค่าหรืออ่อนค่าเป็นค่าที่ตั้งไว้ล่วงหน้าเท่านั้น
•mintAddressLock : สินทรัพย์ดิจิทัลที่ผู้ใช้จำนำไว้สามารถปลดล็อกได้โดยบัญชีที่ตั้งไว้ล่วงหน้าเท่านั้น
Reves Finance ดำเนินการล็อคและปลดล็อคสินทรัพย์ดิจิทัลที่ผู้ใช้ฝากไว้ผ่านสัญญาอัจฉริยะสามรายการต่อไปนี้
•FNFTHandler: สืบทอดมาจากโทเค็น ERC-1155 (การใช้งาน openzepplin) ทุกครั้งที่ดำเนินการล็อค fnftId จะเพิ่มขึ้น (fnftId คล้ายกับ tokenId ใน ERC721) เมื่อสร้าง FNFT ผู้ใช้จำเป็นต้องระบุ totalSupply เมื่อผู้ใช้ต้องการถอนสินทรัพย์อ้างอิงที่อยู่เบื้องหลัง FNFT พวกเขาจำเป็นต้องเบิร์น FNTF ตามสัดส่วนที่สอดคล้องกัน
•LockManage : บันทึกเงื่อนไขสำหรับ FNFT ที่จะปลดล็อค (ปลดล็อค)
•TokenVault: รับและส่งสินทรัพย์อ้างอิงที่ฝากโดยผู้ใช้ และบันทึกข้อมูลเมตาของแต่ละ FNFT ตัวอย่างเช่น ประเภทสินทรัพย์ที่จำนำหลัง FNFT ด้วย fnftId = 1
เนื่องจากการโจมตีนี้ จุดเริ่มต้นของการโจมตีของแฮ็กเกอร์คือฟังก์ชัน mintAddressLock ดังนั้นลองใช้ฟังก์ชันนี้เป็นตัวอย่างเพื่ออธิบายวงจรชีวิตของ FNFT
•unlocker : User X ->ผู้ใช้ A เรียกใช้ฟังก์ชัน mintAddressLock ของ Revest
เฉพาะผู้ใช้ X เท่านั้นที่สามารถปลดล็อกเนื้อหานี้ได้ • ผู้รับ : [ผู้ใช้ A , ผู้ใช้ B , ผู้ใช้ C] • ปริมาณ : [50 , 25 , 25] -> ปริมาณมิ้นท์คือ 100 (ผลรวม (ปริมาณ)) , ผู้ใช้ A , ผู้ใช้ B , ผู้ใช้ C แต่ละคนมีเหรียญ 50, 25 และ 25 เหรียญ • สินทรัพย์ : WETH -> FNFT จากโรงกษาปณ์ใช้ WETH เป็นหลักประกัน • DepositAmount : 1e18 -> จำนวนหลักประกันหลังแต่ละ FNFT คือ 1 WETH ( ทศนิยม WETH คือ 18 )
สมมติว่าไม่มี FNFT อื่นในระบบปัจจุบัน ผู้ใช้ A โต้ตอบกับระบบผ่าน mintAddressLock และ fnftId ที่ส่งคืนโดย FNFTHandler = 1
•fnftId : 1•unlocker : User X
LockManger เพิ่มบันทึกที่เกี่ยวข้องสำหรับมัน
•fnftId : 1•asset : WETH•depoistAmount : 1e18
Token Vault เพิ่มบันทึกที่เกี่ยวข้องสำหรับมัน
จากนั้น Token Valut จำเป็นต้องโอน 100 * 1e18 WETH จากผู้ใช้ A
สุดท้าย ระบบให้ 01-FNFT แก่ผู้ใช้ A ผู้ใช้ B และผู้ใช้ C 50, 25 และ 25 ชิ้นตามลำดับ
สิ่งนี้ทำได้โดยการสร้าง FNFT ผ่านฟังก์ชัน mintAddressLock
หลังจากที่ผู้ใช้ X ปลดล็อก 01-FNFT แล้ว ผู้ใช้ B สามารถถอนสินทรัพย์อ้างอิงผ่านการถอน FNFT ดังที่แสดงในรูปที่ 2 ผู้ใช้ B ต้องการถอนสินทรัพย์ดิจิทัล 25 รายการที่จำนำโดย 01-FNFT ในมือของเขา
โปรโตคอลจะตรวจสอบก่อนว่าปลดล็อค 01-FNTF แล้วหรือไม่ หากปลดล็อคแล้ว โปรโตคอลจะเขียน 25 01-FNFT ของผู้ใช้ B และโอน 25*1e18 WETH ให้กับเขา ในขณะนี้ อุปทานทั้งหมดของ 01-FNFT คือ 75
สัญญา Revest ยังมีอินเทอร์เฟซอื่นที่เรียกว่า depositAdditionalToFNFT เพื่อให้ผู้ใช้สามารถเพิ่มสินทรัพย์อ้างอิงให้กับ FNFT ที่มีอยู่ ด้านล่างเราจะอธิบายการใช้งาน "ปกติ" ด้วยรูปภาพ 2 ภาพ
มีสามกรณีที่นี่
1.quantity == 01-FNFT.totalSupply() ดังรูปที่ 3
•quantity = 75 ->ใช้สถานการณ์ในรูปที่ 2 เป็นบริบท ผู้ใช้ A ต้องการเพิ่มหลักประกันเพิ่มเติมสำหรับ 01-FNFT
•amount = 0.5*1e18 ->คำมั่นสัญญาเพิ่มเติมสำหรับ 75 01-FNFT
แต่ละ 01-FNFT จะเพิ่ม 0.5*1e18 WETH
ดังนั้น ผู้ใช้ A จำเป็นต้องโอน 37.5*1e18 WETH (75 * 0.5*1e18) ไปยัง Token Vault Token Vault แก้ไขระบบบัญชีและเปลี่ยน depositAmount เป็น 1.5*1e18 ตอนนี้ 01-FNFT แต่ละรายการมีสินทรัพย์ 1.5*1e18 WETH
ในเวลานี้ ผู้ใช้ C เรียกใช้การถอน FNFT เพื่อกำจัด 25 01-FNFT ที่เขาถืออยู่ และเขาสามารถถอนเงินได้ 25*(1.5*1e18) = 37.5*1e18 WETH
ดังนั้น อุปทานทั้งหมดของ 01-FNFT คือ 50 ในเวลานี้
2.ปริมาณ < 01-FNFT.totalSupply() ดังรูปที่ 4
•quantity = 10 ->ใช้ฉากในรูปที่ 3 เป็นบริบท ผู้ใช้ A ยังคงเพิ่มหลักประกันเพิ่มเติมสำหรับ 01-FNFT
คำมั่นสัญญาเพิ่มเติมสำหรับ 10 01-FNFT •จำนวน = 0.5*1e18 -> เพิ่ม 0.5*1e18 WETH ในแต่ละ 10 01-FNFTs< 01-FNFT.totalSupply()
เนื่องจากปริมาณ•fnftId : 2•asset : WETH•depositAmount : 2.0*1e18 (1.5*1e18 + 0.5*1e18)
ดังนั้น ผู้ใช้ A จ่าย 5*1e18 WETH ตามข้อตกลง ระบบจะเบิร์น 10 01-FNFTs, มิ้นต์ 10 02-FNFTs และเบิร์นสินทรัพย์ที่ดำเนินการโดย 10 01-FNFTs และสินทรัพย์ที่โอนใหม่ของผู้ใช้ A อัดฉีดเข้าไปใน 02-FNFT . ดังนั้นจึงมี
ณ ขณะนี้
• 01-FNFT.totalSupply : 40 01-FNFT.depositAmount : 1.5*1e18 (ตามเหตุผลแล้วควรเป็น ดูด้านล่าง: ช่องโหว่ Zero-day ใหม่) • 02-FNFT.totalSupply : 10 02-FNFT.depositAmount : 2.0* 1e18
ในกรณีนี้ การทำธุรกรรมจะย้อนกลับ
1. What't the Re-entrancy vulnerability
ชื่อเรื่องรอง
หลังจากทำความเข้าใจเวิร์กโฟลว์พื้นฐานของฟังก์ชัน mintAddressLock และฟังก์ชัน depositAdditionalToFNFT แล้ว เรามาดูวิธีการกลับเข้าใช้ใหม่โดยผู้โจมตี สมมติว่า fnftId ล่าสุด = 1 (ไม่ส่งผลต่อความเข้าใจ)
ดังแสดงในรูปที่ 5
ขั้นแรก:
•depositAmount = 0
•quantities = [2]
ผู้โจมตีเรียกใช้ฟังก์ชัน mintAddressLock
โรงกษาปณ์ออก 01-FNFT 2 ชิ้น เนื่องจากผู้โจมตีตั้งค่า DepositAmount เป็น 0 ดังนั้นเขาจึงไม่ได้โอนสินทรัพย์ดิจิทัลใดๆ เทียบเท่ากับสินทรัพย์อ้างอิงที่ดำเนินการหลัง 01-FNFT คือ 0
•depositAmount = 0
ขั้นตอนที่ 2: ผู้โจมตีเรียกฟังก์ชัน mintAddressLock อีกครั้ง
•ปริมาณ = [360000] เตรียมเหรียญกษาปณ์ 36w ชิ้น 02-FNFT depositAmount คือ 0
ในขั้นตอนสุดท้ายของการดำเนินการ ผู้โจมตีใช้กลไกการเรียกกลับ ERC-1155 เพื่อป้อนฟังก์ชัน depositAdditionalToFNFT อีกครั้ง (ดูรายละเอียดฟังก์ชัน _doSafeTransferAcceptanceCheck ด้านล่าง)
•quantity = 1
•amount = 1*1e18
•fnftId = 1
ใน depositAdditionalToFNFT ผู้โจมตีจะผ่านเข้ามา< fntfId.totalSupply(),เพราะปริมาณ
สุดท้าย ผู้โจมตีเรียกใช้ฟังก์ชันการถอน NFNFT เผา 360,001 02-FNFT และดึงเอา RENA ไป 360,001*1e18
การแก้ไขที่แนะนำ
2. the New Zero-day Vulnerability
ชื่อเรื่องรอง
ในขณะที่ทีม blockSecTeam กำลังวิเคราะห์รหัสของ Revest Finance ฟังก์ชัน handle MultipleDeposits ก็ดึงดูดความสนใจของเราได้
เมื่อผู้ใช้เรียกใช้ฟังก์ชัน depositAdditionToNFT เพื่อเพิ่มหลักประกัน ฟังก์ชันจะเปลี่ยน depositAmount ของ FNFT จากโค้ด เราพบว่าเมื่อ newFNFTId != 0 ฟังก์ชันนี้ไม่เพียงแต่เปลี่ยน depositAmount ของ FNFT ที่สอดคล้องกับ fnftId เท่านั้น แต่ยังเปลี่ยน depositAmount ที่สอดคล้องกับ newFNFTId อีกด้วย
ตามสามัญสำนึก เมื่อ newFNFTId !=0 ระบบควรบันทึกเฉพาะ depositAmount ที่สอดคล้องกับ newNFTId จำนวนเงินฝากที่สอดคล้องกับ fnftId ไม่ควรเปลี่ยนแปลง
เราคิดว่านี่เป็น Logic Bug ที่ร้ายแรงมาก ผู้โจมตีสามารถถอนสินทรัพย์ดิจิทัลออกจากระบบได้อย่างง่ายดายโดยใช้ช่องโหว่นี้ ภาพสามภาพต่อไปนี้อธิบายถึงหลักการของการโจมตีจำลอง
สมมติว่า fnftId ล่าสุด = 1
ขั้นแรก ผู้โจมตีเรียกใช้ฟังก์ชัน mintAddressLock และมิ้นต์สร้าง 01-FNFT จำนวน 360,000 รายการ ผู้โจมตีตั้งค่าจำนวนเงินเป็น 0 ดังนั้นเขาจึงไม่ต้องโอนสินทรัพย์ใดๆ ไปยังโปรโตคอล Reves Finance
หลังจากโรงกษาปณ์สิ้นสุดลง ผู้โจมตีมี 360,000 01-FNFT พร้อม depositAmount = 0
•fnftId = 1
•amount = 1 * 1e18
•quantity = 1
จากนั้นผู้โจมตีจะเรียกใช้ฟังก์ชัน depositAdditionalToFNFT ด้วยพารามิเตอร์ต่อไปนี้
โปรโตคอลโอนจำนวนผู้โจมตี * จำนวนโทเค็น นั่นคือ 1 * 1e18RENA
โปรโตคอลจะเบิร์น 01-FNFT หนึ่งตัวของผู้โจมตีและมิ้นท์ 02-FNFT หนึ่งตัวสำหรับมัน (สมมติว่า fnftId ล่าสุด = 2)
ตามตรรกะในฟังก์ชัน handleMultipleDeposits สินทรัพย์ที่มี fnftId = 2 จะตั้งค่า depositAmount เป็น 1.0*1e18
แต่สำหรับสินทรัพย์ที่มี fnftId = 1 เงินฝากจะถูกตั้งค่าเป็น 1.0*1e18 ซึ่งควรเป็น 0!
เห็นได้ชัดว่าการใช้วิธีนี้ในการโจมตีนั้นง่ายกว่าและตรงไปตรงมากว่าการโจมตีแบบกลับเข้าที่จริง
ข้อความ
ในการตอบสนองต่อช่องโหว่นี้ ทีม blockSecTeam ได้จัดเตรียมวิธีการแพตช์ที่สอดคล้องกัน
ชื่อเรื่องรอง
เนื่องจากสัญญาสองฉบับที่มีช่องโหว่ของ TokenVault และ FNFTHandler เก็บสถานะสำคัญไว้มากมายจึงไม่สามารถปรับใช้ซ้ำได้ในช่วงเวลาสั้นๆ Revest Finance จึงปรับใช้สัญญา Revest อีกครั้งอย่างเป็นทางการ (https://etherscan.io/address/ รหัส 0x36c2732f1b2ed69cf17133ab01f2876b614a2f27#) เวอร์ชันนี้จะปิดฟังก์ชันที่ซับซ้อนส่วนใหญ่เพื่อหลีกเลี่ยงการโจมตีเพิ่มเติม ฝ่ายโครงการจะย้ายสถานะและปรับใช้สัญญาคงที่อีกครั้งในอนาคต
ชื่อเรื่องรอง
การปรับปรุงความปลอดภัยของโครงการ DeFi ไม่ใช่เรื่องง่าย นอกจากการตรวจสอบโค้ดแล้ว เราเชื่อว่าชุมชนควรใช้วิธีเชิงรุกมากขึ้น เช่น การตรวจสอบโครงการและการเตือนล่วงหน้า และแม้แต่การบล็อกการโจมตีเพื่อทำให้ชุมชน DeFi ปลอดภัยยิ่งขึ้น (https://mp.weixin.qq.com/s/o41Da2PJtu7LEcam9eyCeQ).
อ้างอิง
*[1]: https://blocksecteam.medium.com/revest-finance-vulnerabilities-more-than-re-entrancy-1609957b742f
