Cảnh báo rủi ro: Đề phòng huy động vốn bất hợp pháp dưới danh nghĩa 'tiền điện tử' và 'blockchain'. — Năm cơ quan bao gồm Ủy ban Giám sát Ngân hàng và Bảo hiểm
Tìm kiếm
Đăng nhập
简中
繁中
English
日本語
한국어
ภาษาไทย
Tiếng Việt
BTC
ETH
HTX
SOL
BNB
Xem thị trường
Nguyên tắc mã hóa an toàn chung của Solidity
Eocene
特邀专栏作者
2023-05-27 08:00
Bài viết này có khoảng 4840 từ, đọc toàn bộ bài viết mất khoảng 7 phút
Dựa trên các đặc điểm của mã hóa Solidity, bài viết này tóm tắt các vấn đề bảo mật liên quan và đưa ra các ví dụ để minh họa tác hại do mã hóa không an toàn gây ra và các biện pháp khắc phục t

1. Lưu trữ/gán/xóa biến:

tiêu đề phụ

1. Không lưu trữ bất kỳ thông tin nhạy cảm nào trên chuỗi

Chi tiết lỗ hổng:Tính minh bạch dựa trên chuỗi khối,Mọi dữ liệu hợp đồng được triển khai trên chuỗi đều minh bạch và có thể nhìn thấy

, ngay cả đối với các biến được sửa đổi riêng tư. Do khả năng hiển thị của riêng tư chỉ dành cho các chức năng và hợp đồng bên ngoài nên bất kỳ người dùng nào cũng có thể nhận được các giá trị này bằng cách truy xuất dữ liệu trên chuỗi. Trong trường hợp này, bất kỳ hoạt động bảo mật nào dự kiến ​​sẽ được đảm bảo bằng sửa đổi riêng tư trên chuỗi đều không an toàn.

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);

            }

        }

}

Nếu mã rút thăm trúng thưởng đơn giản sau tồn tại:

Ngay cả khi hạt giống đã được khai báo là một biến riêng tư, bất kỳ ai cũng có thể truy xuất giá trị hạt giống trên chuỗi thông qua địa chỉ hợp đồng và vị trí vị trí của hạt giống, để tính toán giá trị tương ứng để thu được eth.

Không lưu trữ bất kỳ giá trị khóa nào để xác minh trong hợp đồng, lưu trữ các giá trị khóa ngoài chuỗi và chỉ triển khai logic xác minh tương ứng trên chuỗi.

tiêu đề phụ

2. Chú ý đến các giá trị mặc định của biến

Chi tiết lỗ hổng:

Trong solidity, giá trị ban đầu của một biến là 0/false. Trong trường hợp này, nếu ảnh hưởng của giá trị ban đầu của biến không được xem xét khi đưa ra phán đoán dựa trên một biến nhất định, nó có thể gây ra các vấn đề bảo mật tương ứng.

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);

        }

    }

}

Hãy xem xét mã mở khóa airdrop sau:

Mục đích ban đầu của hợp đồng là phân phối mã thông báo cho tất cả các địa chỉ đã mở khóa, nhưng nó bỏ qua điều đó về tính vững chắc, giá trị ban đầu của tất cả các biến là 0/false. Trong kiểu ánh xạ, khóa chỉ được sử dụng để ghép nối với vị trí và địa chỉ tương ứng với khóa trong bộ nhớ được tính toán thông qua keccak 256, nghĩa là bất kỳ địa chỉ nào, dù được khởi tạo hay không, sẽ có bộ nhớ tương ứng với nó , và giá trị ban đầu Thường là 0/false.

Không đưa ra phán đoán quan trọng dựa trên giá trị mặc định của các biến trong bất kỳ trường hợp nào, đặc biệt là trong các biến dựa trên các loại ánh xạ và ngăn chặn nghiêm ngặt các vấn đề như vậy.

tiêu đề phụ

3. Xóa giá trị của kiểu struct không dùng nữa

Chi tiết lỗ hổng:

Đối với bất kỳ loại ánh xạ nào, khi loại trường giá trị là cấu trúc và giá trị tương ứng không còn cần thiết, cài đặt xóa sẽ được sử dụng để xóa giá trị. Nếu không, giá trị sẽ vẫn còn trong vị trí tương ứng.

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;

    }

}

Xem xét mã của mẫu:

Mã hợp đồng ở trên tính toán số eth thu được dựa trên số tiền cam kết và thời gian cam kết, nhưng sau khi hoàn thành cam kết, chỉ giá trị của staker[msg.sender] được đặt thành false và các stake tương ứng[msg.sender] vẫn còn tồn tại. Vì vậy, kẻ tấn công có thể gọi hàm getStake() mà không bị hạn chế để lấy eth.

Biện pháp sửa chữa:Tất nhiên, đoạn mã trên cũng có một số vấn đề phụ trợ khác dẫn đến tồn tại các lỗ hổng, nhưng bạn nên lưu ý rằng,, nếu không thì cặp giá trị luôn tồn tại trong vị trí tương ứng.

2. Định nghĩa hàm:

tiêu đề phụ

1. Khả năng hiển thị của các chức năng được khai báo phải được hiển thị

Chi tiết lỗ hổng:Khả năng hiển thị mặc định của các chức năng là công khai,Đối với bất kỳ chức năng nào, khả năng hiển thị của nó phải được khai báo rõ ràng

, để ngăn chặn sự tồn tại của các lỗ hổng do sơ suất, đặc biệt là khi chức năng được lồng trong nhiều lớp để gọi chức năng cơ bản, để ngăn chức năng cơ bản bị hiển thị không chính xác do sơ suất.

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();

    }

}

Xem xét ví dụ sau về mã dễ bị tấn công:

Hàm a() giới hạn địa chỉ danh sách trắng thông qua yêu cầu và chuyển tiền đến địa chỉ tương ứng sau khi chuyển. Trong các trường hợp bình thường, _a() không nên được gọi bên ngoài, nhưng ở đây vì khả năng hiển thị của _a() không được khai báo rõ ràng nên nó được coi là công khai, nó có thể được gọi trực tiếp bởi bên ngoài.

Khai báo rõ ràng khả năng hiển thị của tất cả các chức năng, đặc biệt đối với các chức năng không thể gọi trực tiếp từ bên ngoài, chúng phải được khai báo rõ ràng là bảo vệ hoặc riêng tư.

tiêu đề phụ

2. Tấn công vào lại chức năng

Chi tiết lỗ hổng:

Như với bất kỳ chức năng nào, bạn phải xem xét các vấn đề có thể phát sinh sau khi truy cập lại. Vào lại ở đây bao gồm tất cả các sự cố vào lại do các lệnh gọi bên ngoài như chuyển/gửi/gọi/staticall.

contract Fund {

    mapping(address => uint) shares;

    function withdraw() public {

    if (payable(msg.sender).send(shares[msg.sender]))

    shares[msg.sender] = 0;

    }

}

Hãy xem xét mẫu mã sau đây:

Đối với hợp đồng khiếu nại, khi msg.sender độc hại, nó có thể khiến msg.sender trích không giới hạn số dư của tất cả các hợp đồng hiện tại. Nhưng chúng ta cũng phải lưu ý rằng khi gọi chuyển/gửi/gọi/staticall và bất kỳ chức năng hợp đồng bên ngoài nào, nó có thể gây ra sự cố truy cập lại.

Theo các chi tiết triển khai cụ thể của hợp đồng, việc triển khai các biến chính có thể được sửa đổi trước. Ví dụ: trong hợp đồng kháng cáo, giá trị của cổ phiếu[msg.sender] có thể được ghi lại trước, sau đó có thể thực hiện thao tác gửi sau khi đặt chia sẻ [msg.sender] thành 0. Tất nhiên, nó cũng có thể được thực hiện thông qua sự kết hợp của các biến toàn cục và các bộ trang trí.

3. Tương tác bên ngoài

tiêu đề phụ

1. Giới hạn địa chỉ và danh sách chức năng của các cuộc gọi bên ngoài

Chi tiết lỗ hổng:

Đối với các cuộc gọi đến các chức năng bên ngoài, trong các trường hợp hợp lý, địa chỉ hợp đồng và chức năng hợp đồng được gọi phải được giới hạn

contract Eocene{

    function callExt(address _target, bytes calldata data) public{

        _target.call(data);

    }

    function delegateCallExt(address _target, bytes calldata data) public{

        _target.delegatecall(data);

    }

}

Hãy xem xét mẫu mã sau đây:

Hàm callExt được sử dụng để gọi bất kỳ địa chỉ nào của bất kỳ chức năng nào, trong trường hợp này rất dễ gây ra sự cố nhập lại, một khi hợp đồng có bất kỳ tài sản Mã thông báo nào trong bất kỳ ví nào, nó có thể trực tiếp gọi chức năng chuyển của Mã thông báo tương ứng thông qua chức năng này. Đi bộ.

Tuy nhiên, nếu không có hạn chế khi gọi hàm delegateCallExt, nó có thể khiến hợp đồng bị hủy trực tiếp, dẫn đến việc chuyển toàn bộ số dư địa chỉ hợp đồng và hợp đồng bị hủy.

Đối với các cuộc gọi đến bất kỳ hợp đồng bên ngoài nào, trước tiên hãy xem xét liệu địa chỉ có thể bị hạn chế bởi danh sách trắng hay không và xem xét thêm liệu tên chức năng của địa chỉ đã chỉ định có thể bị hạn chế hay không.

tiêu đề phụ

2. Khi sử dụng cuộc gọi, gửi, ủy quyền, cuộc gọi tĩnh, việc đánh giá các cuộc gọi bên ngoài không chỉ phụ thuộc vào các ngoại lệ mà còn đánh giá theo giá trị trả về

Chi tiết lỗ hổng:

Chức năng khiếu nại không gây ra hoàn nguyên do lỗi nội bộ mà chỉ trả về hoàn nguyên. Bất cứ khi nào chúng được sử dụng, giá trị trả về của hàm phải được sử dụng để xác định xem việc thực thi có thành công hay không.

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);

    }

}

Hãy xem xét mã mẫu sau:

Trong hàm Deposit(), trước tiên, hợp đồng sẽ cố gắng chuyển mã thông báo được chỉ định của msg.sender đến địa chỉ hiện tại. Sau khi chuyển thành công, hợp đồng sẽ đúc một số xu hiện tại cho msg.sender. Tuy nhiên, vì chức năng .call sẽ không hoàn nguyên toàn bộ giao dịch khi nó không thành công, nên ngay cả khi nó không chuyển được bất kỳ loại tiền nào từ msg.sender sang địa chỉ hiện tại, thì nó vẫn sẽ đúc số tiền hiện tại cho msg.sender.

Việc đánh giá kết quả thực thi của cuộc gọi, gửi, ủy quyền và cuộc gọi tĩnh phải dựa trên giá trị trả về của nó, chứ không phải liệu nó có hoàn nguyên hay không.

Thứ tư, kiểm soát truy cập:

tiêu đề phụ

1. Không xác thực danh tính dựa trên tx.origin

Chi tiết lỗ hổng:Không xác thực dựa trên tx.origin

, tx.origin là trình khởi tạo toàn bộ giao dịch và sẽ không thay đổi với lệnh gọi đệ quy của hợp đồng. Mọi xác thực dựa trên tx.origin đều không thể đảm bảo rằng tx.origin là msg.sender. Xác thực dựa trên tx.origin của nó cũng tăng cường bảo mật tài khoản người dùng.

contract Eocene{

    mapping(address=>bool) whitelist;

    function freeDeposit() public{

        require(whitelist[tx.origin],'not in whitelist');

        payable(msg.sender).transfer( 1 ether);

    }

}

Xem xét ví dụ sau về một lỗ hổng:

Khi bất kỳ địa chỉ nào trong danh sách trắng bị một số liên kết lừa đảo gây ra để gọi bất kỳ chức năng và địa chỉ hợp đồng độc hại nào có vẻ vô hại và địa chỉ độc hại gọi hàm freeDeposit của mã mẫu, thì các tài sản thuộc về địa chỉ danh sách trắng sẽ được chuyển đến địa chỉ hợp đồng độc hại.

Biện pháp sửa chữa:Không xác thực dựa trên tx.origin. Hoặc đối với mã kháng cáo, khi đổi thành pay(tx.origin).transfer( 1 ether) thì cũng không vấn đề gì., nhưng hãy sử dụng require(whitelist[msg.sender],'not in whitelist'); để đưa ra đánh giá.

tiêu đề phụ

2. Không đánh giá tài khoản eos dựa trên giá trị trả về của extcodesize

Chi tiết lỗ hổng:Trong giai đoạn khởi tạo mã hợp đồng, ngay cả khi địa chỉ là địa chỉ hợp đồng, giá trị trả về của extcodesize sẽ là 0

, nếu phán đoán được đưa ra dựa trên giá trị trả về, kết quả thu được sẽ không chính xác.

contract Eocene{

    function withdraw() public{

        uint size;

        assembly {

        size := extcodesize(caller())

        }

        require(size== 0,"not eos account");

        msg.sender.transfer( 1 ether);

    }

}

Hãy xem xét mẫu mã sau đây:

Trong chức năng rút tiền của hợp đồng khiếu nại, hy vọng rằng chỉ tài khoản EOS mới có thể nhận được mã thông báo thông qua giá trị trả về kích thước mã hóa mở rộng, nhưng nó bỏ qua rằng khi hợp đồng được khởi tạo, giá trị trả về mã hóa mã hóa cho địa chỉ hợp đồng cũng bằng 0. Do đó, phán quyết là không chính xác và bất kỳ địa chỉ nào cũng có thể nhận được mã thông báo từ hợp đồng.

Không đưa ra đánh giá dựa trên việc liệu địa chỉ bên ngoài có phải là địa chỉ hợp đồng hay không và cố gắng đảm bảo rằng mã hợp đồng hoạt động bình thường trong bất kỳ loại tài khoản nào.

5. Các phép toán số học

tiêu đề phụ

1. Khi thực hiện bất kỳ phép toán số nào, hãy xem xét vấn đề tràn

Chi tiết lỗ hổng:

Vấn đề tràn đề cập đến vấn đề tràn gây ra khi hợp đồng thực hiện các hoạt động số nguyên. Lý do chính là bất kỳ loại số nào cũng có độ dài tối đa, khi phép toán của hai số nguyên vượt quá giá trị tối đa của nó, phần thừa sẽ bị cắt bớt, gây ra sự cố.

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');

    }

}

Hãy xem xét mẫu mã sau đây:

Đối với hàm khiếu nại, hãy lưu ý rằng khi balanceof[msg.sender] < số lượng, do loại balanceof bị giới hạn ở số nguyên không dấu, nên kết quả phép tính tổng sẽ cho kết quả là giá trị âm của kiểu int và khi chuyển đổi thành kiểu uint, nó sẽ là một giá trị dương rất lớn, tại thời điểm này, hạn chế của yêu cầu bị bỏ qua và kẻ tấn công có thể đánh cắp bất kỳ số lượng mã thông báo nào từ bản tóm tắt hợp đồng.

Biện pháp sửa chữa:, để đảm bảo rằng kết quả tính toán cuối cùng không gây tràn.

tiêu đề phụ

2. Khi thực hiện bất kỳ thao tác số nguyên nào, hãy sử dụng kiểu int một cách cẩn thận

Chi tiết lỗ hổng:

Khi thực hiện bất kỳ phép tính nào với các kiểu số nguyên, hãy cẩn thận chuyển đổi kiểu uint thành kiểu int để tính toán, trừ khi bạn cần loại thao tác này. Vì khi chuyển đổi số nguyên kiểu uint thành kiểu int, một số trường hợp tràn số đối với kiểu uint sẽ không hợp lệ ở kiểu int.

contract Eocene{

    int public result;

    uint public uresult;

    function cal(uint _a, uint _b) public{

        result = int(_a)-int(_b);

        uresult = uint(result);

    }

}

Hãy xem xét mẫu mã sau đây:

Khi biên dịch với solidity phiên bản 0.8.0 trở lên, nếu bạn gọi `cal(0, 1)`, mặc dù `0-1` gây tràn trong uint, nhưng nó sẽ không gây tràn trong tính toán hoàn nguyên kiểu int (vì kết quả 0-1 nằm trong phạm vi của kiểu int). Và khi giá trị kết quả được chuyển đổi thành loại uint, nó là giá trị kết quả sau khi phép tính loại uint thực tế bị tràn, dẫn đến sự cố tràn được ngụy trang.

Nhưng cần lưu ý rằng nếu cal(type(int).min, type(int).max) được gọi ở đây, quá trình hoàn nguyên sẽ vẫn được kích hoạt, bởi vì phép tính số nguyên tại thời điểm này cũng nằm ngoài phạm vi của kiểu int

Biện pháp sửa chữa:trong tiến trình. Nếu bản thân thao tác số nguyên cần tràn, hãy cân nhắc sử dụng bỏ chọn để bao bọc quá trình triển khai thao tác kiểu uint.

tiêu đề phụ

3. Trong bất kỳ thao tác nào có thể mất độ chính xác, hãy ngăn chặn sự mất độ chính xác không thể chấp nhận được bằng cách mở rộng

Chi tiết lỗ hổng:

Khi thực hiện bất kỳ thao tác số nguyên nào, các vấn đề có thể xảy ra do mất độ chính xác sẽ được xem xét và độ chính xác của nó được mở rộng.

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;

    }

}

Hãy xem xét mã sau đây của mẫu:

Xem xét hợp đồng khiếu nại, số lượng mã thông báo cần nhận được tính bằng msg.value/basePrice bằng bạc hà, nhưng do độ chính xác của phép tính `/`, tất cả các phần khi msg.value nhỏ hơn 1 e 16 sẽ bị khóa trong hợp đồng Trong quá trình này, điều này không chỉ dẫn đến lãng phí ETH mà còn ảnh hưởng khá xấu đến trải nghiệm người dùng.

Đối với các phép tính số nguyên có thể thiếu độ chính xác, trước tiên hãy mở rộng số nguyên bằng `* 1 eN` (N là kích thước độ chính xác bắt buộc).

6. Giá trị ngẫu nhiên:

tiêu đề phụ

1. Không sử dụng bất kỳ dữ liệu on-chain nào có thể đoán được/vận hành làm hạt giống số ngẫu nhiên

Chi tiết lỗ hổng:

Do tính đặc thù của chuỗi khối, không có giá trị thực sự ngẫu nhiên trên chuỗi và không nên sử dụng bất kỳ dữ liệu nào trên chuỗi làm giá trị ngẫu nhiên hoặc hạt giống số ngẫu nhiên.

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);

        }

    }

}

Ví dụ mã như sau:

Đối với hợp đồng khiếu nại, hãy sử dụng thẻ thời gian khối hiện tại để tính giá trị ngẫu nhiên, so sánh giá trị đó với giá trị ngẫu nhiên do người dùng gửi và thưởng cho người dùng giá trị ngẫu nhiên tương tự. Nó có vẻ là một tình huống ngẫu nhiên dựa trên thời gian, nhưng trên thực tế, bất kỳ người dùng nào sử dụng keccak 256 (abi.encodePacked(block.timestamp)) đều có thể tính toán giá trị thông qua lệnh gọi hợp đồng và gửi nó tới mã hợp đồng của hàm người chiến thắng tới lấy eth . Ngoài ra, chúng ta cũng nên hiểu rằng giá trị của block.timestamp có thể bị giả mạo một cách ác ý bởi những người khai thác và điều đó không nhất thiết phải công bằng.

Biện pháp sửa chữa:, hãy cân nhắc sử dụng chainlink để nhận các giá trị ngẫu nhiên ngoại tuyến

Bảy, DOS:

tiêu đề phụ

1. Nghiêm cấm mọi thao tác sao chép toàn bộ mảng biến trạng thái vào một biến bộ nhớ

Chi tiết lỗ hổng:

Giới hạn của Solidity về kích thước bộ nhớ khả dụng của hàm thấp hơn nhiều so với dung lượng lưu trữ (0x ffffffffffffffff), mọi hành vi sao chép toàn bộ mảng động vào bộ nhớ có thể vượt quá kích thước bộ nhớ khả dụng, dẫn đến hoàn nguyên.

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);

    }

}

Hãy xem xét mẫu mã sau đây:

Trong đoạn mã trên, `uint[] memory _id=id;` sẽ đưa giá trị biến của `uint[] id;` trong bộ lưu trữ vào bộ nhớ và hàm push có thể chèn giá trị vào `uint[] id ;`, và bởi vì Solidity có những hạn chế về dung lượng bộ nhớ. Khi độ dài của `uint[] id;` vượt quá `(0x ffffffffffffffff-0x 40)/0x 20-1`, nó sẽ gây ra tình trạng sử dụng bộ nhớ quá mức và hoàn nguyên. Điều đó có nghĩa là chức năng pop của hợp đồng này không bao giờ có thể được thực thi thành công, hoặc bất kỳ chức năng nào có thao tác `uint[] memory _id=id;` đều không thể được thực thi thành công.

Biện pháp sửa chữa:Không sao chép mảng động biến vào bộ nhớ bất cứ lúc nàoBất kỳ chức năng nào có mức sử dụng bộ nhớ vượt quá giá trị này đều không thể thực thi thành công

tiêu đề phụ

2. Trong bất kỳ vòng lặp for nào, phán đoán vòng lặp không thể dựa trên các biến có thể sửa đổi bên ngoài

Chi tiết lỗ hổng:

Nếu phán đoán của bất kỳ vòng lặp for nào dựa trên các biến có thể thay đổi bên ngoài, thì có thể có vấn đề là các biến có thể thay đổi bên ngoài quá lớn và mức tiêu thụ gas quá cao. Các cuộc tấn công DOS xảy ra khi mức tiêu thụ gas đủ cao để mỗi người gọi hợp đồng có thể chịu được.

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);

    }

}

Hãy xem xét đoạn mã sau:

Ở đây chúng tôi xóa thao tác sao chép mảng từ bộ lưu trữ sang bộ nhớ, nhưng một vấn đề khác với mã này là vòng lặp for dựa trên độ dài của `uint[] id;`, và độ dài của id chỉ có thể tăng lên chứ không thể tăng lên giảm trong hợp đồng, có nghĩa là lượng gas tiêu thụ bởi chức năng pop() sẽ ngày càng lớn hơn, khi lượng gas quá lớn vượt quá mức tiêu thụ gas tối đa có thể chịu được khi thực hiện chức năng pop, sẽ ít người thực hiện pop , và một cuộc tấn công DOS sẽ được thực hiện.

Biện pháp sửa chữa:, bất kỳ thao tác vòng lặp nào cũng có thể đánh giá độ dài thực thi tối đa của nó để ngăn chặn sự tồn tại của các sự cố dos.

tiêu đề phụ

3. Trong vòng lặp, sử dụng try/catch để bắt các ngoại lệ chưa xác định

Chi tiết lỗ hổng:

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]);

        }

    }

}

Nếu có một hoàn nguyên có thể do một địa chỉ bên ngoài bên trong bất kỳ vòng lặp nào gây ra, thì việc nắm bắt hoàn nguyên phải được xem xét. Mặt khác, một khi bất kỳ việc thực hiện vòng lặp bên trong nào không thành công, tất cả mức tiêu thụ gas trước đó sẽ trở nên vô nghĩa. Tuy nhiên, khi lỗi thực thi bên trong vòng lặp có thể được kiểm soát bởi địa chỉ bên ngoài, nếu không có try/catch để bắt các ngoại lệ có thể xảy ra, điều đó có thể khiến phán đoán vòng lặp không bao giờ được hoàn thành hoàn toàn, tạo ra một cuộc tấn công DOS.

Mã này sử dụng vòng lặp for để chuyển tiền cho từng ứng viên, nhưng nó không tính đến việc khi bất kỳ ứng viên nào được hoàn nguyên trực tiếp trong hàm dự phòng hoặc nhận, vòng lặp sẽ không bao giờ được thực thi thành công, gây ra một cuộc tấn công DOS.

Trong bất kỳ vòng lặp for nào, nếu có một cuộc gọi bên ngoài và không thể đánh giá liệu cuộc gọi đó có hoàn nguyên hay không, bạn phải sử dụng try/catch để cố gắng bổ sung ngoại lệ nhằm ngăn chặn các cuộc tấn công DOS do hoàn nguyên

8. Sử dụng trình biên dịch phiên bản cao hơn:

tiêu đề phụ

1. Sử dụng trình biên dịch trên 0.8.17 để biên dịch hợp đồng

tiêu đề cấp đầu tiên

- SOL-2022-7 

- SOL-2022-6 

- Solidity

về chúng tôi

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.

Learn more: [Website] | [Medium] | [Twitter]

Sự an toàn
hợp đồng thông minh
nhà phát triển
công nghệ
Chào mừng tham gia cộng đồng chính thức của Odaily
Nhóm đăng ký
https://t.me/Odaily_News
Tài khoản chính thức
https://twitter.com/OdailyChina