ข้ามไปยังเนื้อหา

การโจมตีแบบ Reentrancy

การโจมตีแบบ reentrancy ใช้ external call หรือ callback เพื่อกลับเข้าสู่ logic ของสัญญาขณะที่ state ยังไม่สอดคล้องกัน บทความนี้อธิบายเส้นทางโจมตี การป้องกัน และขอบเขตการตรวจสอบ

อัปเดต

เพื่อการศึกษาเท่านั้น ไม่ใช่คำแนะนำการลงทุน ช่องโหว่ reentrancy อาจทำให้สินทรัพย์ของสัญญาสูญเสียอย่างรวดเร็วและย้อนกลับไม่ได้ และการป้องกันหรือการ audit เพียงอย่างเดียวไม่อาจพิสูจน์ว่าสัญญาปลอดภัย

คำตอบโดยตรง

การโจมตีแบบ reentrancy อาจเกิดขึ้นเมื่อ logic ของสัญญาเรียกโค้ดภายนอก แล้วผู้รับ call กลับมาก่อนการทำงานเดิมเสร็จและก่อน invariant ถูกคืนสภาพ หากยอดคงเหลือ share สิทธิ์ หรือราคาเก่ายังมองเห็นได้ callback อาจทำคำสั่งซ้ำหรือมีผลต่อคำสั่งอื่นโดยใช้ state ที่ไม่สอดคล้องกัน

callback ไม่จำเป็นต้องเข้าสู่ฟังก์ชันเดิมหรือโอน native currency เท่านั้น token hook, callback ของผู้รับ NFT, การเรียก vault หรือ strategy และ callback ของ flash loan ล้วนส่งการควบคุมไปยังโค้ดที่ไม่น่าเชื่อถือได้ Reentrancy อาจข้ามฟังก์ชัน สัญญา และโมดูล ส่วน read-only reentrancy อาจทำให้โปรโตคอลอื่นใช้ค่าชั่วคราว

กลไกการทำงาน

การโจมตีทั่วไปมีลำดับดังนี้:

  1. ผู้โจมตีเข้าสู่ฟังก์ชันที่เปลี่ยน state และผ่านการตรวจสอบเริ่มต้น
  2. สัญญาที่มีช่องโหว่เรียก address, token หรือ protocol ก่อนทำบัญชีของตนเสร็จ
  3. โค้ดของผู้โจมตีกลับเข้าสัญญาเดิมหรือสัญญาที่เชื่อมต่อ ขณะที่ state เก่ายังมองเห็นได้
  4. call ที่ซ้อนกันทำผลเดิมซ้ำหรือเปลี่ยน state ร่วม แล้วการทำงานย้อนกลับโดย invariant ถูกทำลายแล้ว

การส่งต่อการควบคุมภายนอกอาจชัดเจนแบบ low-level call หรือซ่อนอยู่หลัง token transfer, receiver hook, safe mint, strategy adapter หรือ arbitrary-call interface โดยทั่วไป revert จะย้อน call tree ที่เกี่ยวข้อง แต่ผู้โจมตีอาจจัดลำดับ call ซ้อนที่สำเร็จและกลับตามปกติหลังดึงมูลค่าหรือทำบัญชีเสียหาย

ควรใช้การป้องกันหลายชั้น:

  • ใช้ checks-effects-interactions โดยตรวจสอบก่อน บันทึก internal effects ที่เกี่ยวข้องทั้งหมด แล้วค่อยโต้ตอบภายนอกเป็นขั้นสุดท้าย
  • ใช้ reentrancy guard กับทุก entry point ที่ใช้ invariant ที่ป้องกันร่วมกัน ไม่ใช่เฉพาะฟังก์ชันที่มี call ชัดเจน
  • ใช้รูปแบบให้ผู้รับเป็นฝ่าย claim เมื่อเหมาะสม และลด call ไปยัง token, receiver, hook, proxy และ integration ที่ไม่น่าเชื่อถือ
  • กำหนด invariant ข้ามสัญญา และทดสอบ callback อันตราย เส้นทางข้ามฟังก์ชัน multicall ซ้อน และผู้ใช้ข้อมูลแบบ read-only
  • รวมการตรวจ implementation เข้ากับ monitoring, pause และ incident response เมื่อการออกแบบรองรับ

ตัวอย่าง

พิจารณา vault ที่ส่งมูลค่าแล้วจึงล้างยอดคงเหลือของผู้ใช้หลัง call:

function withdraw() external {
    uint256 amount = balances[msg.sender];
    require(amount > 0, "empty balance");

    (bool ok, ) = msg.sender.call{value: amount}("");
    require(ok, "transfer failed");

    balances[msg.sender] = 0;
}

ผู้รับได้การควบคุมขณะที่ balances[msg.sender] ยังเก็บจำนวนเดิม ฟังก์ชันรับสามารถเรียก withdraw() อีกครั้ง ผ่านเงื่อนไขเดิม และขอโอนเพิ่ม การอัปเดตยอดก่อน call ปิดช่องนี้ได้ และ guard ปฏิเสธการเข้าสู่แบบซ้อนได้ แต่ทั้งสองอย่างไม่พิสูจน์ว่าระบบทั้งหมดปลอดภัย เพราะ entry point หรือสัญญาที่เชื่อมต่ออื่นอาจเปิด invariant ที่ยังไม่สมบูรณ์เดียวกัน

ความเสี่ยง

  • guard ป้องกันฟังก์ชันหนึ่ง แต่ฟังก์ชันอื่นเปิดเผย state เดียวกัน
  • ลำดับ call ภายในถูกต้อง แต่ invariant ข้ามสัญญายังไม่สอดคล้องระหว่าง callback
  • token, receiver, callback ของ flash loan หรือ strategy adapter รันโค้ดภายนอกโดยไม่คาดคิด
  • ฟังก์ชัน view เผยแพร่ราคาหรืออัตราแลกเปลี่ยนชั่วคราวที่โปรโตคอลอื่นใช้ในธุรกรรมเดียวกัน
  • upgrade, module หรือการเปลี่ยน storage layout ข้ามหรือทำลาย lock เดิม
  • การทดสอบครอบคลุมการถอนแบบวนซ้ำ แต่พลาดเส้นทางข้ามฟังก์ชัน ข้ามสัญญา และ read-only

สำหรับผู้ใช้ ป้าย audit หรือ reentrancy guard เป็นหลักฐานว่ามี control ไม่ใช่การรับประกัน upgrade, integration และคำสั่งฉุกเฉินที่มีสิทธิ์อาจเปลี่ยนพื้นผิวการโจมตี ควรจำกัด approval และ exposure ตรวจสอบ implementation ที่ deploy แล้ว และอย่าสมมติว่าย้อนคืนความเสียหายได้

ความเข้าใจผิดที่พบบ่อย

  • Reentrancy หมายถึงการเรียกฟังก์ชันถอนเดิมซ้ำเท่านั้น การโจมตีอาจเข้าสู่ฟังก์ชันหรือสัญญาอื่น หรือเพียงเปิดข้อมูลที่ไม่สอดคล้องให้ผู้อ่าน
  • มีเพียงการโอน native currency ที่เรียก callback มาตรฐาน token, receiver hook และ integration ของ protocol ก็รันโค้ดภายนอกได้
  • guard หรือ checks-effects-interactions ทำให้สัญญาปลอดภัย ขอบเขต entry point ร่วมทั้งหมด และ invariant ข้ามสัญญายังต้องตรวจสอบ
  • audit ที่ผ่านแล้วตัดความเสี่ยง reentrancy ออก audit มีขอบเขตจำกัด การเปลี่ยนแปลงภายหลังและ integration ที่ไม่ผ่านการตรวจอาจสร้างเส้นทางใหม่

หัวข้อที่เกี่ยวข้อง

แหล่งข้อมูล

การนำทาง

ค้นหาในวิกิ...