Logo Zephyrnet

Hướng dẫn cho Người mới bắt đầu về Kiểm toán Hợp đồng Thông minh: Phần 1

Ngày:

Mục lục

Chào mừng bạn đến với Hướng dẫn dành cho người mới bắt đầu về kiểm toán hợp đồng thông minh! Một trong những cách tốt nhất để bắt đầu với kiểm toán hợp đồng thông minh là bắt đầu và xem xét một số loại lỗ hổng phổ biến trong hợp đồng thông minh.

Sẽ rất hữu ích nếu bạn đã có hiểu biết cơ bản về ngôn ngữ lập trình Solidity của Ethereum. Như chúng ta sẽ xem xét một số mã được viết bởi các lập trình viên Noob solidity.

Tấn công vào lại

Kịch bản trong thế giới thực:

Hãy tưởng tượng bạn có 50 viên sôcôla. Bạn có một cô em gái nghịch ngợm mà bạn chỉ được phép lấy 2 viên sôcôla từ bạn bất kỳ lúc nào. Bạn cũng không nên tặng cô ấy hơn 10 viên sôcôla trong một ngày vì sợ cô ấy bị sâu răng. Để đảm bảo điều này, mỗi tối bạn đếm xem có bao nhiêu sôcôla còn lại với bạn. Bạn có nghĩ rằng điều này sẽ làm việc? Hay bạn sẽ bị hack bởi em gái của bạn?

Thật không may, nó sẽ không hoạt động! Em gái của bạn tính ra rằng bạn không biết bạn có bao nhiêu sôcôla cho đến khi trời tối. Vì vậy, ngay ngày hôm sau, em gái của bạn đến thăm bạn 6 lần trước buổi tối và uống 2 viên sôcôla mỗi lần! Đây là những gì chúng tôi gọi là một cuộc tấn công Re-entrancy.

Ở đây bạn đang cập nhật số lượng sôcôla bạn có vào buổi tối thay vì cập nhật số lượng mỗi khi em gái của bạn lấy 2 viên sôcôla từ bạn. Đây cũng là những gì xảy ra với hợp đồng thông minh. Hợp đồng thông minh giả định một số dư cụ thể trong khi kẻ tấn công thực sự bận rút một số lượng tiền điện tử khỏi hợp đồng nhiều lần.

Ví dụ về mã trong thế giới thực:

Mã này thuộc về một hợp đồng thông minh có tên là Không thiên vị. Bất kỳ ai cũng có thể rút ether từ một hợp đồng Unbanked miễn là số dư của msg.sender (tức là người gọi của withdraw ) lớn hơn hoặc bằng số tiền được yêu cầu rút.

function withdraw(uint _amount) { require(balances[msg.sender] >= _amount); msg.sender.call.value(_amount)(); balances[msg.sender] -= _amount;
}

Lưu ý rằng có một từ khóa gọi được sử dụng để gửi số lượng ether cần thiết đến msg.sender. Kẻ tấn công có thể khai thác điều này bằng cách tạo một hợp đồng có tên Thief, trong đó hắn gọi hàm rút tiền trong một fallback() hàm số. MỘT fallback() chức năng trong Solidity là một chức năng đặc biệt được thực thi khi ether được gửi đến hợp đồng thông minh.

Điều này có nghĩa là kẻ tấn công có thể đệ quy gọi hàm rút tiền. Do đó, trước khi hợp đồng thông minh cập nhật, số dư của msg.sender ở dòng mã cuối cùng, kẻ tấn công đã rút ether nhiều lần. Điều này có thể tránh được nếu số dư được cập nhật trước khi sử dụng từ khóa cuộc gọi, do đó sau kiểm tra-hiệu ứng-tương tác mô hình.

Va chạm:

Sản phẩm Cuộc tấn công lần đầu tiên của Reentrancy xảy ra vào năm 2016 trên một DAO (Tổ chức tự trị phi tập trung) dẫn đến một vụ hack khoảng 50 triệu đô la. Để đảo ngược vụ hack này, cộng đồng Ethereum đã tách chuỗi khối Ethereum, vốn đã tạo ra ETC (Ethereum Classic) và ETH (Ethereum).

Tràn và Tràn số học

Kịch bản trong thế giới thực:

Hãy chơi một trò chơi suy nghĩ. Nó bao gồm một Vòng quay và người chiến thắng được quyết định dựa trên con số lớn nhất mà anh ta có thể quay được trên bánh xe. Bánh xe được đánh dấu từ 256 đến -256.

Các quy tắc của trò chơi là con trỏ cho tất cả người chơi nằm trên 0 ở đầu mỗi lần quay. Và một người chơi chỉ được phép quay theo hướng của số âm. Làm thế nào bạn sẽ thắng trò chơi này?

Một chiến lược tốt để giành chiến thắng trong trò chơi này mỗi lần là quay bánh xe với sức mạnh đến mức bánh xe quay đến -256 và sau đó quay thành 256 trong một lần. Điều này có thể xảy ra vì 256 đến chỉ sau -256 trên bánh xe. Đây là những gì chúng tôi gọi là một quy trình số học. Và tràn số học ngược lại với điều này.

Ví dụ về mã trong thế giới thực:

An tràn hoặc tràn xảy ra khi một phép toán số học đạt đến giá trị nhỏ nhất hoặc tối đa.

function withdraw(uint _amount) public { require(balances[msg.sender] - _amount > 0); address payable to = payable(msg.sender); to.transfer(_amount); balances[msg.sender] -= _amount;
}

Sản phẩm _amount tham số của hàm rút là một số nguyên không dấu. Giá trị của ánh xạ số dư (giống như một từ điển trong python hoặc một cặp khóa-giá trị trong C ++ hoặc Java) cũng là một số nguyên không dấu.


mapping(address => uint256) public balances

Tuyên bố bắt buộc kiểm tra xem số dư của msg.sender là tích cực hay không. Nhưng tuyên bố này sẽ luôn đúng ngay cả khi số tiền lớn hơn số dư của msg.sender. Điều này là do cả hai balances_amount các biến có kiểu số nguyên không dấu và kết quả số học của chúng (sau phần gạch dưới) cũng sẽ là một số nguyên không dấu!

Và như bạn có thể nhớ lại, một số nguyên không dấu luôn là số dương. Điều này có nghĩa là kẻ tấn công có thể rút một lượng Ether không giới hạn từ hợp đồng thông minh! Bạn có thể tìm thấy một ví dụ chi tiết và mã triển khai cho lỗ hổng này Ở đây.

Một điều quan trọng khác cần lưu ý ở đây là phép toán số học giữa hai số nguyên không dấu cũng là một số nguyên không dấu. Sẽ rất nguy hiểm nếu điều này bị bỏ qua trong các hợp đồng thông minh, vì nó có thể dẫn đến các vi phạm bảo mật không mong muốn!

function votes(uint postId, uint upvote, uint downvotes) { if (upvote - downvote < 0) { deletePost(postId) }
}

Như bạn có thể nhận thấy trong ví dụ trên, câu lệnh if khá vô nghĩa vì upvote - downvote luôn luôn tích cực. Và bài viết sẽ bị xóa ngay cả khi downvotes lớn hơn upvotes. Để tránh các cuộc tấn công như vậy, bạn nên sử dụng phiên bản trình biên dịch Solidity lớn hơn 0.8.0.

Va chạm:

Một đồng xu được gọi là Đồng xu PoWH được ra mắt vào năm 2017. Mặc dù đây là một trò chơi Ponzi, nhưng bản thân nó đã bị tấn công do lỗi tràn số học dẫn đến mất khoảng 866 ETH hoặc 950,000 đô la vào thời điểm đó. Bạn có thể đọc chi tiết về điều này Ở đây.

Phải đọc: Bài học từ cuộc tấn công vào người tí hon, DEX lớn nhất trên Algorand

Tấn công từ chối dịch vụ

Kịch bản trong thế giới thực:

Hãy tưởng tượng bạn đang ở trong một trường Đại học Công nghệ Bitcoin. Mọi thứ có vẻ ổn ngoại trừ việc có một bàn ăn chung cho tất cả mọi người. Và thật không may, có rất ít người từ lớp khác luôn xoay xở để chiếm bàn ăn trước bất kỳ ai từ lớp của bạn.

Trong kịch bản thực tế, họ đang từ chối dịch vụ thiết yếu cho mọi người dẫn đến mất thời gian quý báu. Đây là những gì chúng tôi gọi là 'cuộc tấn công từ chối dịch vụ'.

Ví dụ về mã trong thế giới thực:

Trong trò chơi có tên Vua của Ether, ai cũng có thể trở thành vua. Nhưng quy tắc để trở thành vua là một người phải gửi nhiều ether hơn so với vị vua hiện tại. Điều này có thể được thực hiện bằng cách gọi claimThrone() chức năng của hợp đồng Vua Ether, trong đó người đó gửi ether trực tiếp đến vị vua trước đó và trở thành vị vua mới.

function claimThrone() external payable { require(msg.value > balance, "Need to pay more to become the king"); (bool sent, ) = king.call{value: balance}(""); require(sent, "Failed to send Ether"); balance = msg.value; king = msg.sender; }

Như bạn có thể đoán, mã này dễ bị tấn công DoS, nhưng làm thế nào? Đối với điều này, bạn sẽ cần hiểu rằng có hai loại địa chỉ trong Ethereum- đầu tiên là địa chỉ của một bên ngoài tài khoản sở hữu hoặc chỉ đơn giản là địa chỉ của một chiếc ví và thứ hai là địa chỉ hợp đồng. Bây giờ ether có thể được gửi từ một trong hai loại địa chỉ này.

Nếu điều này, ether được gửi theo địa chỉ hợp đồng, thì hợp đồng sẽ trở thành vua. Nhưng giả sử rằng hợp đồng mới này không có fallback() chức năng cần thiết nếu hợp đồng muốn chấp nhận ether. Sau đó, nếu một người mới đến và cố gắng gọi cho claimThrone() chức năng, nó sẽ luôn luôn không thành công!

Lưu ý rằng điều này cũng xảy ra một phần vì claimThrone() hàm kiểm tra rõ ràng việc chuyển ether có thành công hay không trong câu lệnh bắt buộc thứ hai. Bạn có thể tìm thấy mã hoàn chỉnh và thực hiện một cuộc tấn công DoS vào nó Ở đây.

Cũng có thể mã dễ bị tấn công DoS nếu mã có vòng lặp trên một mảng có kích thước lớn. Điều này xảy ra bởi vì giới hạn khí có thể được vượt quá trong những trường hợp như vậy. Bạn có thể đọc về nó Ở đây.

Va chạm:

Một trò chơi được gọi là Chính phủTinh thần, rõ ràng là một kế hoạch của Ponzi, gặp khó khăn với 1100 ether vì cần một lượng lớn khí đốt để xử lý khoản thanh toán.

Sự ngẫu nhiên không an toàn

Kịch bản trong thế giới thực:

Ngày xưa có một người đàn ông tên là Hesky luôn đi cùng với chú khỉ Pesky của mình. Hesky đã tiến hành các trò chơi xổ số và thu được lợi nhuận tốt. Một ngày nọ, Alice nhận thấy Hesky đang chăm chú nhìn chú khỉ Pesky của mình. Sau đó, cô thấy anh ta viết gì đó trên một tờ giấy và dán nó vào một phong bì. Tò mò, cô quyết định điều tra thêm.

Cuối buổi tối hôm đó, Alice thấy rằng người trúng số đã được quyết định bằng cách mở phong bì niêm phong một cách công khai. Sau khi quan sát anh ta trong vài ngày, Alice đã phát hiện ra rằng Hesky đã quyết định số trúng xổ số bằng cách nhìn vào cử chỉ của Pesky (ví dụ nếu con khỉ gãi đầu, Hesky đã viết ra 10)! Bây giờ Alice đã có công thức để trúng mỗi xổ số và chỉ cần mua vé số với đúng số!

Hesky đã cho rằng không bao giờ có thể tìm ra cách “ngẫu nhiên” của mình để quyết định người trúng số, nhưng quả thực ông đã sai.

Ví dụ về mã trong thế giới thực:

Trong ví dụ này, một số ngẫu nhiên được tạo dựa trên băm của sự kết hợp giữa số của một khối và dấu thời gian của khối đó. băm này sau đó được gán cho biến câu trả lời. Giờ đây, bất kỳ ai đoán được con số ngẫu nhiên (dường như) này sẽ được thưởng 1 Ether. Bạn có nghĩ rằng điều này là không thể xử lý được?

function guess(uint _guess) public { uint answer = uint( keccak256(abi.encodePacked(blockhash(block.number - 1), block.timestamp)) ); if (_guess == answer) { (bool sent, ) = msg.sender.call{value: 1 ether}(""); require(sent, "Failed to send Ether"); } }

Không! Kẻ tấn công vẫn có thể đoán số ngẫu nhiên này bằng cách chỉ cần sao chép, dán mã để tạo giá trị được gán cho biến câu trả lời và chuyển cùng một biến câu trả lời cho guess() chức năng!


guessTheRandomNumber.guess(answer);

Bạn có thể tìm thấy mã hoàn chỉnh Ở đây. Để tránh cuộc tấn công này, bạn nên sử dụng một Hàm ngẫu nhiên có thể xác minh được chẳng hạn như Chuỗi liên kết VRF.

Va chạm:

Khoảng 400 ETH đã bị mất do một cuộc tấn công vào Xổ số hàng tỷ thông minh hợp đồng. Đáng ngạc nhiên, ngay cả bản thân xổ số hợp đồng cũng là một kế hoạch Ponzi (ouch!).

Thao tác thời gian

Kịch bản trong thế giới thực:

Satoshi rất thích ăn bánh quy. Anh ấy thích tất cả các loại bánh mà mẹ anh ấy làm. Nhưng mẹ của anh ấy rất nghiêm khắc và cảm thấy rằng ăn quá nhiều bánh quy không tốt cho anh ấy. Vì vậy, mẹ của anh ấy đưa ra một quy tắc rằng anh ấy sẽ chỉ nhận được bánh vào lúc 8 giờ tối.

Vào lúc 7:45 tối hôm đó, Satoshi chạy đến chỗ mẹ và xin bánh quy. Mẹ anh hỏi- "Mấy giờ rồi?"

"Bây giờ là 8 'O đồng hồ!" - anh ấy trả lời.

"Được chứ. Sau đó, lấy bánh quy từ tủ của tôi. ”

Và do đó, Satoshi đã có thể thao túng thời gian thành công trong 15 phút để anh ta có thể lấy được bánh quy của mình! Thật là một chương thèm ăn bánh quy!

Ví dụ về mã trong thế giới thực:

Dấu thời gian của một khối có thể được điều khiển bởi khoảng 15 giây bởi một thợ mỏ. Bằng cách này, người khai thác có thể đặt dấu thời gian thuận lợi và đưa giao dịch của mình vào cùng khối mà anh ta khai thác. Chức năng play() thuộc hợp đồng Trò chơi có tên G-Dot.

function play() public { require(now > 1640392200 && neverPlayed == true); neverPlayed = false; msg.sender.transfer(1500 ether);
}

Hợp đồng này thưởng 1500 ether cho người chơi đầu tiên gọi chức năng chơi. Nhưng như bạn thấy, hàm play chỉ có thể được gọi nếu dấu bây giờ hoặc dấu block.tim của giao dịch có chứa lệnh gọi đến play() chức năng lớn hơn thời gian kỷ nguyên 1640392200.

Người khai thác có thể dễ dàng thao túng dấu thời gian này và bao gồm giao dịch của anh ta là gọi play() hoạt động trong cùng một khối sao cho chính anh ta là người chơi đầu tiên. Bằng cách này, nó được đảm bảo rằng người khai thác sẽ giành chiến thắng trong trò chơi!

Va chạm:

Dấu block.timestamp được sử dụng để tạo các số ngẫu nhiên trong Chính phủ và do đó dễ bị tấn công thao túng thời gian.

Liên hệ với QuillAudits

QuillAudits là một nền tảng kiểm tra hợp đồng thông minh an toàn được thiết kế bởi QuillHash
Công nghệ.
Đây là một nền tảng kiểm toán phân tích và xác minh chặt chẽ các hợp đồng thông minh để kiểm tra các lỗ hổng bảo mật thông qua việc xem xét thủ công hiệu quả với các công cụ phân tích tĩnh và động, máy phân tích khí cũng như máy đồng hóa. Hơn nữa, quá trình kiểm toán cũng bao gồm kiểm tra đơn vị rộng rãi cũng như phân tích cấu trúc.
Chúng tôi tiến hành cả kiểm tra hợp đồng thông minh và kiểm tra thâm nhập để tìm ra tiềm năng
các lỗ hổng bảo mật có thể gây hại cho tính toàn vẹn của nền tảng.

Nếu bạn cần bất kỳ hỗ trợ nào trong việc kiểm tra hợp đồng thông minh, vui lòng liên hệ với các chuyên gia của chúng tôi ở đây!

Để cập nhật công việc của chúng tôi, Hãy tham gia Cộng đồng của chúng tôi: -

Twitter | LinkedIn Facebook | Telegram

Nguồn: https://blog.quillhash.com/2022/01/19/beginners-guide-to-smart-contract-auditing-part-1/

tại chỗ_img

Tin tức mới nhất

tại chỗ_img

Trò chuyện trực tiếp với chúng tôi (chat)

Chào bạn! Làm thế nào để tôi giúp bạn?