Storage ABIEncoderV2 Array bug and how Slither detect it
มาแกะ Slither กัน
ช่องโหว่ Storage ABIEncoderV2 Array คืออะไร ?
คือ bug ของ abi.encode โดยจาก code
expected output คือได้ decoded array ของ [[1,2], [3,4], [5,6]]
actual output คือได้ decoded array ของ [[1,2], [2,3], [3,4]]
โดย bug นี้จะเกิดใน compiler version 0.4.7–0.5.9 เท่านั้น
วิธีที่ Slither ใช้ในการ detect ช่องโหว่นี้
Slither คืออะไร ?
Slither คือ smart contract analyzer โดยคร่าวๆคือ
- เป็น static analysis tool
- พัฒนาด้วย python
เอาละ เราจะมาอธิบาย slither ไปพร้อมๆกับตัวอย่างเลยดีกว่า
case 1: simple state array decoded
เอาละ เรามาไล่ code แบบ top down กัน
อย่างแรกคือ ถ้าเราแยกชิ้นส่วน Slither ให้เหลือแค่ code ที่ต้องใช้เพื่อตรวจจับ ABIEncoderV2 Array จะออกมาเป็นยังไง ?
โอเค ตอนนี้เราแกะมาเฉพาะ code ส่วน detect ช่องโหว่นี้ได้ละ ผลลัพธ์การ detect จะดูยากหน่อย เพราะเราไม่ได้เอา code ส่วน format result มาด้วย
เอาละ แล้ว slither.register_detector(detectors[‘abiencoderv2-array’]) ไปเอา code มาจากส่วนไหน ?
ไฟล์นี้จะเป็น logic หลักของการ detect bug นี้เลย มาดู logic กัน
ขั้นแรกเป็นการตรวจเช็ค version compiler ว่าเป็น version ที่มี bug นี้รึเปล่า
note: Slither สามารถเช็ค version compiler ได้ด้วย self.compilation_unit.solc_version จาก class detector ต่างๆ
ต่อมาเป็นการเช็คว่ามีการใช้ pragma experimental ABIEncoderV2 รึเปล่า ซึ่งถ้าไม่ได้ใส่มาแสดงว่าไม่ได้ใช้ feature ABIEncoderV2 แสดงว่าไม่มี bug นี้
งั้นแปลว่า ถ้าไม่ได้ใช้ pragma experimental ABIEncoderV2 แล้ว abi.encode ของ version 0.4.7–0.5.9 จะไม่มี bug นี้
note: Slither สามารถเช็ค pragma ได้ด้วย self.compilation_unit.pragma_directives
ต่อมาจะเป็นการไล่ใส่ แต่ละ contracts ในไฟล์ sol ไปเช็คช่องโหว่
note: Slither สามารถดึงค่าแต่ละ contract ได้ด้วย self.contracts
โอเค การ detect ช่องโหว่ จะเริ่มจากการไล่ functions และ modifier ของ contract นั้น (ไม่ต้องไล่ดู state variable เพราะ ถ้าเป็น internal ก็ต้องเรียกใช้ใน function อยู่ดี แต่ถ้าเป็น public ก็จะมี function default สำหรับการ read ให้เหมือนกันอยู่ดี)
มาลองดู function และ modifier ที่ Slither ดึงมาได้กัน
slitherConstructorVariables มาจากไหน ? อันนี้จะเริ่มเข้าสู่เบื้องหลังของ Slither ละ
จริงๆแล้ว Slither ทำงานโดย
solidity code => แปลงเป็นภาษา SlithIR => วิเคราะห์ช่องโหว่จาก code ภาษา SlithIR
มาลองแปลง code solidity เป็น slithIR กัน
จาก code
ใช้คำสั่ง
slither <file.sol> --print slithir
โดยหนึ่งใน feature ของภาษา SlithIR คือการ list function ของ Solidity
โอเค list function และ modifier มาได้แล้วยังไงต่อ ?
ต่อมาจะเป็นการไล่ code ไปทีละบรรทัด ของแต่ละ function
โดยเราจะไม่ได้ไล่ code solidity ตรงๆ แต่จะใช้ node จาก SlithIR ซึ่งทำหน้าที่แทนแต่ละบรรทัดบน solidity
โอเคเริ่มจาก function bad2
จาก code
แปลงเป็น slithIR
โอเคจาก expression: b = abi.encode(bad_arr) จะนำไปวิเคราะห์ช่องโหว่ได้ยังไง ?
SlitherIR ใช้สิ่งที่เรียกว่า IRs ซึ่งเป็นการแยก expression ของภาษา solidiy ออกเป็นคำสั่งย่อยๆ เช่นจากรูป จะแยกได้เป็น 2 IRs
โดยเทคนิคนี้เรียกว่า SSA ถ้าอธิบายง่ายๆคือ code ที่แต่ละตัวแปร มีค่าได้แค่ค่าเดียว ไม่สามารถเปลี่ยนแปลงได้ ตัวอย่าง code ที่ simple กว่านี้ก็เช่น
จาก expresssion
x = 3
y = x++
x = y + x
แปลงเป็น SSA form ได้เป็น
x1 = 3
y1 = x1
x2 = x1 + 1
x3 = y1 + x2
โอเคกลับมาที่แต่ละ ir
เริ่มจาก ir แรก
ซึ่งเป็นการเช็คการเรียก function abi.encode ดังนั้น ir นี้ก็จะผ่านการเช็คเงื่อนไขด่านนี้ ด้วยการ short circuit
แต่คำถามคือ
abi.encode() ถูกเรียกด้วย array ของ array รึเปล่า ซึ่งนำมาสู่เงื่อนไขถัดไป
โดย args ของ abi.encode จะต้องเป็น array ของ array หรือ array ของ struct ซึ่งจาก code เราเป็น array ของ array
และเงื่อนไขสุดท้ายคือ args นั้นเป็น state variable หรือ local variable ที่มี flag is_storage
อันนี้เราเข้าเงื่อนไข state variable … local variable ที่มี flag is_storage ต้องเอาตัวอย่างที่เข้าไขเงื่อนไขมาวิเคราะห์อีกที
โอเค โดยสรุปแล้ว จาก code solidity ด้านบน
แปลงเป็น slithIR
จะเจอช่องโหว่ Storage ABIEncoderV2 Array ที่ function bad2 ใน expression b = abi.encode(bad_arr)
โอเค น่าจะประมาณนี้ก่อน จริงๆยังมี alternative case ของ ช่องโหว่ นี้ที่ตรงตาม logic ระบุไว้อีก แต่ยังมีอีกหลายช่องโหว่เลยเนี่ยสิ งั้นครั้งหน้า ไปช่องโหว่อื่นกันครับ
reference: