1. การจัดเก็บ/การกำหนด/การลบตัวแปร:
ชื่อเรื่องรอง
1. อย่าเก็บข้อมูลที่ละเอียดอ่อนใด ๆ ไว้ในห่วงโซ่
รายละเอียดช่องโหว่:ความโปร่งใสบนพื้นฐานบล็อคเชนข้อมูลสัญญาใด ๆ ที่ใช้งานบนห่วงโซ่นั้นโปร่งใสและมองเห็นได้
แม้แต่สำหรับตัวแปรที่แก้ไขส่วนตัว เนื่องจากการเปิดเผยส่วนตัวมีไว้สำหรับฟังก์ชันและสัญญาภายนอกเท่านั้น ผู้ใช้สามารถรับค่าเหล่านี้ได้โดยการดึงข้อมูลในห่วงโซ่ ในกรณีนี้ การดำเนินการรักษาความลับใด ๆ ที่คาดว่าจะได้รับการรับประกันโดยการปรับเปลี่ยนส่วนตัวบนเครือข่ายนั้นไม่ปลอดภัย
contract Eocene{
mapping(address => bytes 32) candidate;
uint private seed = 0x 12341 3d;
function select() public{
bytes 32 result = keccak 256(abi.encodePacked(seed));
if(result == candidate[msg.sender]){
payable(msg.sender).transfer( 1 ether);
}
}
}
หากมีรหัสชิงโชคอย่างง่ายดังต่อไปนี้:
แม้ว่า Seed จะได้รับการประกาศเป็นตัวแปรส่วนตัวแล้ว ทุกคนสามารถดึงค่า Seed บนเชนผ่านที่อยู่ของสัญญาและตำแหน่งสล็อตของ Seed เพื่อคำนวณค่าที่สอดคล้องกันเพื่อรับ eth
อย่าเก็บค่าคีย์ใด ๆ สำหรับการตรวจสอบในสัญญา จัดเก็บค่าคีย์นอกเชน และใช้เฉพาะลอจิกการตรวจสอบที่สอดคล้องกันในเชน
ชื่อเรื่องรอง
2. ให้ความสนใจกับค่าเริ่มต้นของตัวแปร
รายละเอียดช่องโหว่:
ในของแข็ง ค่าเริ่มต้นของตัวแปรคือ 0/เท็จ ในกรณีนี้ หากไม่คำนึงถึงอิทธิพลของค่าเริ่มต้นของตัวแปรเมื่อทำการตัดสินตามตัวแปรบางตัว อาจทำให้เกิดปัญหาด้านความปลอดภัยที่เกี่ยวข้องได้
contract Eocene{
mapping(address => bool) unlocked;
uint averageDrop;
address token;
function setAverageDrop() public {
averageDrop = 1000;
}
function drop() public {
if(unlocked[msg.sender] == false){
ERC 20(token).transfer(msg.sender, averageDrop);
}
}
}
พิจารณารหัสปลดล็อก airdrop ต่อไปนี้:
ความตั้งใจดั้งเดิมของสัญญาคือการแจกจ่ายโทเค็นไปยังที่อยู่ที่ไม่ได้ล็อกทั้งหมด แต่ไม่สนใจว่าในความแข็งแกร่ง ค่าเริ่มต้นของตัวแปรทั้งหมดคือ 0/เท็จ ในประเภทการแมป คีย์จะใช้สำหรับการประกบกับสล็อตเท่านั้น และที่อยู่ที่ตรงกับคีย์ในที่เก็บข้อมูลจะคำนวณผ่าน keccak 256 ซึ่งหมายความว่าที่อยู่ใดๆ ไม่ว่าจะเริ่มต้นหรือไม่ก็ตาม จะมีที่เก็บข้อมูลที่สอดคล้องกัน และค่าเริ่มต้น มักจะเป็น 0/เท็จ
อย่าใช้วิจารณญาณโดยพิจารณาจากค่าเริ่มต้นของตัวแปรไม่ว่าในกรณีใดๆ โดยเฉพาะในตัวแปรตามประเภทการแมป และป้องกันปัญหาดังกล่าวอย่างเคร่งครัด
ชื่อเรื่องรอง
3. ลบค่าประเภท struct ที่ไม่ได้ใช้แล้ว
รายละเอียดช่องโหว่:
สำหรับประเภทการแม็ปใดๆ เมื่อประเภทฟิลด์ค่าเป็นโครงสร้างและไม่ต้องการค่าที่เกี่ยวข้องอีกต่อไป ควรใช้การตั้งค่าลบเพื่อลบค่า มิฉะนั้น ค่าจะยังคงอยู่ในสล็อตที่เกี่ยวข้อง
contract Eocene{
struct Stake{
uint amount;
uint needReceive;
uint startTime;
}
mapping(address => Stake) stakes;
mapping(address => bool) staker;
function getStake() public{
Stake memory _stake = stakes[msg.sender];
(msg.sender).transfer(_stake.needReceive);
staker[msg.sender] = false;
// delete stakes[msg.sender] // need do but don't
}
function calReceive() public{
require(staker[msg.sender],'not staker');
stakes[msg.sender].needReceive = stakes[msg.sender].amount * (block.time - stakes[msg.sender].startTime);
stakes[msg.sender].amount = 0;
}
}
พิจารณารหัสของแบบฟอร์ม:
รหัสสัญญาด้านบนคำนวณจำนวน eth ที่ได้รับตามจำนวนการจำนำและเวลาการจำนำ แต่หลังจากการจำนำเสร็จสิ้น จะมีการตั้งค่าเฉพาะค่าของ staker[msg.sender] เป็นเท็จ และค่าเดิมพันที่สอดคล้องกัน[msg.sender] ยังคงมีอยู่ ดังนั้นผู้โจมตีสามารถเรียกฟังก์ชัน getStake() เพื่อรับ eth ได้โดยไม่มีข้อจำกัด
มาตรการซ่อมแซม:แน่นอน โค้ดข้างต้นยังมีปัญหาเสริมอื่น ๆ ที่นำไปสู่การมีอยู่ของช่องโหว่ แต่คุณควรตระหนักว่ามิฉะนั้น คู่ของค่าจะมีอยู่ในช่องที่เกี่ยวข้องเสมอ
2. คำจำกัดความของฟังก์ชัน:
ชื่อเรื่องรอง
1. การเปิดเผยฟังก์ชันที่ประกาศซึ่งต้องแสดง
รายละเอียดช่องโหว่:การมองเห็นเริ่มต้นของฟังก์ชั่นเป็นแบบสาธารณะสำหรับฟังก์ชันใด ๆ จะต้องประกาศการเปิดเผยอย่างชัดเจน
เพื่อป้องกันการมีอยู่ของช่องโหว่ที่เกิดจากความประมาทเลินเล่อ โดยเฉพาะอย่างยิ่งเมื่อฟังก์ชันซ้อนกันหลายชั้นเพื่อเรียกใช้ฟังก์ชันพื้นฐาน เพื่อป้องกันไม่ให้ฟังก์ชันพื้นฐานถูกมองเห็นอย่างไม่ถูกต้องเนื่องจากความประมาทเลินเล่อ
contract Eocene{
mapping(address => bool) whitelist;
function _a() {
payable(msg.sender).transfer( 1 ether);
}
function a() public{
require(whitelist[msg.sender],'not in whitelist');
_a();
}
}
พิจารณาตัวอย่างรหัสที่มีช่องโหว่ต่อไปนี้:
ฟังก์ชัน a() จำกัดที่อยู่รายการที่อนุญาตพิเศษผ่าน need และโอนเงินไปยังที่อยู่ที่เกี่ยวข้องหลังจากผ่านไป ภายใต้สถานการณ์ปกติ _a() ไม่ควรถูกเรียกจากภายนอก แต่ในที่นี้ เนื่องจากการมองเห็นของ _a() ไม่ได้ถูกประกาศอย่างชัดเจน ถือเป็นสาธารณะสามารถเรียกโดยตรงจากภายนอก
ประกาศการมองเห็นของฟังก์ชันทั้งหมดอย่างชัดเจน โดยเฉพาะอย่างยิ่งสำหรับฟังก์ชันที่ไม่สามารถเรียกโดยตรงจากภายนอกได้ จะต้องประกาศอย่างชัดเจนว่าเป็นแบบป้องกันหรือแบบส่วนตัว
ชื่อเรื่องรอง
2. ฟังก์ชั่นการโจมตีซ้ำ
รายละเอียดช่องโหว่:
เช่นเดียวกับการทำงานใดๆ คุณต้องพิจารณาถึงปัญหาที่อาจเกิดขึ้นหลังจากการกลับเข้าที่ใหม่ Reentrancy ในที่นี้รวมถึงปัญหา Reentrancy ทั้งหมดที่เกิดจากการโทรจากภายนอก เช่น โอน/ส่ง/โทร/staticall
contract Fund {
mapping(address => uint) shares;
function withdraw() public {
if (payable(msg.sender).send(shares[msg.sender]))
shares[msg.sender] = 0;
}
}
พิจารณารูปแบบรหัสต่อไปนี้:
สำหรับสัญญาอุทธรณ์ เมื่อ msg.sender เป็นอันตราย อาจทำให้ msg.sender ดึงยอดคงเหลือของสัญญาปัจจุบันทั้งหมดโดยไม่จำกัด แต่เราต้องตระหนักด้วยว่าเมื่อเรียกใช้การโอน/ส่ง/โทร/staticall และฟังก์ชันสัญญาภายนอกใดๆ อาจทำให้เกิดปัญหาในการกลับเข้ามาใหม่ได้
ตามรายละเอียดการใช้งานเฉพาะของสัญญา การใช้งานตัวแปรหลักสามารถแก้ไขได้ก่อน ตัวอย่างเช่น ในสัญญาอุทธรณ์ สามารถบันทึกมูลค่าของหุ้น [msg.sender] ก่อน แล้วจึงดำเนินการส่งได้ หลังจากตั้งค่าการแชร์ [msg.sender] เป็น 0 แน่นอนว่าสามารถรับรู้ได้ผ่านการรวมกันของตัวแปรส่วนกลางและตัวตกแต่ง
3. ปฏิสัมพันธ์ภายนอก
ชื่อเรื่องรอง
1. จำกัดที่อยู่และรายการฟังก์ชันของการโทรภายนอก
รายละเอียดช่องโหว่:
สำหรับการเรียกใช้ฟังก์ชันภายนอก ภายใต้สถานการณ์ที่เหมาะสม ที่อยู่สัญญาและฟังก์ชันสัญญาที่จะเรียกใช้ต้องถูกจำกัด
contract Eocene{
function callExt(address _target, bytes calldata data) public{
_target.call(data);
}
function delegateCallExt(address _target, bytes calldata data) public{
_target.delegatecall(data);
}
}
พิจารณารูปแบบรหัสต่อไปนี้:
ฟังก์ชัน callExt ใช้เพื่อเรียกที่อยู่ของฟังก์ชันใด ๆ ในกรณีนี้ มันง่ายที่จะทำให้เกิดปัญหาการกลับเข้ามาใหม่ เมื่อสัญญามี Token ในกระเป๋าเงินใด ๆ ก็สามารถเรียกฟังก์ชันการโอนของ Token ที่เกี่ยวข้องได้โดยตรงผ่าน ฟังก์ชั่นนี้ เดิน
อย่างไรก็ตาม หากไม่มีข้อจำกัดเมื่อเรียกใช้ฟังก์ชัน delegateCallExt อาจทำให้สัญญาถูกทำลายโดยตรง ส่งผลให้มีการโอนยอดคงเหลือที่อยู่สัญญาทั้งหมดและสัญญาถูกทำลาย
สำหรับการเรียกไปยังสัญญาภายนอกใดๆ ขั้นแรกให้พิจารณาว่าที่อยู่สามารถถูกจำกัดโดยรายการที่อนุญาตหรือไม่ และพิจารณาต่อไปว่าสามารถจำกัดชื่อฟังก์ชันของที่อยู่ที่ระบุได้หรือไม่
ชื่อเรื่องรอง
2. เมื่อใช้ call, send, delegatecall, staticcall การตัดสินการโทรจากภายนอกไม่ควรขึ้นอยู่กับข้อยกเว้นเท่านั้น แต่ยังตัดสินจากค่าที่ส่งคืนด้วย
รายละเอียดช่องโหว่:
ฟังก์ชันการอุทธรณ์ไม่ทำให้เกิดการย้อนกลับเนื่องจากข้อผิดพลาดภายใน แต่จะส่งคืนการย้อนกลับเท่านั้น ทุกครั้งที่ใช้งาน ต้องใช้ค่าส่งคืนของฟังก์ชันเพื่อระบุว่าการดำเนินการสำเร็จหรือไม่
contract Eocene{
address token; //any token address
function deposit(uint amount) public{
token.call(abi.EncodeWithSignature("transferfrom(address from, address receipt, uint amount)"), msg.sender, address(this), amount);
mint(msg.sender, amount);
}
}
พิจารณาโค้ดตัวอย่างต่อไปนี้:
ในฟังก์ชัน ฝาก () สัญญาจะพยายามถ่ายโอนโทเค็นที่ระบุของ msg.sender ไปยังที่อยู่ปัจจุบันก่อน หลังจากโอนสำเร็จ จะสร้างเหรียญปัจจุบันสำหรับ msg.sender อย่างไรก็ตาม เนื่องจากฟังก์ชัน .call จะไม่ย้อนกลับการทำธุรกรรมทั้งหมดเมื่อล้มเหลว แม้ว่าจะไม่สามารถโอนสกุลเงินใดๆ จาก msg.sender ไปยังที่อยู่ปัจจุบันได้ ฟังก์ชันนี้จะยังคงสร้างสกุลเงินปัจจุบันของจำนวนเงินเป็น msg.sender
การตัดสินผลการดำเนินการของ call, send, delegatecall และ staticcall จะต้องขึ้นอยู่กับค่าที่ส่งกลับ ไม่ใช่ขึ้นอยู่กับว่าจะคืนค่าหรือไม่
ประการที่สี่ การควบคุมการเข้าถึง:
ชื่อเรื่องรอง
1. ไม่มีการตรวจสอบตัวตนตาม tx.origin
รายละเอียดช่องโหว่:อย่าตรวจสอบสิทธิ์ตาม tx.origin
tx.origin เป็นผู้ริเริ่มการทำธุรกรรมทั้งหมดและจะไม่เปลี่ยนแปลงเมื่อมีการเรียกซ้ำของสัญญา การรับรองความถูกต้องใด ๆ ที่ใช้ tx.origin ไม่สามารถรับประกันได้ว่า tx.origin เป็น msg.sender การรับรองความถูกต้องตาม tx.origin ยังช่วยเพิ่มความปลอดภัยของบัญชีผู้ใช้อีกด้วย
contract Eocene{
mapping(address=>bool) whitelist;
function freeDeposit() public{
require(whitelist[tx.origin],'not in whitelist');
payable(msg.sender).transfer( 1 ether);
}
}
พิจารณาตัวอย่างช่องโหว่ต่อไปนี้:
เมื่อที่อยู่ใด ๆ ในรายการที่อนุญาตถูกชักจูงโดยลิงก์ฟิชชิ่งเพื่อเรียกที่อยู่และฟังก์ชันของสัญญาที่เป็นอันตรายซึ่งดูเหมือนไม่เป็นอันตราย และที่อยู่ที่เป็นอันตรายเรียกฟังก์ชัน freeDeposit ของโค้ดตัวอย่าง ทรัพย์สินที่ควรเป็นของที่อยู่รายการที่อนุญาตจะถูกโอนไปยัง ที่อยู่สัญญาที่เป็นอันตราย
มาตรการซ่อมแซม:ไม่มีการรับรองความถูกต้องตาม tx.origin หรือสำหรับรหัสอุทธรณ์ เมื่อเปลี่ยนเป็น payable(tx.origin).transfer(1 ether) ก็จะไม่ทำให้เกิดปัญหาแต่ใช้ need(whitelist[msg.sender],'not in whitelist'); ในการตัดสิน
ชื่อเรื่องรอง
2. อย่าตัดสินบัญชี eos จากค่าที่ส่งคืนของ extcodesize
รายละเอียดช่องโหว่:ในขั้นตอนการเริ่มต้นของรหัสสัญญา แม้ว่าที่อยู่จะเป็นที่อยู่ของสัญญา ค่าที่ส่งคืนของ extcodesize จะเป็น 0
หากตัดสินตามมูลค่าที่ส่งคืน ผลลัพธ์ที่ได้จะไม่ถูกต้อง
contract Eocene{
function withdraw() public{
uint size;
assembly {
size := extcodesize(caller())
}
require(size== 0,"not eos account");
msg.sender.transfer( 1 ether);
}
}
พิจารณารูปแบบรหัสต่อไปนี้:
ในฟังก์ชั่นการถอนของสัญญาการอุทธรณ์ หวังว่าบัญชี EOS เท่านั้นที่สามารถรับโทเค็นผ่านค่าส่งคืน extcodesize ได้ แต่ไม่สนใจว่าเมื่อสัญญาเริ่มต้น ค่าส่งคืน extcodesize สำหรับที่อยู่สัญญาจะเป็น 0 เช่นกัน เป็นผลให้การตัดสินไม่ถูกต้อง และที่อยู่ใด ๆ ก็สามารถรับโทเค็นจากสัญญาได้
อย่าตัดสินโดยพิจารณาว่าที่อยู่ภายนอกเป็นที่อยู่ของสัญญาหรือไม่ และพยายามตรวจสอบให้แน่ใจว่ารหัสสัญญาทำงานได้ตามปกติภายใต้บัญชีประเภทใดก็ได้
5. การดำเนินการทางคณิตศาสตร์
ชื่อเรื่องรอง
1. เมื่อดำเนินการตัวเลขใด ๆ ให้พิจารณาปัญหาล้น
รายละเอียดช่องโหว่:
ปัญหาโอเวอร์โฟลว์หมายถึงปัญหาโอเวอร์โฟลว์ที่เกิดขึ้นเมื่อสัญญาดำเนินการเกี่ยวกับจำนวนเต็ม เหตุผลหลักคือตัวเลขประเภทใดๆ มีความยาวสูงสุด เมื่อการดำเนินการของจำนวนเต็ม 2 จำนวนเกินค่าสูงสุด ส่วนที่เกินจะถูกตัดทอน ทำให้เกิดปัญหา
contract Eocene{
mapping(address=>uint) balanceof;
function withdraw(uint amount) public{
payable(msg.sender).transfer(amount);
balanceof[msg.sender] = balanceof[msg.sender]-amount;
require(balanceof[msg.sender] >= 0,'not enough balance');
}
}
พิจารณารูปแบบรหัสต่อไปนี้:
สำหรับฟังก์ชันการอุทธรณ์ ให้พิจารณาว่าเมื่อยอดคงเหลือ[msg.sender] <จำนวนเงิน เนื่องจากประเภทยอดคงเหลือถูกจำกัดไว้ที่จำนวนเต็มที่ไม่ได้ลงนาม ผลการคำนวณทั้งหมดจะส่งผลให้มีค่าเป็นลบของประเภท int และเมื่อแปลงเป็นประเภท uint ก็จะ เป็นค่าบวกที่สูงมาก ในขณะนี้ ข้อจำกัดของความต้องการถูกข้ามไป และผู้โจมตีสามารถขโมยโทเค็นจำนวนเท่าใดก็ได้จากสรุปสัญญา
มาตรการซ่อมแซม:เพื่อให้แน่ใจว่าผลการคำนวณขั้นสุดท้ายไม่ทำให้เกิดการล้น
ชื่อเรื่องรอง
2. เมื่อดำเนินการเกี่ยวกับจำนวนเต็ม ให้ใช้ประเภท int อย่างระมัดระวัง
รายละเอียดช่องโหว่:
เมื่อทำการคำนวณใดๆ ด้วยประเภทจำนวนเต็ม ให้ระวังการแปลงประเภท uint เป็นประเภท int สำหรับการคำนวณ เว้นแต่คุณต้องการการดำเนินการประเภทนี้ เนื่องจากเมื่อแปลงจำนวนเต็มประเภท uint เป็นประเภท int บางกรณีของการล้นสำหรับประเภท uint จะไม่ถูกต้องในประเภท int
contract Eocene{
int public result;
uint public uresult;
function cal(uint _a, uint _b) public{
result = int(_a)-int(_b);
uresult = uint(result);
}
}
พิจารณารูปแบบรหัสต่อไปนี้:
เมื่อคอมไพล์ด้วย solidity เวอร์ชัน 0.8.0 หรือใหม่กว่า หากคุณเรียก `cal( 0, 1)` แม้ว่า ` 0-1` จะทำให้เกิดโอเวอร์โฟลว์ใน uint แต่จะไม่ทำให้เกิดการโอเวอร์โฟลว์ในการคำนวณ int ชนิดย้อนกลับ (เนื่องจาก ผลลัพธ์ของ 0-1 อยู่ในช่วงประเภท int) และเมื่อค่าผลลัพธ์ถูกแปลงเป็นประเภท uint จะเป็นค่าผลลัพธ์หลังจากการคำนวณประเภท uint ล้น ซึ่งนำไปสู่ปัญหาโอเวอร์โฟลว์โดยปลอมตัว
แต่ควรสังเกตว่าหากเรียก cal(type(int).min, type(int).max) ที่นี่ การย้อนกลับจะยังคงถูกทริกเกอร์ เนื่องจากการคำนวณจำนวนเต็มในขณะนี้ยังเกินช่วงของประเภท int
มาตรการซ่อมแซม:กำลังดำเนินการ. หากการดำเนินการจำนวนเต็มจำเป็นต้องโอเวอร์โฟลว์ ให้ลองใช้การยกเลิกการเลือกเพื่อรวมการดำเนินการประเภท uint
ชื่อเรื่องรอง
3. ในการดำเนินการใดๆ ที่อาจสูญเสียความแม่นยำ ให้ป้องกันการสูญเสียความแม่นยำที่ยอมรับไม่ได้โดยการขยาย
รายละเอียดช่องโหว่:
เมื่อดำเนินการเกี่ยวกับจำนวนเต็ม ปัญหาที่อาจเกิดขึ้นจากการสูญเสียความแม่นยำจะถูกพิจารณา และความแม่นยำของมันจะขยายออกไป
contract Eocene {
uint totalsupply;
mapping(address=>uint) balancesof;
uint BasePrice = 1 e 16;
function mint() public payable {
uint tokens = msg.value/BasePrice;
balancesof[msg.sender] += tokens;
totalsupply += tokens;
}
}
พิจารณารหัสต่อไปนี้ของแบบฟอร์ม:
เมื่อพิจารณาถึงสัญญาการอุทธรณ์ จำนวนของโทเค็นที่ควรได้รับจะถูกคำนวณโดย msg.value/basePrice ในรูปแบบ mint แต่เนื่องจากความแม่นยำของการคำนวณ `/` ทุกส่วนเมื่อ msg.value น้อยกว่า 1 และ 16 จะถูกล็อค ในสัญญา ในกระบวนการนี้ไม่เพียงแต่จะนำไปสู่การเสีย eth เท่านั้น แต่ยังส่งผลเสียต่อประสบการณ์ของผู้ใช้อีกด้วย
สำหรับการคำนวณจำนวนเต็มที่อาจขาดความแม่นยำ ก่อนอื่นให้ขยายจำนวนเต็มด้วย `* 1 eN` (N คือขนาดความแม่นยำที่ต้องการ)
6. ค่าสุ่ม:
ชื่อเรื่องรอง
1. อย่าใช้ข้อมูลบนเครือข่ายที่เดาได้/ดำเนินการใดๆ เป็นข้อมูลสุ่มตัวเลข
รายละเอียดช่องโหว่:
เนื่องจากความเฉพาะเจาะจงของ blockchain จึงไม่มีค่าสุ่มอย่างแท้จริงบน chain และข้อมูลใด ๆ บน chain ไม่ควรใช้เป็นค่าสุ่มหรือ seed number สุ่ม พิจารณารับค่าสุ่มจาก off-chain
contract Eocene{
function winner(bytes 32 value) public payable{
require(msg.value > 0.5 ether,"not enough value");
if(value == keccak 256(abi.encodePacked(block.timestamp))){
msg.sender.transfer( 1 ether);
}
}
}
ตัวอย่างโค้ดมีดังนี้:
สำหรับสัญญาอุทธรณ์ ให้ใช้แท็กบล็อกเวลาปัจจุบันเพื่อคำนวณค่าสุ่ม เปรียบเทียบกับค่าสุ่มที่ผู้ใช้ส่งมา และให้รางวัลผู้ใช้ด้วยค่าสุ่มเดียวกัน ดูเหมือนว่าจะเป็นสถานการณ์สุ่มตามเวลา แต่ในความเป็นจริงผู้ใช้ที่ใช้ keccak 256 (abi.encodePacked(block.timestamp)) สามารถคำนวณมูลค่าผ่านการเรียกสัญญาและส่งไปยังรหัสสัญญาของฟังก์ชันผู้ชนะเพื่อ รับ eth นอกจากนี้ เราควรเข้าใจด้วยว่ามูลค่าของ block.timestamp สามารถถูกดัดแปลงโดยผู้ทำเหมืองโดยมุ่งร้าย และไม่จำเป็นต้องยุติธรรม
มาตรการซ่อมแซม:ให้พิจารณาใช้ chainlink เพื่อรับค่าสุ่มแบบออฟไลน์
เซเว่น, ดอส:
ชื่อเรื่องรอง
1. ห้ามดำเนินการใด ๆ ที่คัดลอกอาร์เรย์ตัวแปรสถานะทั้งหมดไปยังตัวแปรหน่วยความจำ
รายละเอียดช่องโหว่:
ขีดจำกัดของ Solidity ในขนาดหน่วยความจำที่พร้อมใช้งานของฟังก์ชันนั้นต่ำกว่าที่เก็บข้อมูลมาก (0x ffffffffffffffff) พฤติกรรมใดๆ ของการคัดลอกอาร์เรย์ไดนามิกโดยรวมไปยังหน่วยความจำอาจเกินขนาดหน่วยความจำที่มีอยู่ ส่งผลให้เกิดการย้อนกลับ
contract Eocene{
uint[] id;
function pop(uint amount) public{
require(amount>0,'not valid amount');
uint[] memory _id=id; // this may be revert because of memory space limit
for(uint i= 0;i<_id.length;i++)
{
if(amount==_id[i]){
id[i] = 0;
}
}
}
function push(uint amount) public{
require(amount>0,'not valid amount');
id.push(amount);
}
}
พิจารณารูปแบบรหัสต่อไปนี้:
ในโค้ดข้างต้น `uint[] memory _id=id;` จะใส่ค่าตัวแปรของ `uint[] id;` ในที่เก็บข้อมูลลงในหน่วยความจำ และฟังก์ชัน push สามารถแทรกค่าลงใน `uint[] id ;` และเนื่องจาก Solidity มีข้อจำกัดเกี่ยวกับพื้นที่หน่วยความจำ เมื่อความยาวของ `uint[] id;` เกิน `(0x ffffffffffffffff-0x 40)/0x 20-1` จะทำให้มีการใช้หน่วยความจำมากเกินไปและเปลี่ยนกลับ หมายความว่าฟังก์ชัน pop ของสัญญานี้ไม่สามารถดำเนินการได้สำเร็จ หรือฟังก์ชันใดๆ ที่มี `uint[] memory _id=id;` ไม่สามารถดำเนินการได้สำเร็จ
มาตรการซ่อมแซม:อย่าคัดลอกอาร์เรย์ไดนามิกตัวแปรไปยังหน่วยความจำเมื่อใดก็ได้ฟังก์ชันใดๆ ที่มีการใช้หน่วยความจำเกินกว่าค่านี้จะไม่สามารถดำเนินการได้สำเร็จ。
ชื่อเรื่องรอง
2. ใน for loop ใดๆ การตัดสินของ loop ไม่สามารถอิงตามตัวแปรภายนอกที่แก้ไขได้
รายละเอียดช่องโหว่:
หากการตัดสินของ for ลูปขึ้นอยู่กับตัวแปรที่แก้ไขได้ภายนอก อาจมีปัญหาที่ตัวแปรที่แก้ไขได้ภายนอกมีขนาดใหญ่เกินไปและปริมาณการใช้ก๊าซสูงเกินไป การโจมตีของ DOS เกิดขึ้นเมื่อปริมาณการใช้ก๊าซสูงพอที่ผู้เรียกสัญญาแต่ละรายจะทนได้
contract Eocene{
uint[] id;
function pop(uint amount) public{
require(amount>0,'not valid amount');
for(uint i= 0;i
{
if(amount==id[i]){
id[i] = 0;
}
}
}
function push(uint amount) public{
require(amount>0,'not valid amount');
id.push(amount);
}
}
พิจารณารหัสต่อไปนี้:
ที่นี่เราลบการดำเนินการคัดลอกอาร์เรย์จากที่เก็บข้อมูลไปยังหน่วยความจำ แต่ปัญหาอีกอย่างของรหัสนี้คือ for loop นั้นขึ้นอยู่กับความยาวของ `uint[] id;` และความยาวของ id สามารถเพิ่มได้เท่านั้นแต่ไม่ได้ ลดลงในสัญญา ซึ่งหมายความว่าแก๊สที่ใช้โดยฟังก์ชัน pop() จะมีขนาดใหญ่ขึ้นเรื่อย ๆ เมื่อแก๊สมีขนาดใหญ่เกินกว่าปริมาณการใช้แก๊สสูงสุดที่สามารถทนได้โดยการเรียกใช้ฟังก์ชัน pop จะมีไม่กี่คนที่จะดำเนินการป๊อป และการโจมตี DOS จะเกิดขึ้น
มาตรการซ่อมแซม:การดำเนินการวนซ้ำใด ๆ ควรจะสามารถตัดสินความยาวสูงสุดของการดำเนินการเพื่อป้องกันการเกิดปัญหา dos
ชื่อเรื่องรอง
3. ในลูป ใช้ try/catch เพื่อตรวจจับข้อยกเว้นที่ไม่ได้ระบุ
รายละเอียดช่องโหว่:
contract Eocene{
address[] candidates;
mapping(address=>uint) balanceof;
function claim() public{
for(uint i= 0;i
{
address candidate = candidates[i];
require(balanceof[candidate]>0,'no balance');
payable(candidate).transfer(balanceof[candidate]);
}
}
}
หากมีการย้อนกลับที่อาจเกิดจากแอดเดรสภายนอกภายในลูปใด ๆ จะต้องพิจารณาการจับการย้อนกลับ มิฉะนั้น เมื่อการดำเนินการวงในล้มเหลว ปริมาณการใช้ก๊าซทั้งหมดก่อนหน้านี้จะไม่มีความหมาย อย่างไรก็ตาม เมื่อการดำเนินการที่ล้มเหลวภายในลูปสามารถควบคุมได้โดยแอดเดรสภายนอก หากไม่มี try/catch เพื่อตรวจจับข้อยกเว้นที่เป็นไปได้ อาจทำให้การตัดสินลูปไม่เสร็จสมบูรณ์ ทำให้เกิดการโจมตี DOS
โค้ดนี้ใช้ for loop เพื่อโอนเงินไปยังผู้สมัครแต่ละคน แต่จะไม่คำนึงถึงว่าเมื่อผู้สมัครคนใดถูกเปลี่ยนกลับโดยตรงในฟังก์ชันทางเลือกหรือรับ ลูปจะไม่ถูกดำเนินการสำเร็จ ทำให้เกิดการโจมตี DOS
ในลูปใด ๆ หากมีการเรียกภายนอกและเป็นไปไม่ได้ที่จะตัดสินว่าการโทรนั้นจะเปลี่ยนกลับหรือไม่ คุณต้องใช้ try/catch เพื่อพยายามเติมเต็มข้อยกเว้นเพื่อป้องกันการโจมตีของ DOS ที่เกิดจากการย้อนกลับ。
8. ใช้คอมไพเลอร์เวอร์ชันที่สูงกว่า:
ชื่อเรื่องรอง
1. ใช้คอมไพเลอร์ที่สูงกว่า 0.8.17 เพื่อคอมไพล์สัญญา
ชื่อระดับแรก
- Solidity
เกี่ยวกับเรา
At Eocene Research, we provide the insights of intentions and security behind everything you know or don't know of blockchain, and empower every individual and organization to answer complex questions we hadn't even dreamed of back then.
