paint-brush
Giải quyết lỗ hổng Tràn/Dòng số nguyên trong Hợp đồng thông minhtừ tác giả@dansierrasam79
1,937 lượt đọc
1,937 lượt đọc

Giải quyết lỗ hổng Tràn/Dòng số nguyên trong Hợp đồng thông minh

từ tác giả Daniel Chakraborty9m2023/02/11
Read on Terminal Reader

dài quá đọc không nổi

Các kiểu dữ liệu là thứ mà các lập trình viên có thể dành thời gian để chỉ định hoặc không, tùy thuộc vào ngôn ngữ lập trình mà họ viết mã. Các kiểu dữ liệu rất quan trọng đối với các phép toán số học chính, nhưng có phạm vi giới hạn đối với bất kỳ tính toán nào. Ví dụ phổ biến nhất về tràn số nguyên trong thế giới thực xảy ra trên các phương tiện, trong đó giá trị số dặm đã đi được đặt lại thành 000000.
featured image - Giải quyết lỗ hổng Tràn/Dòng số nguyên trong Hợp đồng thông minh
Daniel Chakraborty HackerNoon profile picture


Hầu như tất cả chúng ta đều đã từng sử dụng Google Trang tính hoặc Microsoft Excel để nhập dữ liệu cho một số tính toán. Giả sử bạn muốn nhập tên nhân viên, số điện thoại, chức danh và mức lương họ kiếm được.


Ở dạng đơn giản nhất, đây là giao diện của một bản ghi hoặc trường hợp, trong Trang tính hoặc Excel:

Bản ghi hoặc trường hợp trong Excel hoặc Google Trang tính

Như bạn có thể biết, cả tên và chức danh của nhân viên đều bao gồm văn bản trong khi Số điện thoại và Mức lương bao gồm một chuỗi số.


Vì vậy, từ quan điểm ngữ nghĩa, chúng ta, với tư cách là con người, hiểu ý nghĩa của các trường này trong thế giới thực và có thể phân biệt giữa chúng.


Rõ ràng, trong khi bạn không cần bằng cấp về Khoa học Máy tính để phân biệt, thì trình biên dịch hoặc trình thông dịch xử lý dữ liệu này như thế nào?

Loại dữ liệu

Đây là nơi các loại dữ liệu xuất hiện và là thứ mà các lập trình viên dành thời gian để chỉ định hoặc không, tùy thuộc vào ngôn ngữ lập trình mà họ viết mã.


Nói cách khác, các điểm dữ liệu dưới tên nhân viên và tiêu đề được gọi là chuỗi. Tất nhiên, tiền lương rõ ràng là một số nguyên, do không có dấu thập phân. Nói một cách đơn giản, đây là những kiểu dữ liệu phải được khai báo như vậy khi bạn viết mã, để chỉ những thao tác phù hợp với kiểu dữ liệu đó mới được thực hiện.


Đây là cách chúng ta khai báo kiểu dữ liệu số nguyên trong Solidity:

Điều đó nói rằng, trường Số điện thoại trong bảng tính ở trên chứa một điểm dữ liệu sẽ được sử dụng làm một chuỗi duy nhất, nhưng cuộc thảo luận đó sẽ dành cho một ngày khác. Hiện tại, trọng tâm của chúng ta sẽ là kiểu dữ liệu nguyên thủy mà tất cả chúng ta đã thực hiện các phép tính số học cơ bản.


Đúng vậy, chúng ta đang nói về kiểu dữ liệu số nguyên, mặc dù quan trọng đối với các phép toán số học chính, nhưng lại có phạm vi giới hạn đối với bất kỳ tính toán nào.

Tại sao lại xảy ra Tràn/Dòng số nguyên?

Có lẽ, ví dụ phổ biến nhất về tràn số nguyên trong thế giới thực xảy ra trên các phương tiện. Còn được gọi là đồng hồ đo quãng đường, những thiết bị này thường theo dõi số dặm xe đã đi.


Vì vậy, điều gì sẽ xảy ra khi giá trị số dặm đã đi đạt đến giá trị số nguyên không dấu là 999999 trong đồng hồ đo quãng đường có sáu chữ số?


Lý tưởng nhất là khi thêm một dặm nữa, giá trị này sẽ đạt 1000000, phải không? Nhưng điều này không xảy ra vì có cung cấp cho chữ số thứ bảy.


Thay vào đó, giá trị của số dặm đã đi đặt lại thành 000000, như hình bên dưới:

Tràn số nguyên, trong một công tơ mét


Theo định nghĩa, do không có chữ số thứ bảy nên điều này dẫn đến "tràn" vì giá trị chính xác không được biểu thị.


Bạn nhận được ảnh rồi phải không?


Ngược lại, điều ngược lại cũng có thể xảy ra ngay cả khi điều này không quá phổ biến. Nói cách khác, khi giá trị được ghi lại nhỏ hơn giá trị nhỏ nhất có sẵn trong phạm vi và giá trị này còn được gọi là 'tràn'.


Như chúng ta đã biết, máy tính sẽ lưu trữ các số nguyên trong bộ nhớ dưới dạng tương đương nhị phân. Bây giờ, để đơn giản, giả sử rằng bạn đang sử dụng một thanh ghi 8-bit.


Vì vậy, nếu bạn muốn lưu trữ số nguyên không dấu 511, số này sẽ được chia thành:


= 2⁸*1 + 2⁷*1 + 2⁶*1 + 2⁵*1 + 2⁴*1 + 2³*1 + 2²*1 + 2¹*1 + 2⁰*1

= 256 + 128 + 64 + 32 + 16 + 8 + 4 + 2 + 1

= 111111111


Trong đó mỗi bit là 1 và như bạn có thể biết, bạn không thể lưu trữ giá trị cao hơn.


Mặt khác, nếu bạn muốn lưu trữ số 0 trong thanh ghi 8 bit, thì đây là giao diện:


= 2⁸*0 + 2⁷*0 + 2⁶*0 + 2⁵*0 + 2⁴*0 + 2³*0 + 2²*0 + 2¹*0 + 2⁰*0

= 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0

= 000000000


Trong đó mỗi bit là 0, điều này sẽ cho bạn biết rằng bạn không thể lưu trữ giá trị thấp hơn.


Nói cách khác, phạm vi số nguyên được phép cho thanh ghi 8 bit như vậy là 0–511. Vì vậy, có thể lưu trữ số nguyên 512 hoặc -1 trong một thanh ghi như vậy không?


Dĩ nhiên là không. Do đó, bạn sẽ lưu trữ một giá trị giống với giá trị đặt lại của số dặm đã đi trong ví dụ về công tơ mét, nhưng dưới dạng giá trị nhị phân.


Rõ ràng, bạn cần các thanh ghi có thêm một vài bit để chứa một số như vậy một cách thoải mái. Hoặc nếu không, có nguy cơ xảy ra tình trạng tràn một lần nữa.


Trong trường hợp số nguyên có dấu, chúng tôi cũng lưu trữ số nguyên âm. Vì vậy, khi chúng tôi cố gắng lưu trữ một số nhỏ hơn phạm vi được chấp nhận hoặc nhỏ hơn 0, như được hiển thị ở trên, sẽ xảy ra tình trạng tràn.


Tuy nhiên, một lần nữa, vì mục đích của việc thực hiện bất kỳ tính toán nào là để thu được kết quả xác định, điều này tốt nhất có thể gây khó chịu nhưng tệ nhất là gây ra thiệt hại hàng triệu USD. Đặc biệt, khi các lỗi tràn hoặc tràn số nguyên này xảy ra trong hợp đồng thông minh.

Tại sao Lỗ hổng Tràn/Dòng số nguyên lại có thể gây hại như vậy?

Mặc dù tràn và tràn số nguyên đã tồn tại trong nhiều thập kỷ, nhưng sự tồn tại của chúng như một lỗi trong hợp đồng thông minh đã làm tăng rủi ro. Khi những kẻ tấn công sử dụng các lỗi như vậy, chúng có thể rút cạn hợp đồng thông minh với số lượng lớn mã thông báo.


Có lẽ lần đầu tiên loại lỗi này xảy ra là với khối 74638, khối này đã tạo ra hàng tỷ Bitcoin cho ba địa chỉ. Sẽ mất hàng giờ để giải quyết lỗi này bằng một soft fork và loại bỏ khối, do đó khiến giao dịch không hợp lệ.


Đầu tiên, các giao dịch có giá trị lớn hơn 21 triệu bitcoin đã bị từ chối. Điều này không khác gì đối với các giao dịch tràn, giống như giao dịch đã gửi rất nhiều tiền vào ba tài khoản nói trên.


Tuy nhiên, các hợp đồng thông minh Ethereum cũng đã trải qua tình trạng tràn và tràn số nguyên, với BeautyChain cũng là một ví dụ nổi bật.


Trong trường hợp này, hợp đồng thông minh chứa một dòng mã bị lỗi:


Đoạn mã đó đã gây ra Tràn số nguyên


Do đó, về mặt lý thuyết, những kẻ tấn công có thể nhận được số lượng mã thông báo BEC không giới hạn, về mặt lý thuyết có thể lên tới giá trị là (2²⁵⁶)-1.


Chức năng chuyển hàng loạt dễ bị tấn công


Bây giờ, hãy xem xét một ví dụ khác về hợp đồng thông minh trong đó xảy ra tình trạng tràn/tràn số nguyên.

Phá vỡ lỗ hổng tràn/tràn số nguyên trong hợp đồng thông minh

Thoạt nhìn, có hai hợp đồng tương tác trong ví dụ này và thể hiện điều gì xảy ra trong trường hợp tràn số nguyên.


Như bạn có thể thấy bên dưới, TimeLock hợp đồng, cho phép bạn gửi và rút tiền nhưng có một điểm khác biệt: bạn chỉ có thể thực hiện thao tác sau chỉ sau một khoảng thời gian. Trong trường hợp này, bạn chỉ có thể rút tiền của mình trong thời gian một tuần.


Hợp đồng TimeLock


Tuy nhiên, một khi bạn gọi chức năng tấn công trong hợp đồng Tấn công, thời gian khóa tại chỗ không còn hiệu lực nữa và đó là lý do tại sao kẻ tấn công có thể rút số dư ngay lập tức.

Nói cách khác, do gây tràn số nguyên với câu lệnh type(uint).max+1-timeLock.locktime(address(this)) nên khóa thời gian bị loại bỏ.


Sử dụng Tràn số nguyên, khóa thời gian cho khoản tiền gửi được loại bỏ ngay lập tức


Ví dụ: khi bạn đã triển khai cả hai hợp đồng thông minh bằng mã ở trên, bạn có thể kiểm tra xem khóa thời gian có giữ hay không bằng cách gọi các chức năng gửi và rút tiền trong hợp đồng Khóa thời gian, như được hiển thị bên dưới:


Số dư, sau khi gửi 2 ETH


Như bạn có thể thấy, bằng cách chọn số lượng 2 Ether, chúng tôi có được số dư hợp đồng thông minh là 2 Ether được hiển thị ở trên:


Gửi 2 ETH


Cụ thể, có thể kiểm tra địa chỉ cụ thể chứa số dư 2 Ether bằng cách thêm địa chỉ vào trường của chức năng số dư và nhấp vào nút số dư:


Địa chỉ nào giữ 2 ETH?


Tuy nhiên, như đã đề cập ở trên, bạn chưa thể rút các khoản tiền này do thời gian khóa tại chỗ. Khi bạn nhìn vào bảng điều khiển sau khi nhấn chức năng rút tiền, bạn sẽ thấy một lỗi được biểu thị bằng ký hiệu 'x' màu đỏ. Như bạn có thể thấy bên dưới, lý do của lỗi này được cung cấp bởi hợp đồng là “Thời gian khóa chưa hết hạn”:


Lỗi thời gian khóa chưa hết hạn


Bây giờ, hãy xem hợp đồng Tấn công đã triển khai, như hình bên dưới:



Bây giờ, để gọi chức năng tấn công, bạn cần gửi giá trị từ 1 Ether trở lên. Vì vậy, trong trường hợp này, chúng tôi đã chọn 2 Ether, như hình bên dưới:


Gửi 2 ETH trước!


Sau đó, nhấn 'tấn công'. Bạn sẽ thấy 2 Ether mà bạn đã gửi sẽ được rút ngay lập tức và được thêm vào hợp đồng Attack, bằng chứng là số dư của 2 Ether bên dưới:


2 ETH được chuyển sang hợp đồng thông minh Attack


Rõ ràng, điều này không nên xảy ra do thời gian khóa dài sẽ có hiệu lực ngay sau khi bạn gửi tiền. Tất nhiên, như chúng ta biết câu lệnh type(uint).max+1-timeLock.locktime(address(this)) giảm thời gian khóa bằng cách sử dụng hàm gainLockTime. Đây chính xác là lý do tại sao chúng tôi có thể rút số dư Ether ngay lập tức.


Điều này đưa chúng ta đến một câu hỏi rõ ràng: có cách nào để khắc phục lỗ hổng tràn và tràn số nguyên không?

2 cách để khắc phục lỗ hổng tràn/dưới số nguyên

Khi nhận ra rằng lỗ hổng tràn/tràn số nguyên có thể tàn phá nghiêm trọng, một số bản sửa lỗi cho lỗi này đã được triển khai. Hãy xem xét cả hai bản sửa lỗi này và cách chúng khắc phục lỗi như vậy:


Phương pháp 1: Sử dụng thư viện SafeMath của OpenZeppelin

Open Zeppelin, với tư cách là một tổ chức, cung cấp nhiều dịch vụ và công nghệ an ninh mạng, với thư viện SafeMath là một phần của kho lưu trữ phát triển hợp đồng thông minh. Repo này chứa các hợp đồng có thể được nhập vào mã hợp đồng thông minh của bạn, với thư viện SafeMath là một trong số đó.


Hãy xem cách một trong các hàm trong SafeMath.sol kiểm tra lỗi tràn số nguyên:


tryAdd hàm SafeMath


Bây giờ, khi việc tính toán a+b đã diễn ra, hãy kiểm tra xem liệu c<a có xảy ra hay không. Tất nhiên, điều này chỉ đúng trong trường hợp tràn số nguyên.


Với phiên bản trình biên dịch của Solidity đạt 0.8.0 trở lên, các kiểm tra tràn và tràn số nguyên hiện đã được tích hợp sẵn. Vì vậy, người ta vẫn có thể sử dụng thư viện này để kiểm tra lỗ hổng này, cả khi sử dụng ngôn ngữ và thư viện này. Tất nhiên, nếu hợp đồng thông minh của bạn yêu cầu phiên bản trình biên dịch nhỏ hơn 0.8.+, thì bạn phải sử dụng thư viện này để tránh tràn hoặc tràn.


Cách 2: Sử dụng phiên bản 0.8.0 của trình biên dịch

Bây giờ, như đã đề cập trước đó, nếu đối với hợp đồng thông minh của bạn, bạn đang sử dụng phiên bản trình biên dịch từ 0.8.0 trở lên, thì phiên bản này có trình kiểm tra tích hợp sẵn cho lỗ hổng bảo mật đó.


Trên thực tế, chỉ để xác minh xem nó có hoạt động với hợp đồng thông minh ở trên hay không, khi thay đổi phiên bản trình biên dịch thành “^0.8.0” và triển khai lại nó, sẽ nhận được lỗi 'hoàn nguyên' sau:


Hoàn nguyên lỗi ngăn Tràn số nguyên


Tất nhiên, không có khoản tiền gửi 2 Ether nào được thực hiện, đó là do việc kiểm tra giá trị khóa thời gian bị tràn. Do đó, không thể rút tiền do không có khoản tiền nào được gửi ngay từ đầu.


Việc chuyển 2 ETH sang hợp đồng Tấn công đã bị ngăn chặn


Không còn nghi ngờ gì nữa, lệnh gọi hàm Attack.attack() không hoạt động ở đây, vì vậy tất cả đều ổn!

Tóm tắt lỗ hổng tràn/dưới dòng

Nếu có bất cứ điều gì mà bạn nên thu thập từ bài đăng blog dài này, thì đó là việc bỏ qua lỗ hổng này, như từ cuộc tấn công BEC, có thể gây tốn kém. Như bạn cũng có thể thấy, nếu không được chọn, rất dễ xảy ra các lỗi không nguy hiểm. Hay chỉ đơn giản là hacker có thể khai thác lỗ hổng này.


Nói về điều này, và sử dụng hiểu biết của chúng tôi về cách thức cuộc tấn công BEC diễn ra, việc nhận ra lỗ hổng này có thể giúp ích rất nhiều trong việc ngăn chặn bất kỳ cuộc tấn công nào khi viết hợp đồng thông minh của bạn, nhờ vào các bản sửa lỗi được cung cấp. Ngay cả khi có một số lỗ hổng hợp đồng thông minh khác đang chờ bạn.