ข้อสังเกตจากเหตุการณ์โจมตีที่เกิดขึ้นบนแพลตฟอร์ม Poly Network

Inspex
2 min readAug 10, 2021

ขั้นตอนต่อไปนี้จะเป็นขั้นตอนที่ทาง 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 ได้จะสามารถ execute putCurEpochConPubKeyBytes() 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

--

--

Inspex

Cybersecurity professional service, specialized in blockchain and smart contract auditing https://twitter.com/InspexCo