Phân tích chi tiết lỗ hổng của trình biên dịch Solidity và các chiến lược phòng ngừa

Phân tích lỗ hổng biên dịch viên Solidity và chiến lược ứng phó

Trình biên dịch là một trong những thành phần cơ bản của hệ thống máy tính hiện đại. Nó là một chương trình máy tính, chức năng chính là chuyển đổi mã nguồn của ngôn ngữ lập trình bậc cao mà con người dễ hiểu và viết thành mã lệnh có thể thực thi được trên CPU hoặc máy ảo bytecode của máy tính.

Đa số các nhà phát triển và nhân viên an ninh thường chú trọng đến sự an toàn của mã ứng dụng, nhưng có thể bỏ qua tính an toàn của chính trình biên dịch. Thực tế, trình biên dịch như một chương trình máy tính cũng tồn tại các lỗ hổng an ninh, và những lỗ hổng này trong một số trường hợp có thể mang lại những rủi ro an ninh nghiêm trọng. Ví dụ, trong quá trình biên dịch và phân tích mã Javascript phía trước, trình biên dịch có thể gặp phải lỗ hổng của động cơ phân tích Javascript, dẫn đến việc người dùng bị kẻ tấn công lợi dụng lỗ hổng để thực hiện mã từ xa khi truy cập vào các trang web độc hại, cuối cùng kiểm soát trình duyệt hoặc thậm chí hệ điều hành của nạn nhân.

Trình biên dịch Solidity cũng không phải là ngoại lệ, tồn tại các lỗ hổng bảo mật trong nhiều phiên bản khác nhau.

Lỗ hổng biên dịch Solidity

Chức năng của trình biên dịch Solidity là chuyển đổi mã hợp đồng thông minh mà các nhà phát triển viết thành mã lệnh EVM( của Ethereum Virtual Machine). Những mã lệnh EVM này được đóng gói và tải lên Ethereum thông qua giao dịch, cuối cùng được EVM phân tích và thực thi.

Cần phân biệt giữa lỗ hổng của trình biên dịch Solidity và lỗ hổng của chính EVM. Lỗ hổng của EVM đề cập đến các vấn đề bảo mật phát sinh khi máy ảo thực thi các lệnh. Do kẻ tấn công có thể tải lên bất kỳ mã nào lên Ethereum, mã này cuối cùng sẽ chạy trong mỗi chương trình khách hàng P2P của Ethereum, nếu EVM có lỗ hổng bảo mật, sẽ ảnh hưởng đến toàn bộ mạng lưới Ethereum, có thể gây ra từ chối dịch vụ (DoS) thậm chí dẫn đến việc toàn bộ chuỗi bị kẻ tấn công chiếm quyền kiểm soát. Tuy nhiên, do thiết kế của EVM tương đối đơn giản và mã nguồn chính không được cập nhật thường xuyên, nên khả năng xảy ra các vấn đề nêu trên là tương đối thấp.

Lỗi biên dịch Solidity đề cập đến các vấn đề khi trình biên dịch chuyển đổi Solidity thành mã EVM. Khác với trường hợp trình duyệt sẽ biên dịch và thực thi Javascript trên máy tính của người dùng, quá trình biên dịch Solidity chỉ diễn ra trên máy tính của nhà phát triển hợp đồng thông minh và sẽ không chạy trên Ethereum. Do đó, lỗi biên dịch Solidity sẽ không ảnh hưởng trực tiếp đến mạng Ethereum.

Một trong những mối nguy lớn của lỗ hổng biên dịch Solidity là nó có thể dẫn đến mã EVM được tạo ra không nhất quán với những gì mà nhà phát triển hợp đồng thông minh mong đợi. Do các hợp đồng thông minh trên Ethereum thường liên quan đến tài sản tiền điện tử của người dùng, nên bất kỳ lỗi hợp đồng thông minh nào do biên dịch viên gây ra đều có thể dẫn đến tổn thất tài sản của người dùng, gây ra hậu quả nghiêm trọng.

Các nhà phát triển và nhân viên kiểm toán hợp đồng có thể tập trung vào các vấn đề thực hiện logic mã hợp đồng, cũng như các vấn đề an ninh trên lớp Solidity như tấn công tái nhập và tràn số nguyên. Tuy nhiên, rất khó để phát hiện các lỗ hổng của trình biên dịch Solidity chỉ thông qua việc kiểm toán logic mã nguồn hợp đồng. Cần phải phân tích kết hợp giữa phiên bản trình biên dịch cụ thể và các mẫu mã cụ thể để xác định liệu hợp đồng thông minh có bị ảnh hưởng bởi lỗ hổng của trình biên dịch hay không.

Phân tích lỗ hổng biên dịch Solidity và biện pháp ứng phó

Ví dụ về lỗ hổng biên dịch viên Solidity

Dưới đây là một số ví dụ về lỗ hổng của trình biên dịch Solidity thực tế, thể hiện hình thức cụ thể, nguyên nhân và tác hại của chúng.

SOL-2016-9 HighOrderByteCleanStorage

Lỗ hổng này tồn tại trong các phiên bản biên dịch Solidity sớm hơn (>=0.1.6 <0.4.4).

Xem xét mã sau:

solidity hợp đồng C { uint32 a = 0x1234; uint32 b = 0; chức năng f() công khai { a += 1; } function run() public view returns (uint) { return b; } }

Biến storage b không bị sửa đổi, vì vậy hàm run() nên trả về giá trị mặc định 0. Nhưng thực tế, trong mã được tạo ra bởi phiên bản trình biên dịch có lỗ hổng, run() sẽ trả về 1.

Trong trường hợp không hiểu được lỗ hổng của trình biên dịch, các nhà phát triển thông thường rất khó phát hiện lỗi trong đoạn mã trên chỉ qua việc xem xét mã đơn giản. Đoạn mã ví dụ này khá đơn giản, có thể không gây ra hậu quả nghiêm trọng. Nhưng nếu biến b được sử dụng cho xác thực quyền, ghi chép tài sản, thì sự không nhất quán này có thể dẫn đến hậu quả rất nghiêm trọng.

Nguyên nhân của hiện tượng bất thường này là do EVM sử dụng máy ảo kiểu ngăn xếp, mỗi phần tử trong ngăn xếp có kích thước 32 byte ( tức là kích thước biến uint256 ). Mặt khác, mỗi slot trong bộ nhớ cơ sở cũng có kích thước 32 byte. Ngôn ngữ Solidity hỗ trợ các loại dữ liệu nhỏ hơn 32 byte như uint32, và biên dịch viên khi xử lý các loại biến này cần thực hiện các thao tác làm sạch thích hợp trên phần cao (clean up) để đảm bảo tính chính xác của dữ liệu. Trong trường hợp nêu trên, khi phép cộng gây ra tràn số nguyên, biên dịch viên không thực hiện đúng thao tác làm sạch đối với phần cao của kết quả, dẫn đến việc bit 1 ở phần cao sau khi tràn được ghi vào bộ nhớ, cuối cùng ghi đè lên biến a và biến b, làm cho giá trị của biến b bị thay đổi thành 1.

SOL-2022-4 InlineAssemblyMemorySideEffects

Lỗ hổng này tồn tại trong các phiên bản biên dịch của >=0.8.13 <0.8.15. Xem xét đoạn mã sau:

solidity hợp đồng C { function f() public pure returns (uint) { lắp ráp { mstore(0, 0x42) } uint x; lắp ráp { x := mload(0) } return x; } }

Trình biên dịch Solidity không chỉ đơn thuần là dịch ngôn ngữ Solidity sang mã EVM. Nó còn thực hiện phân tích sâu về luồng điều khiển và dữ liệu, thực hiện nhiều quy trình tối ưu hóa biên dịch khác nhau để giảm kích thước mã được tạo ra, tối ưu hóa mức tiêu thụ gas trong quá trình thực thi. Những thao tác tối ưu hóa này rất phổ biến trong các trình biên dịch ngôn ngữ cấp cao khác, nhưng do cần phải xem xét nhiều trường hợp phức tạp, nên cũng dễ phát sinh lỗi hoặc lỗ hổng bảo mật.

Lỗ hổng của đoạn mã trên xuất phát từ loại thao tác tối ưu hóa này. Hãy xem xét một tình huống như sau: nếu trong một hàm có mã sửa đổi dữ liệu tại địa chỉ bộ nhớ 0, nhưng không có bất kỳ nơi nào khác sử dụng dữ liệu đó, thì thực tế có thể loại bỏ mã sửa đổi bộ nhớ 0, từ đó tiết kiệm gas mà không ảnh hưởng đến logic chương trình sau.

Chiến lược tối ưu hóa này bản thân nó không có vấn đề, nhưng trong việc triển khai mã của trình biên dịch Solidity cụ thể, loại tối ưu hóa này chỉ được áp dụng trong một khối assembly đơn lẻ. Đối với mã ví dụ ở trên, việc ghi và truy cập vào bộ nhớ 0 nằm trong hai khối assembly khác nhau, trong khi trình biên dịch chỉ thực hiện tối ưu hóa phân tích cho từng khối assembly riêng biệt. Do không có bất kỳ thao tác đọc nào sau khi ghi vào bộ nhớ 0 trong khối assembly đầu tiên, nên chỉ thị ghi này được xác định là thừa và sẽ bị loại bỏ, dẫn đến lỗi. Trong phiên bản có lỗ hổng, hàm f() sẽ trả về giá trị 0, trong khi thực tế mã trên nên trả về giá trị đúng là 0x42.

SOL-2022-6 AbiReencodingHeadOverflowWithStaticArrayCleanup

Lỗ hổng này ảnh hưởng đến các phiên bản trình biên dịch >= 0.5.8 < 0.8.16. Xem xét mã sau:

solidity hợp đồng C { function f(uint8[4] calldata a) public pure returns (string memory) { return string(abi.encode(a)); } }

Trong trường hợp bình thường, biến a được trả về bởi đoạn mã trên nên là "aaaa". Nhưng trong phiên bản có lỗ hổng, nó sẽ trả về chuỗi rỗng "".

Nguyên nhân của lỗ hổng này là do Solidity thực hiện thao tác abi.encode trên mảng kiểu calldata, đã sai lầm trong việc làm sạch một số dữ liệu, dẫn đến việc sửa đổi dữ liệu khác lân cận, gây ra sự không nhất quán giữa dữ liệu sau khi được mã hóa và giải mã.

Cần lưu ý rằng, Solidity khi thực hiện cuộc gọi bên ngoài và phát sự kiện, sẽ ngầm mã hóa các tham số bằng abi.encode, do đó xác suất xuất hiện của mã lỗi trên sẽ cao hơn so với cảm giác trực quan.

Phân tích lỗ hổng trình biên dịch Solidity và biện pháp đối phó

Đề xuất an toàn

Dựa trên phân tích mô hình đe dọa từ lỗ hổng của trình biên dịch Solidity và tổng hợp các lỗ hổng lịch sử, đưa ra các khuyến nghị sau cho các nhà phát triển và nhân viên an ninh.

Đối với các nhà phát triển:

  • Sử dụng phiên bản trình biên dịch Solidity mới hơn. Mặc dù các phiên bản mới cũng có thể giới thiệu các vấn đề an ninh mới, nhưng các vấn đề an ninh đã biết thường ít hơn so với các phiên bản cũ.

  • Hoàn thiện các trường hợp kiểm tra đơn vị. Hầu hết các lỗi ở cấp trình biên dịch sẽ dẫn đến kết quả thực thi mã không nhất quán với mong đợi. Những vấn đề này rất khó phát hiện qua việc xem xét mã, nhưng rất dễ bị lộ trong giai đoạn kiểm tra. Do đó, bằng cách tăng cường tỷ lệ bao phủ mã, có thể tối đa hóa việc tránh những vấn đề này.

  • Cố gắng tránh sử dụng lắp ráp nội tuyến, mã hóa và giải mã abi cho mảng nhiều chiều và cấu trúc phức tạp, và tránh việc theo đuổi kỹ thuật một cách mù quáng khi không có nhu cầu rõ ràng. Dựa trên việc tổng hợp các lỗ hổng lịch sử, phần lớn các lỗ hổng liên quan đến các thao tác như lắp ráp nội tuyến và bộ mã hóa abi. Trình biên dịch thực sự dễ gặp lỗi khi xử lý các đặc điểm ngôn ngữ phức tạp. Mặt khác, các nhà phát triển cũng dễ mắc phải những hiểu lầm khi sử dụng các đặc điểm mới, dẫn đến các vấn đề an ninh.

Đối với nhân viên an ninh:

  • Khi thực hiện kiểm toán an ninh mã Solidity, đừng bỏ qua các rủi ro an ninh có thể do trình biên dịch Solidity gây ra. Mục kiểm tra tương ứng trong Phân loại Điểm yếu Hợp đồng Thông minh(SWC) là SWC-102: Phiên bản Trình biên dịch Cũ.

  • Trong quy trình phát triển SDL nội bộ, thúc giục đội ngũ phát triển nâng cấp phiên bản biên dịch Solidity và có thể xem xét việc đưa vào quy trình CI/CD để tự động kiểm tra phiên bản biên dịch.

  • Nhưng không cần quá hoảng sợ về lỗ hổng biên dịch, phần lớn các lỗ hổng biên dịch chỉ được kích hoạt trong các mẫu mã cụ thể, và không phải hợp đồng được biên dịch bằng phiên bản biên dịch có lỗ hổng nhất định sẽ luôn tồn tại rủi ro về an ninh, tác động thực sự đến an ninh cần được đánh giá cụ thể dựa trên tình hình dự án.

Một số tài nguyên hữu ích:

  • Thông báo an ninh do đội ngũ Solidity định kỳ phát hành:

  • Danh sách lỗi được cập nhật định kỳ từ kho chính thức của Solidity:

  • Danh sách lỗi biên dịch của các phiên bản:

  • Trên trang Code của Contract trên Etherscan, biểu tượng hình tam giác dấu chấm than ở góc trên bên phải có thể thông báo về các lỗ hổng bảo mật hiện có của phiên bản trình biên dịch.

Tóm tắt

Bài viết này bắt đầu từ khái niệm cơ bản về trình biên dịch, giới thiệu lỗ hổng của trình biên dịch Solidity và phân tích các rủi ro an ninh có thể xảy ra trong môi trường phát triển Ethereum thực tế, cuối cùng cung cấp cho các nhà phát triển và nhân viên an ninh một số lời khuyên an toàn thực tế.

Phân tích lỗ hổng biên dịch Solidity và biện pháp ứng phó

ETH1.74%
Xem bản gốc
Trang này có thể chứa nội dung của bên thứ ba, được cung cấp chỉ nhằm mục đích thông tin (không phải là tuyên bố/bảo đảm) và không được coi là sự chứng thực cho quan điểm của Gate hoặc là lời khuyên về tài chính hoặc chuyên môn. Xem Tuyên bố từ chối trách nhiệm để biết chi tiết.
  • Phần thưởng
  • 5
  • Chia sẻ
Bình luận
0/400
ZenZKPlayervip
· 07-30 05:45
Lỗ hổng tràn có chút hả hê
Xem bản gốcTrả lời0
OffchainWinnervip
· 07-30 00:55
Viết mã hoàn toàn không chú ý đến lỗi
Xem bản gốcTrả lời0
OnchainSnipervip
· 07-30 00:47
Trình biên dịch gặp sự cố? Đã từng gặp phải rồi.
Xem bản gốcTrả lời0
UnluckyValidatorvip
· 07-30 00:44
Trình biên dịch đều có lỗ hổng, khiến người ta sợ hãi.
Xem bản gốcTrả lời0
LiquiditySurfervip
· 07-30 00:41
Gọi developer nhanh chóng sửa lỗi
Xem bản gốcTrả lời0
Giao dịch tiền điện tử mọi lúc mọi nơi
qrCode
Quét để tải xuống ứng dụng Gate
Cộng đồng
Tiếng Việt
  • 简体中文
  • English
  • Tiếng Việt
  • 繁體中文
  • Español
  • Русский
  • Français (Afrique)
  • Português (Portugal)
  • Bahasa Indonesia
  • 日本語
  • بالعربية
  • Українська
  • Português (Brasil)