การโจมตีด้วยการสร้างเหรียญไม่จำกัด (Infinite Mint) คืออะไรและทำงานอย่างไร?
การโจมตีด้วย Infinite Mint เกิดขึ้นเมื่อสัญญาโทเคนอนุญาตให้ผู้ไม่หวังดีสร้างเหรียญโดยไม่มีการอนุญาตที่ถูกต้อง โดยไม่มีการกำหนดขีดจำกัดสูงสุดของจำนวนเหรียญ หรือทั้งสองอย่างพร้อมกัน ผู้โจมตีจะสร้างยอดสมดุลในอัตราที่ต้องการ แล้วขาย โอน แลกเปลี่ยน หรือใช้เหรียญที่สร้างขึ้นเป็นหลักประกัน ก่อนที่ตลาดและการทำงานร่วมกันจะสามารถตอบสนองได้
ผลลัพธ์ไม่เพียงแค่เกิดภาวะเงินเฟ้อเท่านั้น การสร้างเหรียญไม่จำกัดสามารถทำลายมูลค่าการแลกเปลี่ยนของโทเคน ล้างความสมดุลใน池เงิน แล้วยังสร้างหลักประกันที่ไม่มีฐานในระบบกู้ยืมหรือคลังเก็บสินทรัพย์ การเกิดเหตุการณ์ Elrond ถูกบันทึกเป็นการเสียหายมูลค่า 113.0 ล้านดอลลาร์ในวันที่ 5 มิถุนายน 2022 จากเหตุการณ์ที่ถูกจัดเป็น Infinite Mint แสดงให้เห็นว่าการตั้งค่าการปล่อยเหรียญที่ผิดพลาดสามารถกลายเป็นการขาดทุนในระดับระบบนิเวศได้อย่างไร
การโจมตีด้วย Infinite Mint คืออะไรและทำงานอย่างไร?
การโจมตีแบบ Infinite Mint ใช้ช่องทางการออกโทเคนที่ไม่มีความน่าเชื่อถือในการพิสูจน์ว่าใครสามารถสร้างเหรียญและในจำนวนเท่าใด สาธารณะหรือฟังก์ชัน mint ที่ไม่ได้รับการป้องกันอย่างถูกต้อง อนุญาตให้ผู้ไม่หวังดีสร้างยอดสมดุลที่โปรโตคอลเห็นว่าเป็น ERC-20 ที่ถูกต้องตามกฎหมาย
ฟังก์ชัน mint ปลอดภัยเฉพาะเมื่อการอนุญาตให้เรียกใช้งาน การกำหนดเงื่อนไขการออกเหรียญ และการบันทึกยอดรวมสอดคล้องกับการออกแบบเชิงเศรษฐกิจของโปรโตคอลเท่านั้น หากผู้ใดสามารถเข้าถึงตรรกะ _mint ได้โดยไม่ได้รับอนุญาต หรือผู้ใช้ที่มีสิทธิพิเศษสามารถข้ามเพดานยอดรวมได้ มูลค่าความหายากของโทเคนก็เป็นแค่สมมุติฐานเท่านั้น
ลำดับการโจมตีแบบ Infinite Mint ทั่วไปมีดังนี้:
- ผู้ไม่หวังดีระบุเส้นทางการสร้างเหรียญที่สามารถเรียกใช้งนอกได้หรือข้ามการตรวจสอบสิทธิ์
- เรียกใช้ฟังก์ชันด้วยผู้รับและจำนวนเหรียญที่ควบคุมโดย attacker
- สัญญาเพิ่มยอดสมดุลของ attacker และโดยปกติจะเพิ่ม
totalSupplyด้วย - attacker โอนเหรียญที่สร้างเข้า Pool บน DEX, ตลาดให้กู้ยืม, สะพานเชื่อม หรือ Vault
- attacker แลกเหรียญปลอมเป็นสินทรัพย์จริง หรือใช้เป็นหลักประกัน
- ผู้ให้บริการสภาพคล่อง นายทุน และผู้ถือโทเคนจะรับความสูญเสียทางเศรษฐกิจนี้
ช่องโหว่ Infinite Mint ยิ่งอันตรายรุนแรงเมื่อยอดสมดุลของโทเคนถูก ยอมรับโดยสัญญาอื่น ๆ โดยไม่ตรวจสอบอย่างอิสระว่ายอดนั้นถูกสร้างขึ้นมาอย่างไร โปรโตคอลกู้ยืมหรือระบบอื่น ๆ ไม่แยกระหว่างเหรียญที่ถูกสร้างผ่านการปล่อยตามแผน และเหรียญที่ถูกสร้างผ่านช่องโหว่การควบคุมการเข้าถึง
แพทเทิร์นใน Solidity ที่ทำให้เกิดช่องโหว่ Infinite Mint มีอะไรบ้าง?
โดยทั่วไปช่องโหว่ Infinite Mint เกิดจากการขาดการอนุญาต การไม่กำหนดเพดานยอดรวม หรือการอนุญาตที่ดูถูกต้องแต่สามารถเข้าถึงได้ผ่านเส้นทางการบริหารที่ไม่ได้ตั้งใจ ตรวจสอบทุกฟังก์ชันที่สามารถเพิ่มยอดสมดุล ไม่ว่าจะโดยตรงหรือโดยอ้อม
ตัวอย่างที่เห็นได้ชัดคือฟังก์ชัน mint สาธารณะที่ไม่มีการจำกัดผู้เรียก:
function mint(address to, uint256 amount) external {
balanceOf[to] += amount;
totalSupply += amount;
}
ความเสี่ยงเดียวกันนี้ยังเกิดขึ้นเมื่อผู้พัฒนาตรวจสอบตัวตนผิด เช่น การใช้ tx.origin เพื่อกำหนดสิทธิ์การสร้างเหรียญก็ไม่ควร เนื่องจาก attacker อาจก่อให้เกิดการเรียกผ่านบัญชีที่ได้รับสิทธิ์โดยไม่ได้ตั้งใจ อีกตัวอย่างคือ การตรวจสอบว่าสัญญาได้รับสิทธิพิเศษหรือไม่ ก็ไม่สามารถยืนยันได้ว่าผู้เรียกเป็นผู้ที่ได้รับอนุญาตให้สร้างเหรียญจริงหรือไม่
สาเหตุที่พบบ่อยเพิ่มเติมได้แก่:
- ฟังก์ชัน
setMinterที่ใครก็สามารถเรียกใช้งานได้ ทำให้ attacker สามารถมอบสิทธิ์ในการสร้างเหรียญให้ตนเอง - Proxy ที่สามารถอัปเกรดซอฟต์แวร์ได้ อาจปลอดภัยที่สามารถเปลี่ยนแปลงข้อจำกัดในการสร้างเหรียญ
- สะพานเชื่อมหรือผู้จัดการข้ามเครือข่ายที่เชื่อถือข้อความใดก็ได้เป็นคำสั่งสร้างเหรียญ
- การจำกัดสูงสุดในฟังก์ชัน
mintเดียว แต่ไม่ในช่วงอื่น ๆ เช่นการเรียกรางวัลหรือฟังก์ชันการย้ายถ่ายโอน - ข้อผิดพลาดในการคำนวณเลขคณิต หรือหน่วยที่ใช้แปลงค่าซึ่งทำให้เพดานที่ตั้งไว้ไม่มีผล
- การบันทึกบัญชี Burn-and-Mint ที่ไม่ตรวจสอบการเผาเหรียญก่อนการออกเหรียญใหม่
ตัวแปร totalSupply เพียงอย่างเดียวยังไม่เป็นการควบคุมยอดรวม จำเป็นต้องมีการปฏิเสธทุกการสร้างเหรียญที่ทำให้ totalSupply + amount เกินเพดานสูงสุดที่ตั้งไว้ ซึ่งต้องเป็นกฎที่ใช้ครอบคลุมทุกเส้นทางการออกเหรียญ รวมถึงการกู้คืนจากฝ่ายบริหาร การสร้างเหรียญผ่านสะพาน การให้รางวัล และการย้ายถ่ายโอนต่าง ๆ
โค้ด Solidity สำหรับ Infinite Mint ที่อ่อนแอ และแก้ไขแล้วเป็นอย่างไร?
สัญญา Infinite Mint ที่อ่อนแอจะแสดงฟังก์ชันการออกเหรียญที่ไม่จำกัด การเรียกใช้นอกโดยบัญชีภายนอกใดก็ได้สามารถสร้างเหรียญได้ในจำนวนใดก็ได้โดยไม่มีการจำกัด
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract VulnerableInfiniteMintToken {
string public constant name = "Vulnerable Token";
string public constant symbol = "VULN";
uint256 public totalSupply;
mapping(address => uint256) public balanceOf;
event Transfer(address indexed from, address indexed to, uint256 amount);
function mint(address to, uint256 amount) external {
// อ่อนแอ: ผู้ใดก็สามารถสร้างเหรียญได้ตามชอบ
totalSupply += amount;
balanceOf[to] += amount;
emit Transfer(address(0), to, amount);
}
function transfer(address to, uint256 amount) external returns (bool) {
require(balanceOf[msg.sender] >= amount, "ยอดไม่เพียงพอ");
unchecked {
balanceOf[msg.sender] -= amount;
}
balanceOf[to] += amount;
emit Transfer(msg.sender, to, amount);
return true;
}
}
ส่วนเวอร์ชันที่แก้ไขแล้วจะต้องแยกผู้ดูแลระบบจากผู้สร้างเหรียญที่ได้รับอนุญาต และบังคับให้มีการตรวจสอบเพดานสูงสุดของยอดสมดุลก่อนที่จะเพิ่มจำนวนของเหรียญในทุกขั้นตอน เพเพียงแค่การตั้งค่าขีดจำกัดก็ไม่เพียงพอ การอนุญาตก็ต้องถูกจัดการอย่างเป็นระบบ ทั้งสองส่วนนี้ต่างมีหน้าที่รับผิดชอบที่แตกต่างกัน
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract CappedRoleMintToken {
string public constant name = "Capped Token";
string public constant symbol = "CAP";
address public immutable admin;
uint256 public immutable maxSupply;
uint256 public totalSupply;
mapping(address => bool) public isMinter;
mapping(address => uint256) public balanceOf;
event Transfer(address indexed from, address indexed to, uint256 amount);
event MinterUpdated(address indexed minter, bool allowed);
modifier onlyAdmin() {
require(msg.sender == admin, "ไม่ใช่ผู้ดูแลระบบ");
_;
}
modifier onlyMinter() {
require(isMinter[msg.sender], "ไม่ใช่ผู้สร้างเหรียญ");
_;
}
constructor(uint256 maxSupply_) {
require(maxSupply_ > 0, "เพดานเป็นศูนย์");
admin = msg.sender;
maxSupply = maxSupply_;
isMinter[msg.sender] = true;
}
function setMinter(address minter, bool allowed) external onlyAdmin {
isMinter[minter] = allowed;
emit MinterUpdated(minter, allowed);
}
function mint(address to, uint256 amount) external onlyMinter {
// แก้ไข: การออกเหรียญที่ได้รับอนุญาตต้องไม่เกินเพดานที่กำหนดไว้
require(totalSupply + amount <= maxSupply, "เกินเพดาน");
totalSupply += amount;
balanceOf[to] += amount;
emit Transfer(address(0), to, amount);
}
function transfer(address to, uint256 amount) external returns (bool) {
require(balanceOf[msg.sender] >= amount, "ยอดไม่เพียงพอ");
unchecked {
balanceOf[msg.sender] -= amount;
}
balanceOf[to] += amount;
emit Transfer(msg.sender, to, amount);
return true;
}
}
หากโปรโตคอลของคุณมีการสร้างเหรียญผ่านรางวัล การสะพานเชื่อม หรือการย้ายถ่ายโอนภายในคลังเก็บ ระบบตรวจสอบความปลอดภัยและการตรวจสอบความถูกต้องของแต่ละเส้นทางการออกเหรียญก่อนที่จะนำไปใช้งานจริงเป็นสิ่งที่ควรได้รับการตรวจสอบอย่างละเอียด
วิธีป้องกันการโจมตี Infinite Mint ใน Solidity?
การป้องกันการโจมตี Infinite Mint ต้องใช้แบบ layered ที่ประกอบด้วยการกำหนดอำนาจชัดเจน การบังคับใช้ข้อจำกัดด้านยอดรวม การจำกัดสิทธิ์ด้านบริหาร และการทดสอบว่าการสร้างยอดสมดุลเป็นไปตามกฎที่ตั้งไว้ วิธีการออกแบบที่เหมาะสมขึ้นอยู่กับว่าระบบมีการกำหนดจำนวนแบบคงที่, ควบคุมด้วยเพดาน, การปล่อยตามตาราง, รองรับหลักประกัน หรือเชื่อมต่อกับสะพานเชื่อม
| เทคนิค | สิ่งที่หยุดได้ | ค่าใช้จ่ายด้านแก๊สหรือความซับซ้อน | ข้อจำกัด |
|---|---|---|---|
Role-gated mint |
ผู้ไม่หวังดีเรียกสร้างเหรียญได้โดยอิสระ | การอ่านข้อมูลสถานะเพื่อเช็คสิทธิ์ | ผู้สร้างเหรียญที่ถูกแฮ็กอาจยังสร้างเหรียญได้ตามอิทธิพลของตนเอง |
| เพดานสูงสุดถาวร | การสร้างเหรียญเกินจำนวนที่กำหนดไว้ | การตรวจสอบเลขคณิตเพียงครั้งเดียวต่อการสร้าง | ไม่มีเพื่อป้องกันผู้ได้รับอนุญาตสร้างเหรียญเต็มเพดานก่อนกำหนด |
| แยกหน้าที่ผู้ดูแลและผู้สร้างเหรียญ | ผู้สร้างเหรียญสามารถมอบสิทธิ์ให้ตนเอง | โค้ดจัดการบทบาทเพิ่มเติม | ผู้ดูแลระบบยังคงเป็นขอบเขตความเชื่อถือสำคัญ |
| การบริหารแบบ multisignature หรือล่าช้าด้วยเวลา | การเปลี่ยนแปลงผู้สร้างเหรียญหรือการตั้งค่าการออกเหรียญด้วยคีย์เดียว | ความล่าช้าในการดำเนินงานและการบริหารจัดการ | ไม่สามารถแก้ไขตรรกะการสร้างเหรียญผิดพลาดที่ได้ประกาศแล้ว |
เส้นทาง _mint ภายในศูนย์กลาง |
การละเว้นตรวจสอบเพดานในฟังก์ชันการออกเหรียญรอง | ความพยายามในการ refactor | ผู้ตรวจสอบจะต้องตรวจสอบเส้นทางภายนอกเข้าสู่ _mint ให้ครบถ้วน |
| การเฝ้าระวังกิจกรรมการสร้างเหรียญผ่าน event | การตรวจจับผิดสังเกตได้ล่าช้า | โครงสร้างพื้นฐานสำหรับตรวจสอบภายนอก | การเฝ้าระวังไม่สามารถย้อนกลับเหรียญที่สร้างขึ้นมาแล้วได้ |
สำหรับ token ที่กำหนดจำนวนสูงสุด การออกแบบที่แข็งแกร่งที่สุดมักคือการสร้างเหรียญทั้งหมดในระหว่างการเปิดตัว และลบความสามารถในการสร้างเหรียญเพิ่มเติมในภายหลัง สำหรับ token ที่ปล่อยตามตาราง ให้กำหนดตารางการปล่อยให้ชัดเจน บังคับจำนวนรวมให้สอดคล้องในฟังก์ชันออกเหรียญภายใน ระบบ ส่วนฟังก์ชันภายนอกควรมีสิทธิ์อนุมัติแบบชัดเจน
แนวทางของ Soken จะถือว่าการสร้างเหรียญเป็นการสร้างสินทรัพย์ที่มีเส้นแบ่งขอบเขตชัดเจน ไม่ใช่ฟังก์ชันที่ทำได้เพียงเพื่อความสะดวก ทุกเส้นทางที่เพิ่มยอดสมดุลของโทเคนควรมีโมเดลอำนาจที่ชัดเจน ข้อจำกัดทางเศรษฐกิจที่แน่นอน และมีการทดสอบว่ามีอินแวเรียนต์ที่สามารถทดสอบได้
เหตุการณ์จริงใดบ้างที่ถูกจัดเป็น Infinite Mint?
เหตุการณ์ที่ถูกบันทึกว่ามี Infinite Mint แสดงให้เห็นว่าข้อผิดพลาดด้านการออกเหรียญส่งผลต่อสินทรัพย์ที่เป็นที่ยอมรับและโปรโตคอลใหม่บนหลายเชน เหตุการณ์ในตารางด้านล่างถูกจัดเป็น Infinite Mint ในฐานข้อมูล hacks ของ DefiLlama
| โปรโตคอล | วันที่ | มูลค่าที่เสียไป | ชนิดเชน |
|---|---|---|---|
| Elrond | 2022-06-05 | $113.0M | Elrond |
| Paid Network | 2021-03-05 | $27.0M | Ethereum |
| Gala | 2024-05-20 | $22.3M | Ethereum |
| Haven Protocol | 2021-07-03 | $8.2M | Haven |
| OcelotDex | 2024-09-20 | $4.9M | Ethereum |
| TSR | 2026-06-01 | $2.5M | BSC |
| Raydium AMM | 2026-06-10 | $1.3M | Solana |
| Flooring Protocol | 2026-06-08 | $500K | Ethereum |
เหตุการณ์เหล่านี้มีคุณสมบัติทางเศรษฐกิจหลักร่วมกันคือ เมื่อเหรียญปลอดหลักประกันเข้าสู่ตลาดแบบบูรณาการแล้ว การโจมตีจะลุกลามไปนอกสัญญาโทเคนโดยตรง Pool สภาพคล่องซื้อเหรียญปลอม ตลาดให้กู้ยืมอาจรับเหรียญเป็นหลักประกัน และสะพานเชื่อมหรือ Vault อาจกระจายยอดสมดุลไปสู่ระบบอื่น ๆ ต่อไป
คำถามสำคัญคือ แท้จริงแล้วคำถามกว้างกว่าการถามว่า ” attacker เรียก mint ได้ไหม?” คำถามที่ควรถามคือ “เส้นทางที่ไม่น่าเชื่อถือหรือเชื่อถือเกินไปใด ๆ สามารถทำให้โปรโตคอลรับรู้เหรียญที่ไม่มีหลักประกันเป็นสินทรัพย์ที่ถูกต้องได้หรือไม่?”
วิธีตรวจจับช่องโหว่ Infinite Mint ก่อนการเปิดใช้งานใน Solidity?
การตรวจจับช่องโหว่ Infinite Mint ต้องทำการแมปทุกการเปลี่ยนแปลงสถานะที่เพิ่มยอดสมดุล แล้วพิสูจน์ว่าทุกการเปลี่ยนแปลงนั้นเป็นการกระทำที่ได้รับอนุญาตและรักษาอินแวเรียนต์ของยอดรวมไว้ การตรวจสอบด้วยมือควรเริ่มจากสัญญาโทเคน แต่ต้องขยายไปถึงสะพานเชื่อม, Vault, ตัวแจกจ่ายรางวัล, โมดูลอัปเกรด และตัวช่วยบริหาร
รายการตรวจสอบสำหรับการรีวิว:
- ค้นหาทุกการเขียนข้อมูลเกี่ยวกับยอดสมดุล, หุ้นส่วน, ตัวนับยอดรวม และฟังก์ชัน
mintภายใน - ยืนยันว่าเส้นทางการออกเหรียญภายนอกมีการอนุญาตที่เหมาะสม
- ตรวจสอบให้แน่ใจว่าเส้นทางแต่ละเส้นใช้ข้อจำกัดเพดานหรือการปล่อยเหรียญโดยรวมเดียวกัน
- ตรวจสอบฟังก์ชันที่เพิ่ม ลบ หรือเปลี่ยนแปลงผู้สร้างเหรียญและผู้ดูแล
- ติดตามเส้นทางการอัปเกรด สะพานเชื่อม การย้ายถ่ายและ callback ที่อาจเข้าไปสู่ตรรกะ
mint - ค้นหา บัญชีที่มีสิทธิพิเศษสามารถเปลี่ยนแปลงเพดาน ผู้สร้าง หรือหน่วยการบัญชีได้โดยไม่คาดคิด
- ยืนยันว่า เหตุการณ์การสร้างเหรียญและ
totalSupplyสอดคล้องกับยอดสมดุลที่เกิดขึ้นจริง - ทดสอบการทำงานร่วมกันที่รับเหรียญเป็นหลักประกัน สภาพคล่อง หรือสินทรัพย์ Vault
Slither เป็นเครื่องมือที่ดีสำหรับ enumeration ฟังก์ชันที่สามารถเรียกใช้นอก ระบบ การเปลี่ยนแปลงสถานะที่มีสิทธิ์ และรูปแบบการควบคุมสิทธิ์ที่น่าสงสัย แต่ไม่สามารถรับประกันได้ว่าระบบของคุณมีโมเดลยอดรวมทางเศรษฐกิจที่ถูกต้องตามหลักการ การวิเคราะห์ด้วยมือจำเป็นสำหรับเส้นทางการสร้างเหรียญแบบข้ามสัญญาและความรับผิดชอบของสัญญาอื่น ๆ
Foundry fuzzing สามารถทดสอบได้ว่าผู้เรียกแบบสุ่มไม่สามารถสร้างเหรียญ และยอดรวมจะไม่เกินเพดาน ในการทดสอบ invariance นั้นมีประสิทธิภาพสูงเพราะสามารถแสดงคุณสมบัติด้านความปลอดภัยโดยตรง ตัวอย่างเช่น:
function invariant_supplyNeverExceedsCap() public view {
require(token.totalSupply() <= token.maxSupply(), "เกินเพดาน");
}
อีก invariance ควรตรวจสอบให้แน่ใจว่าสิ่งที่อนุญาตเท่านั้นที่สามารถเพิ่มยอดรวมได้ ในวิธีการตรวจสอบของ Soken ข้อนี้จะถูกทดสอบในทุกฟังก์ชันสาธารณะ ไม่ใช่แค่ mint สำหรับข้อมูลเพิ่มเติมด้านการควบคุมสิทธิ์ การบันทึกบัญชี และความเสี่ยงของสัญญาที่เกี่ยวข้อง ตรวจสอบได้ที่ Soken Hub
ข้อมูลเชิงลึกด้านความปลอดภัย: วิธีของ Soken ถือว่าทุกหน่วยของเหรียญใหม่เป็นหนี้สินที่โปรโตคอลสร้างขึ้น ถ้าสัญญาไม่สามารถพิสูจน์ได้ว่าใครอนุญาตให้สร้างขึ้นมาและทำไมยังอยู่ในโมเดลการออกเหรียญ การออกเหรียญนั้นเป็นขอบเขตความปลอดภัยที่ต้องออกแบบใหม่
การตรวจสอบสิทธิ์ตามบทบาทและการกำหนดเพดานยอดรวมช่วยเปลี่ยนการออกเหรียญที่ไม่มีข้อจำกัดให้เป็นกระบวนการที่มีขอบเขตและรับผิดชอบได้ แต่ต้องใช้อย่างครอบคลุมในทุกเส้นทางการสร้างเหรียญ เป้าหมายทางด้านวิศวกรรมคือการรวมศูนย์การสร้างยอดสมดุลและทำให้การข้ามไปได้เป็นไปไม่ได้ตั้งแต่ต้น
ก่อนเปิดตัวหรืออัปเกรด ควรเขียนข้อสมมุติฐานของยอดรวมที่ตั้งใจไว้เป็นภาษาธรรมดา นำไปใช้ในเส้นทางการสร้างเหรียญร่วมกัน และบังคับใช้ด้วยการทดสอบ invariants ของ Foundry เพื่อให้แน่ใจว่ายังคงปลอดภัย