ขั้นตอนต่อไปนี้จะเป็นขั้นตอนที่ทาง Inspex ใช้ในการทำความเข้าใจเหตุการณ์ที่เกิดขึ้นกับ Poly Network โดยทางทีมจะเล่าเกี่ยวกับขั้นตอนที่ทางทีม Inspex ใช้เพื่อทำความเข้าใจเหตุการณ์และข้อสังเกตต่าง ๆ ที่ทางทีมพบ แต่จะไม่ใช่การวิเคราะห์ต้นตอของช่องโหว่หรือการโจมตีในครั้งนี้
สำหรับรายละเอียดการโจมตี สามารถอ่านได้จาก Twitter ด้านล่าง (เขียนดีมาก)
https://twitter.com/kelvinfichter/status/1425217046636371969
โดยทางทีม Inspex ได้ทำการวิเคราะห์จาก transaction ต่อไปนี้
https://www.bscscan.com/tx/0xd59223a8cd2406cfd0563b16e06482b9a3efecfd896d590a3dba1042697de11a
ขั้นตอนการวิเคราะห์
1. ทีมงาน Inspex เริ่มวิเคราะห์จาก address ที่มีการส่ง token กลับไปหาผู้โจมตี คือ 0x2f7ac9436ba4b548f9582af91ca1ef02cd2f1f03 ซึ่งเราพบว่า เป็น Lock Proxy contract โดยอ้างอิงจาก https://github.com/polynetwork/docs/blob/master/config/README.md
2. จากนั้นจึงใช้ Tenderly debugger เพื่อตรวจสอบ execution flow ของ 1 ใน transaction ที่เกี่ยวข้องกับเหตุการณ์ในครั้งนี้ และพบว่า 0x06af4b9f function ของ 0x2f7ac9436ba4b548f9582af91ca1ef02cd2f1f03 ถูกเรียกใช้งาน
https://dashboard.tenderly.co/tx/bsc/0xd59223a8cd2406cfd0563b16e06482b9a3efecfd896d590a3dba1042697de11a/debugger?trace=0.4
3. หลังจากได้ source code และ function signature ที่ถูกเรียกใช้งานแล้ว ทีมงาน Inspex ย้อนหาชื่อ function ของ signature 0x06af4b9f และพบว่าเป็น unlock()
function ใน LockProxy
contract (https://github.com/polynetwork/eth-contracts/blob/d16252b2b8/contracts/core/lock_proxy/LockProxy.sol#L102-L119) โดย function ดังกล่าวจะทำการ mint เหรียญปลายทางตามจำนวนเหรียญที่ถูก burn ใน chain ต้นทางจากการ bridge ซึ่ง function นี้ถูกเรียกใช้ในขั้นตอนสุดท้ายเพื่อดึงเหรียญไปที่ address ของผู้โจมตี
4. อย่างไรก็ตาม unlock()
function จะสามารถถูกเรียกได้เฉพาะจาก address ที่เก็บไว้ในตัวแปร managerProxyContract
เท่านั้น ซึ่งถูกจัดเก็บไว้ที่ storage slot 1 ของ address 0x2f7ac9436ba4b548f9582af91ca1ef02cd2f1f03
5. ทีมงาน Inspex ได้ทำการดึง storage slot 1 ของ address 0x2f7ac9436ba4b548f9582af91ca1ef02cd2f1f03 และพบว่าได้เป็นค่า address 0x7cea671dabfba880af6723bddd6b9f4caa15c87b ซึ่งจากการเทียบทีละไฟล์ใน GitHub กับ function signature ของ 0x7cea671dabfba880af6723bddd6b9f4caa15c87b พบว่าเป็น EthCrossChainManager
contract (https://github.com/polynetwork/eth-contracts/blob/master/contracts/core/cross_chain_manager/logic/EthCrossChainManager.sol)
6. จาก Tenderly เราพบว่า function ที่มี signature 0xd450e04c ของ 0x7cea671dabfba880af6723bddd6b9f4caa15c87b ถูกเรียกใช้งานโดยผู้โจมตี จากการย้อนหาชื่อ function พบว่าคือ verifyHeaderAndExecuteTx()
function (https://github.com/polynetwork/eth-contracts/blob/master/contracts/core/cross_chain_manager/logic/EthCrossChainManager.sol#L127-L173)
7. ถ้าผ่านเงื่อนไขต่าง ๆ ของ verifyHeaderAndExecuteTx()
function ตัว _executeCrossChainTx()
function จะถูกเรียกซึ่งจะสามารถใช้ในการ execution transaction ไปหา contract ต่าง ๆ ได้ ในกรณีนี้คือ unlock()
function
8. จากการวิเคราะห์ พบว่า verifyHeaderAndExecuteTx()
function จะมีการตรวจสอบ signature จากตัวแปร headerSig
โดยใช้คำสั่ง ECCUtils.verifySig()
9. ECCUtils.verifySig()
จะเป็นการตรวจสอบว่า header จะต้องถูก sign มาจาก Poly chain consensus node เป็นจำนวน n — ( n — 1) / 3
node (n คือจำนวน keeper ของ node ที่ถูก set ไว้ใน contract)
10. โดย keeper ของ node ต่าง ๆ จะถูกเก็บที่ contract 0x11e2A718d46EBe97645b87F2363AFE1BF28c2672 ซึ่งคือ (https://github.com/polynetwork/eth-contracts/blob/master/contracts/core/cross_chain_manager/data/EthCrossChainData.sol) ใน state ชื่อ ConKeepersPkBytes
11. จากการไปดึง state ConKeepersPkBytes
ที่ storage slot 3 ของ 0x11e2A718d46EBe97645b87F2363AFE1BF28c2672 จะได้ value เป็น 0x010000000000000014a87fb85a93ca072cd4e5f0d4f178bc831df8a00b00003a
โดย 8 byte แรกจะเป็นจำนวนของ keeper (0x0100000000000000) และเมื่อแปลงแบบ little endian แล้วจะได้ค่าเท่ากับ 1 ซึ่งหมายถึงมี 1 keeper ถูก set ไว้
12. owner ของ EthCrossChainData
contract (0x11e2A718d46EBe97645b87F2363AFE1BF28c2672) ก็เป็น EthCrossChainManager
contract (0x7cea671dabfba880af6723bddd6b9f4caa15c87b) เหมือนกันกับ LockProxy Contract
ข้อสังเกต
- การจะผ่านการ validate ของ
verifyHeaderAndExecuteTx()
function จำเป็นจะต้องมี private key ของ public key ที่ถูก set ในEthCrossChainData
contract เท่านั้น โดยจะสามารถถูก set ได้โดยใช้putCurEpochConPubKeyBytes()
function ที่จะสามารถ execute ได้เพียงแค่ contract owner เท่านั้น
(https://github.com/polynetwork/eth-contracts/blob/master/contracts/core/cross_chain_manager/data/EthCrossChainData.sol#L45-L48) - contract owner ของ
EthCrossChainData
contract เป็นEthCrossChainManager
contract ทำให้ถ้าใครก็ตามสามารถทำการส่ง cross-chain transaction ผ่านEthCrossChainManager
ได้จะสามารถ executeputCurEpochConPubKeyBytes()
function ทำการแก้ไขค่า keeper ที่เก็บในConKeepersPkBytes
state ได้
https://github.com/polynetwork/eth-contracts/blob/master/contracts/core/cross_chain_manager/data/EthCrossChainData.sol#L45-L48 - หลังจากการโจมตี keeper ที่ถูกเก็บใน
EthCrossChainData
contract มีแค่ 1 keeper เท่านั้น ทำให้คนที่มี private key เพียงแค่ 1 คน จะสามารถเรียกใช้งานverifyHeaderAndExecuteTx()
function ได้เลย
About Inspex
Inspex คือบริษัทที่เกิดจากการรวมตัวของทีมงานผู้มีประสบการณ์ทางด้าน cybersecurity ในหลากหลายสาขา เพื่อให้บริการต่าง ๆ ที่เกี่ยวข้องกับเทคโนโลยี blockchain และ smart contract โดยมีวัตถุประสงค์หลักเพื่อผลักดันระดับความมั่นคงปลอดภัยของแพลตฟอร์มต่าง ๆ รวมถึงภาพรวมของ blockchain ecosystem ด้วยการบริการที่มีคุณภาพ
สอบถามข้อมูลเพิ่มเติมได้ทาง Twitter, Telegram, contact@inspex.co