Mẫu proxy cho phép các hợp đồng thông minh nâng cấp logic của chúng trong khi vẫn duy trì các địa chỉ và giá trị trạng thái trên chuỗi của chúng. Cuộc gọi đến hợp đồng proxy sẽ thực thi mã từ hợp đồng logic thông qua delegateCall để sửa đổi trạng thái của hợp đồng proxy.
Bài viết này sẽ cung cấp tổng quan về các loại hợp đồng ủy quyền, các sự cố bảo mật liên quan và đề xuất cũng như các phương pháp hay nhất để sử dụng hợp đồng ủy quyền.
Giới thiệu về Hợp đồng có thể nâng cấp và Chế độ ủy quyền
Chúng ta đều biết tính năng "không thể giả mạo" của chuỗi khối và mã hợp đồng thông minh không thể sửa đổi sau khi được triển khai trên chuỗi khối.
Vì vậy, khi các nhà phát triển muốn cập nhật mã hợp đồng để nâng cấp logic, sửa lỗi hoặc cập nhật bảo mật, họ phải triển khai một hợp đồng mới và một địa chỉ hợp đồng mới sẽ được tạo.
Để giải quyết vấn đề này, bạn có thể sử dụng chế độ proxy.
Chế độ proxy nhận ra khả năng nâng cấp của hợp đồng mà không thay đổi địa chỉ triển khai của hợp đồng, hiện là chế độ nâng cấp hợp đồng phổ biến nhất.
Chế độ proxy là một hệ thống hợp đồng có thể nâng cấp, bao gồm hợp đồng proxy và hợp đồng triển khai logic.
Hợp đồng proxy xử lý tương tác người dùng và lưu trữ dữ liệu và trạng thái hợp đồng. Cuộc gọi của người dùng tới hợp đồng proxy sẽ thực thi mã từ hợp đồng logic thông qua delegatecall(), do đó thay đổi trạng thái của hợp đồng proxy. Việc nâng cấp được thực hiện bằng cách cập nhật địa chỉ hợp đồng logic được ghi trong khe lưu trữ được xác định trước của hợp đồng proxy.
Ba chế độ proxy thông thường hơn là proxy trong suốt, proxy UUPS và proxy Beacon.
Proxy minh bạch
Trong chế độ proxy minh bạch, chức năng nâng cấp được thực hiện trong hợp đồng proxy. Vai trò quản trị viên của hợp đồng proxy được trao quyền trực tiếp vận hành hợp đồng proxy để cập nhật địa chỉ triển khai logic tương ứng với proxy. Người gọi không có đặc quyền quản trị viên sẽ ủy quyền cuộc gọi của họ cho hợp đồng thực hiện.
Lưu ý: Quản trị viên hợp đồng proxy không thể đóng vai trò chính trong việc triển khai hợp đồng theo logic, thậm chí không thể là người dùng thông thường vì quản trị viên proxy không thể tương tác với hợp đồng triển khai.
Ủy quyền UUPS
Trong chế độ UUPS (Tiêu chuẩn proxy có thể nâng cấp toàn cầu), chức năng nâng cấp hợp đồng được triển khai trong hợp đồng logic. Do cơ chế nâng cấp được lưu trữ trong hợp đồng logic nên phiên bản nâng cấp có thể xóa logic liên quan đến nâng cấp để cấm nâng cấp trong tương lai. Trong chế độ này, tất cả các cuộc gọi đến hợp đồng proxy được chuyển tiếp đến hợp đồng thực hiện logic.
Proxy báo hiệu
Chế độ proxy Beacon cho phép nhiều hợp đồng proxy chia sẻ cùng một triển khai logic bằng cách tham chiếu hợp đồng Beacon. Hợp đồng Beacon cung cấp địa chỉ của hợp đồng triển khai logic cho hợp đồng proxy được gọi. Khi nâng cấp lên địa chỉ triển khai logic mới, chỉ cần cập nhật địa chỉ được ghi trong hợp đồng Beacon.
Sự cố lạm dụng proxy và bảo mật
Các nhà phát triển có thể sử dụng các hợp đồng chế độ proxy để triển khai các hệ thống hợp đồng có thể nâng cấp. Tuy nhiên, chế độ proxy cũng có ngưỡng hoạt động nhất định, nếu sử dụng không đúng cách có thể gây ra các vấn đề bảo mật nghiêm trọng cho dự án. Các phần sau giới thiệu các sự cố liên quan đến lạm dụng proxy và rủi ro tập trung mà proxy gây ra.
Tiết lộ khóa được quản lý proxy
Quản trị viên proxy kiểm soát cơ chế nâng cấp của chế độ proxy minh bạch, nếu khóa riêng của quản trị viên bị rò rỉ, kẻ tấn công có thể nâng cấp hợp đồng logic và thực thi logic độc hại của riêng chúng trên trạng thái proxy.
Vào ngày 5 tháng 3 năm 2021, PAID Network đã phải hứng chịu một cuộc tấn công "đúc tiền" do quản lý khóa cá nhân kém. Mạng TRẢ TIỀN đã bị khai thác bởi kẻ tấn công đã đánh cắp khóa riêng của quản trị viên proxy và kích hoạt cơ chế nâng cấp để thay đổi hợp đồng logic.
Sau khi nâng cấp, kẻ tấn công có thể phá hủy TRẢ TIỀN của người dùng và đúc một lô TRẢ TIỀN cho chính mình, có thể bán sau này. Bản thân mã này không có lỗ hổng bảo mật, nhưng kẻ tấn công đã lấy được khóa riêng để nâng cấp hợp đồng từ quản trị viên.
** Triển khai proxy UUPS chưa được khởi tạo **
Đối với chế độ proxy UUPS, trong quá trình khởi tạo hợp đồng proxy, các tham số ban đầu được người gọi chuyển đến hợp đồng proxy, sau đó hợp đồng proxy gọi hàm initialize() trong hợp đồng logic để đạt được quá trình khởi tạo.
Hàm khởi tạo () thường được bảo vệ bằng công cụ sửa đổi "bộ khởi tạo" để hạn chế hàm chỉ được gọi một lần. Sau khi gọi hàm initialize(), từ quan điểm của hợp đồng proxy, hợp đồng logic được khởi tạo.
Tuy nhiên, từ quan điểm của hợp đồng logic, hợp đồng logic không được khởi tạo vì phương thức khởi tạo() không được gọi trực tiếp trong hợp đồng logic. Cho rằng bản thân hợp đồng logic không được khởi tạo, bất kỳ ai cũng có thể gọi hàm initialize() để khởi tạo nó, đặt biến trạng thái thành một giá trị độc hại và có khả năng chiếm đoạt hợp đồng logic.
Tác động của một hợp đồng logic bị chiếm dụng phụ thuộc vào mã hợp đồng trong hệ thống. Trong trường hợp xấu nhất, kẻ tấn công có thể nâng cấp hợp đồng logic trong chế độ proxy UUPS thành hợp đồng độc hại và thực hiện lệnh gọi chức năng "tự hủy", điều này có thể khiến toàn bộ hợp đồng proxy trở nên vô dụng và tài sản trong hợp đồng sẽ bị phá hủy vĩnh viễn bị mất.
trường hợp
① Đóng băng nhiều chữ ký chẵn lẻ: Hợp đồng logic không được khởi tạo. Kẻ tấn công kích hoạt quá trình khởi tạo nhiều ví và khóa ether trong hợp đồng bằng cách gọi hàm tự hủy().
② Harvest Finance, Teller, KeeperDAO và Rivermen đều sử dụng các hợp đồng logic chưa được khởi tạo, điều này sẽ cho phép kẻ tấn công tùy ý thiết lập các tham số khởi tạo của hợp đồng và thực hiện tự hủy () trong khi ủy nhiệm () để hủy hợp đồng ủy quyền.
Xung đột bộ nhớ
Trong một hệ thống hợp đồng có thể nâng cấp, hợp đồng ủy quyền không khai báo các biến trạng thái mà sử dụng các vị trí lưu trữ giả ngẫu nhiên để lưu trữ dữ liệu quan trọng.
Hợp đồng proxy lưu trữ các giá trị của các biến trạng thái hợp đồng logic liên quan đến nơi chúng được khai báo. Nếu hợp đồng proxy khai báo các biến trạng thái của chính nó và cả hợp đồng proxy và hợp đồng logic đều cố gắng sử dụng cùng một vị trí lưu trữ, xung đột lưu trữ sẽ xảy ra.
Hợp đồng proxy do thư viện OpenZeppelin cung cấp không khai báo các biến trạng thái trong hợp đồng mà dựa trên tiêu chuẩn EIP 1967, lưu giá trị cần lưu trữ (chẳng hạn như địa chỉ quản lý) trong một khe lưu trữ cụ thể để tránh xung đột.
trường hợp
Vào ngày 23 tháng 7 năm 2022, theo giờ Bắc Kinh, nền tảng âm nhạc phi tập trung Audius đã bị tấn công. Sự cố xảy ra do việc đưa ra logic mới trong hợp đồng ủy quyền, dẫn đến xung đột lưu trữ.
Hợp đồng proxy khai báo biến trạng thái địa chỉ proxyAdmin và giá trị của nó sẽ được đọc không chính xác khi mã hợp đồng logic được thực thi.
Giá trị của proxyAdmin do bên dự án tùy chỉnh bị coi nhầm thành giá trị của khởi tạo và được khởi tạo, do đó công cụ sửa đổi trình khởi tạo trả về kết quả sai, điều này cho phép kẻ tấn công gọi lại hàm initialize() và cấp cho mình quyền quản lý hợp đồng. Sau đó, những kẻ tấn công đã thay đổi các tham số biểu quyết và thông qua đề xuất độc hại của chúng để đánh cắp tài sản của Audius.
Gọi delegatecall() trong hợp đồng logic hoặc hợp đồng không đáng tin cậy
Giả sử delegatecall() tồn tại trong hợp đồng logic và hợp đồng không xác thực đúng mục tiêu của cuộc gọi. Trong trường hợp này, kẻ tấn công có thể khai thác chức năng này để thực hiện các cuộc gọi đến các hợp đồng độc hại mà chúng kiểm soát, phá hoại việc triển khai logic hoặc để thực thi logic tùy chỉnh.
Tương tự, nếu có một hàm address.call() không hạn chế trong hợp đồng logic, thì một khi kẻ tấn công cung cấp các trường dữ liệu và địa chỉ một cách ác ý, nó có thể được sử dụng làm hợp đồng ủy quyền.
trường hợp
Các cuộc tấn công Pickle Finance, Furucombo và dYdX.
Trong những sự cố này, hợp đồng dễ bị tấn công đã được phê duyệt bởi mã thông báo của người dùng và có một lệnh gọi()/delegatecall() trong hợp đồng do người dùng cung cấp để gọi địa chỉ và dữ liệu của hợp đồng, kẻ tấn công sẽ có thể gọi hợp đồng chức năng transferFrom() để rút số dư của người dùng. Trong sự cố dYdX, dYdX đã thực hiện cuộc tấn công mũ trắng của riêng họ để bảo vệ tiền.
Thực hành tốt nhất
nói chung là
(1) Chỉ sử dụng chế độ proxy khi cần thiết
Không phải mọi hợp đồng đều cần phải được nâng cấp. Như đã trình bày ở trên, có nhiều rủi ro liên quan đến việc sử dụng mẫu proxy. Thuộc tính "có thể nâng cấp" cũng gây ra các vấn đề về niềm tin, vì quản trị viên proxy có thể nâng cấp hợp đồng mà không cần sự đồng ý của cộng đồng. Chúng tôi khuyên bạn chỉ nên tích hợp mẫu proxy vào các dự án khi cần thiết.
(2) Không sửa đổi thư viện proxy
Thư viện hợp đồng proxy rất phức tạp, đặc biệt là phần liên quan đến cơ chế nâng cấp và quản lý lưu trữ. Bất kỳ lỗi nào trong quá trình sửa đổi sẽ ảnh hưởng đến hoạt động của hợp đồng proxy và logic. Một số lượng lớn các lỗi nghiêm trọng liên quan đến tác nhân mà chúng tôi đã tìm thấy trong quá trình kiểm tra của mình là do sửa đổi thư viện tác nhân không chính xác. Sự cố Audius là một ví dụ điển hình về hậu quả của việc sửa đổi hợp đồng đại lý không đúng cách.
Các điểm chính của quản lý và vận hành hợp đồng đại lý
(1) Khởi tạo hợp đồng logic
Kẻ tấn công có thể chiếm lấy một hợp đồng logic chưa được khởi tạo và có khả năng xâm phạm hệ thống hợp đồng ủy quyền. Vì vậy, vui lòng khởi tạo hợp đồng logic sau khi triển khai hoặc sử dụng _disableInitializers() trong hàm tạo của hợp đồng logic để tự động tắt khởi tạo.
(2) Đảm bảo tính bảo mật của tài khoản quản lý đại lý
Hệ thống hợp đồng có thể nâng cấp thường yêu cầu vai trò đặc quyền là "quản trị viên ủy quyền" để quản lý việc nâng cấp hợp đồng. Nếu khóa quản lý bị rò rỉ, kẻ tấn công có thể tự do nâng cấp hợp đồng thành hợp đồng độc hại, có thể đánh cắp tài sản của người dùng. Chúng tôi khuyên bạn nên quản lý cẩn thận các khóa riêng tư của tài khoản quản trị viên proxy để tránh mọi nguy cơ bị hack tiềm ẩn. Ví đa chữ ký có thể được sử dụng để ngăn chặn các lỗi quản lý khóa đơn điểm.
(3) Sử dụng tài khoản riêng để quản lý proxy minh bạch
Quản lý proxy và quản trị logic phải là các địa chỉ riêng biệt để tránh mất tương tác với việc triển khai logic. Nếu quản lý proxy và quản trị logic tham chiếu đến cùng một địa chỉ, thì không có cuộc gọi nào được chuyển tiếp để thực hiện các chức năng đặc quyền, do đó cấm thay đổi các chức năng quản trị.
Liên quan đến lưu trữ hợp đồng proxy
(1) Hãy cẩn thận khi khai báo các biến trạng thái trong hợp đồng ủy quyền
Như đã giải thích trong vụ hack Audius, các hợp đồng ủy quyền phải cẩn thận khi khai báo các biến trạng thái của chính chúng. Các biến trạng thái được khai báo theo cách thông thường trong các hợp đồng proxy có thể gây xung đột dữ liệu khi đọc và ghi dữ liệu. Nếu hợp đồng proxy yêu cầu một biến trạng thái, hãy lưu giá trị vào một vị trí lưu trữ như EIP1967 để tránh xung đột khi thực thi mã hợp đồng logic.
(2) Duy trì thứ tự khai báo biến và loại hợp đồng logic
Mỗi phiên bản của hợp đồng logic phải duy trì cùng một thứ tự và loại biến trạng thái, và các biến trạng thái mới cần được thêm vào cuối các biến hiện có. Mặt khác, các cuộc gọi ủy quyền có thể khiến các hợp đồng proxy đọc hoặc ghi đè lên các giá trị được lưu trữ không chính xác và dữ liệu cũ có thể được liên kết với các biến mới được khai báo, điều này có thể gây ra sự cố nghiêm trọng cho ứng dụng.
(3) Bao gồm các khoảng trống lưu trữ trong hợp đồng cơ sở
Hợp đồng logic cần bao gồm các khoảng trống lưu trữ trong mã hợp đồng để dự đoán các biến trạng thái mới khi triển khai triển khai logic mới. Sau khi thêm một biến trạng thái mới, kích thước của khoảng cách cần được cập nhật một cách thích hợp.
(4) Không đặt giá trị biến trạng thái trong quá trình khởi tạo hoặc khai báo
Việc gán một biến trạng thái trong khi khai báo hoặc trong hàm tạo chỉ ảnh hưởng đến giá trị trong hợp đồng logic, không ảnh hưởng đến hợp đồng proxy. Các tham số không thể thay đổi nên được gán bằng cách sử dụng hàm initize().
Thừa kế hợp đồng
(1) Các hợp đồng có thể nâng cấp chỉ có thể kế thừa từ các hợp đồng có thể nâng cấp khác
Hợp đồng có thể nâng cấp có cấu trúc khác với hợp đồng không thể nâng cấp. Ví dụ, hàm tạo không tương thích với việc thay đổi trạng thái của tác nhân, nó sử dụng hàm khởi tạo () để đặt các biến trạng thái.
Bất kỳ hợp đồng nào kế thừa từ một hợp đồng khác cần sử dụng hàm khởi tạo () của hợp đồng được kế thừa để gán các biến tương ứng. Khi sử dụng thư viện OpenZeppelin hoặc viết mã của riêng bạn, hãy đảm bảo rằng các hợp đồng có thể nâng cấp chỉ có thể kế thừa các hợp đồng có thể nâng cấp khác.
(2) Không khởi tạo hợp đồng mới trong hợp đồng logic
Các hợp đồng được tạo và khởi tạo thông qua Solidity sẽ không thể nâng cấp được. Các hợp đồng nên được triển khai riêng lẻ và chuyển địa chỉ của chúng dưới dạng tham số cho hợp đồng logic có thể nâng cấp để đạt được trạng thái có thể nâng cấp.
(3) Rủi ro khởi tạo hợp đồng gốc
Khi khởi tạo hợp đồng Gốc, hàm __{ContractName}_init sẽ khởi tạo hợp đồng Gốc của nó. Nhiều cuộc gọi đến __{ContractName}_init có thể dẫn đến lần khởi tạo thứ hai của hợp đồng Gốc. Lưu ý rằng __{ContractName}_init_unchained() sẽ chỉ khởi tạo các tham số của {ContractName} và sẽ không gọi trình khởi tạo hợp đồng Mẹ của nó.
Tuy nhiên, đây không phải là phương pháp được khuyến nghị vì tất cả các hợp đồng Chính cần phải được khởi tạo và việc không khởi tạo các hợp đồng được yêu cầu sẽ gây ra các vấn đề thực thi trong tương lai.
Thực hiện hợp đồng logic
Tránh tự hủy() hoặc selegatecall()/call() đối với các hợp đồng không đáng tin cậy
Nếu có chức năng tự hủy () hoặc ủy quyền () trong hợp đồng, kẻ tấn công có thể sử dụng các chức năng này để phá vỡ triển khai logic hoặc thực thi logic tùy chỉnh. Các nhà phát triển nên xác thực đầu vào của người dùng và không cho phép các hợp đồng thực hiện các cuộc gọi/cuộc gọi ủy quyền đến các hợp đồng không đáng tin cậy. Ngoài ra, không nên sử dụng delegatecall() trong các hợp đồng logic vì sẽ rất cồng kềnh khi quản lý bố cục lưu trữ trong chuỗi ủy quyền của nhiều hợp đồng.
** Viết ở cuối **
Các hợp đồng ủy quyền bỏ qua bản chất bất biến của các chuỗi khối bằng cách cho phép các giao thức cập nhật logic mã của chúng sau khi triển khai. Tuy nhiên, việc phát triển các hợp đồng ủy quyền vẫn cần phải rất thận trọng và việc triển khai không đúng cách có thể gây ra các vấn đề về logic và bảo mật của dự án.
Nhìn chung, phương pháp hay nhất là sử dụng các giải pháp có thẩm quyền và đã được thử nghiệm rộng rãi, vì mỗi chế độ Proxy trong suốt, UUPS và Beacon đều có các cơ chế nâng cấp đã được chứng minh cho các trường hợp sử dụng tương ứng. Ngoài ra, các vai trò đặc quyền dành cho tác nhân leo thang cũng phải được quản lý an toàn để ngăn kẻ tấn công thay đổi logic của tác nhân.
Hợp đồng triển khai logic cũng nên cẩn thận không sử dụng ủy nhiệm (), điều này có thể ngăn chặn kẻ tấn công thực thi một số mã độc hại, chẳng hạn như tự hủy ().
Mặc dù việc tuân theo các phương pháp hay nhất đảm bảo triển khai hợp đồng proxy ổn định đồng thời duy trì tính linh hoạt có thể nâng cấp, nhưng tất cả mã đều dễ gặp các vấn đề logic hoặc bảo mật mới có thể gây nguy hiểm cho dự án. Do đó, tất cả mã được kiểm tra tốt nhất bởi một nhóm chuyên gia bảo mật có kinh nghiệm kiểm tra và bảo mật các giao thức hợp đồng proxy.
Xem bản gốc
Nội dung chỉ mang tính chất tham khảo, không phải là lời chào mời hay đề nghị. Không cung cấp tư vấn về đầu tư, thuế hoặc pháp lý. Xem Tuyên bố miễn trừ trách nhiệm để biết thêm thông tin về rủi ro.
Phá vỡ tính bất biến của blockchain: Làm thế nào mô hình proxy có thể nâng cấp hợp đồng thông minh
Mẫu proxy cho phép các hợp đồng thông minh nâng cấp logic của chúng trong khi vẫn duy trì các địa chỉ và giá trị trạng thái trên chuỗi của chúng. Cuộc gọi đến hợp đồng proxy sẽ thực thi mã từ hợp đồng logic thông qua delegateCall để sửa đổi trạng thái của hợp đồng proxy.
Bài viết này sẽ cung cấp tổng quan về các loại hợp đồng ủy quyền, các sự cố bảo mật liên quan và đề xuất cũng như các phương pháp hay nhất để sử dụng hợp đồng ủy quyền.
Giới thiệu về Hợp đồng có thể nâng cấp và Chế độ ủy quyền
Chúng ta đều biết tính năng "không thể giả mạo" của chuỗi khối và mã hợp đồng thông minh không thể sửa đổi sau khi được triển khai trên chuỗi khối.
Vì vậy, khi các nhà phát triển muốn cập nhật mã hợp đồng để nâng cấp logic, sửa lỗi hoặc cập nhật bảo mật, họ phải triển khai một hợp đồng mới và một địa chỉ hợp đồng mới sẽ được tạo.
Để giải quyết vấn đề này, bạn có thể sử dụng chế độ proxy.
Chế độ proxy nhận ra khả năng nâng cấp của hợp đồng mà không thay đổi địa chỉ triển khai của hợp đồng, hiện là chế độ nâng cấp hợp đồng phổ biến nhất.
Chế độ proxy là một hệ thống hợp đồng có thể nâng cấp, bao gồm hợp đồng proxy và hợp đồng triển khai logic.
Hợp đồng proxy xử lý tương tác người dùng và lưu trữ dữ liệu và trạng thái hợp đồng. Cuộc gọi của người dùng tới hợp đồng proxy sẽ thực thi mã từ hợp đồng logic thông qua delegatecall(), do đó thay đổi trạng thái của hợp đồng proxy. Việc nâng cấp được thực hiện bằng cách cập nhật địa chỉ hợp đồng logic được ghi trong khe lưu trữ được xác định trước của hợp đồng proxy.
Ba chế độ proxy thông thường hơn là proxy trong suốt, proxy UUPS và proxy Beacon.
Proxy minh bạch
Trong chế độ proxy minh bạch, chức năng nâng cấp được thực hiện trong hợp đồng proxy. Vai trò quản trị viên của hợp đồng proxy được trao quyền trực tiếp vận hành hợp đồng proxy để cập nhật địa chỉ triển khai logic tương ứng với proxy. Người gọi không có đặc quyền quản trị viên sẽ ủy quyền cuộc gọi của họ cho hợp đồng thực hiện.
Lưu ý: Quản trị viên hợp đồng proxy không thể đóng vai trò chính trong việc triển khai hợp đồng theo logic, thậm chí không thể là người dùng thông thường vì quản trị viên proxy không thể tương tác với hợp đồng triển khai.
Ủy quyền UUPS
Trong chế độ UUPS (Tiêu chuẩn proxy có thể nâng cấp toàn cầu), chức năng nâng cấp hợp đồng được triển khai trong hợp đồng logic. Do cơ chế nâng cấp được lưu trữ trong hợp đồng logic nên phiên bản nâng cấp có thể xóa logic liên quan đến nâng cấp để cấm nâng cấp trong tương lai. Trong chế độ này, tất cả các cuộc gọi đến hợp đồng proxy được chuyển tiếp đến hợp đồng thực hiện logic.
Proxy báo hiệu
Chế độ proxy Beacon cho phép nhiều hợp đồng proxy chia sẻ cùng một triển khai logic bằng cách tham chiếu hợp đồng Beacon. Hợp đồng Beacon cung cấp địa chỉ của hợp đồng triển khai logic cho hợp đồng proxy được gọi. Khi nâng cấp lên địa chỉ triển khai logic mới, chỉ cần cập nhật địa chỉ được ghi trong hợp đồng Beacon.
Sự cố lạm dụng proxy và bảo mật
Các nhà phát triển có thể sử dụng các hợp đồng chế độ proxy để triển khai các hệ thống hợp đồng có thể nâng cấp. Tuy nhiên, chế độ proxy cũng có ngưỡng hoạt động nhất định, nếu sử dụng không đúng cách có thể gây ra các vấn đề bảo mật nghiêm trọng cho dự án. Các phần sau giới thiệu các sự cố liên quan đến lạm dụng proxy và rủi ro tập trung mà proxy gây ra.
Tiết lộ khóa được quản lý proxy
Quản trị viên proxy kiểm soát cơ chế nâng cấp của chế độ proxy minh bạch, nếu khóa riêng của quản trị viên bị rò rỉ, kẻ tấn công có thể nâng cấp hợp đồng logic và thực thi logic độc hại của riêng chúng trên trạng thái proxy.
Vào ngày 5 tháng 3 năm 2021, PAID Network đã phải hứng chịu một cuộc tấn công "đúc tiền" do quản lý khóa cá nhân kém. Mạng TRẢ TIỀN đã bị khai thác bởi kẻ tấn công đã đánh cắp khóa riêng của quản trị viên proxy và kích hoạt cơ chế nâng cấp để thay đổi hợp đồng logic.
Sau khi nâng cấp, kẻ tấn công có thể phá hủy TRẢ TIỀN của người dùng và đúc một lô TRẢ TIỀN cho chính mình, có thể bán sau này. Bản thân mã này không có lỗ hổng bảo mật, nhưng kẻ tấn công đã lấy được khóa riêng để nâng cấp hợp đồng từ quản trị viên.
** Triển khai proxy UUPS chưa được khởi tạo **
Đối với chế độ proxy UUPS, trong quá trình khởi tạo hợp đồng proxy, các tham số ban đầu được người gọi chuyển đến hợp đồng proxy, sau đó hợp đồng proxy gọi hàm initialize() trong hợp đồng logic để đạt được quá trình khởi tạo.
Hàm khởi tạo () thường được bảo vệ bằng công cụ sửa đổi "bộ khởi tạo" để hạn chế hàm chỉ được gọi một lần. Sau khi gọi hàm initialize(), từ quan điểm của hợp đồng proxy, hợp đồng logic được khởi tạo.
Tuy nhiên, từ quan điểm của hợp đồng logic, hợp đồng logic không được khởi tạo vì phương thức khởi tạo() không được gọi trực tiếp trong hợp đồng logic. Cho rằng bản thân hợp đồng logic không được khởi tạo, bất kỳ ai cũng có thể gọi hàm initialize() để khởi tạo nó, đặt biến trạng thái thành một giá trị độc hại và có khả năng chiếm đoạt hợp đồng logic.
Tác động của một hợp đồng logic bị chiếm dụng phụ thuộc vào mã hợp đồng trong hệ thống. Trong trường hợp xấu nhất, kẻ tấn công có thể nâng cấp hợp đồng logic trong chế độ proxy UUPS thành hợp đồng độc hại và thực hiện lệnh gọi chức năng "tự hủy", điều này có thể khiến toàn bộ hợp đồng proxy trở nên vô dụng và tài sản trong hợp đồng sẽ bị phá hủy vĩnh viễn bị mất.
trường hợp
① Đóng băng nhiều chữ ký chẵn lẻ: Hợp đồng logic không được khởi tạo. Kẻ tấn công kích hoạt quá trình khởi tạo nhiều ví và khóa ether trong hợp đồng bằng cách gọi hàm tự hủy().
② Harvest Finance, Teller, KeeperDAO và Rivermen đều sử dụng các hợp đồng logic chưa được khởi tạo, điều này sẽ cho phép kẻ tấn công tùy ý thiết lập các tham số khởi tạo của hợp đồng và thực hiện tự hủy () trong khi ủy nhiệm () để hủy hợp đồng ủy quyền.
Xung đột bộ nhớ
Trong một hệ thống hợp đồng có thể nâng cấp, hợp đồng ủy quyền không khai báo các biến trạng thái mà sử dụng các vị trí lưu trữ giả ngẫu nhiên để lưu trữ dữ liệu quan trọng.
Hợp đồng proxy lưu trữ các giá trị của các biến trạng thái hợp đồng logic liên quan đến nơi chúng được khai báo. Nếu hợp đồng proxy khai báo các biến trạng thái của chính nó và cả hợp đồng proxy và hợp đồng logic đều cố gắng sử dụng cùng một vị trí lưu trữ, xung đột lưu trữ sẽ xảy ra.
Hợp đồng proxy do thư viện OpenZeppelin cung cấp không khai báo các biến trạng thái trong hợp đồng mà dựa trên tiêu chuẩn EIP 1967, lưu giá trị cần lưu trữ (chẳng hạn như địa chỉ quản lý) trong một khe lưu trữ cụ thể để tránh xung đột.
trường hợp
Vào ngày 23 tháng 7 năm 2022, theo giờ Bắc Kinh, nền tảng âm nhạc phi tập trung Audius đã bị tấn công. Sự cố xảy ra do việc đưa ra logic mới trong hợp đồng ủy quyền, dẫn đến xung đột lưu trữ.
Hợp đồng proxy khai báo biến trạng thái địa chỉ proxyAdmin và giá trị của nó sẽ được đọc không chính xác khi mã hợp đồng logic được thực thi.
Giá trị của proxyAdmin do bên dự án tùy chỉnh bị coi nhầm thành giá trị của khởi tạo và được khởi tạo, do đó công cụ sửa đổi trình khởi tạo trả về kết quả sai, điều này cho phép kẻ tấn công gọi lại hàm initialize() và cấp cho mình quyền quản lý hợp đồng. Sau đó, những kẻ tấn công đã thay đổi các tham số biểu quyết và thông qua đề xuất độc hại của chúng để đánh cắp tài sản của Audius.
Gọi delegatecall() trong hợp đồng logic hoặc hợp đồng không đáng tin cậy
Giả sử delegatecall() tồn tại trong hợp đồng logic và hợp đồng không xác thực đúng mục tiêu của cuộc gọi. Trong trường hợp này, kẻ tấn công có thể khai thác chức năng này để thực hiện các cuộc gọi đến các hợp đồng độc hại mà chúng kiểm soát, phá hoại việc triển khai logic hoặc để thực thi logic tùy chỉnh.
Tương tự, nếu có một hàm address.call() không hạn chế trong hợp đồng logic, thì một khi kẻ tấn công cung cấp các trường dữ liệu và địa chỉ một cách ác ý, nó có thể được sử dụng làm hợp đồng ủy quyền.
trường hợp
Các cuộc tấn công Pickle Finance, Furucombo và dYdX.
Trong những sự cố này, hợp đồng dễ bị tấn công đã được phê duyệt bởi mã thông báo của người dùng và có một lệnh gọi()/delegatecall() trong hợp đồng do người dùng cung cấp để gọi địa chỉ và dữ liệu của hợp đồng, kẻ tấn công sẽ có thể gọi hợp đồng chức năng transferFrom() để rút số dư của người dùng. Trong sự cố dYdX, dYdX đã thực hiện cuộc tấn công mũ trắng của riêng họ để bảo vệ tiền.
Thực hành tốt nhất
nói chung là
(1) Chỉ sử dụng chế độ proxy khi cần thiết
Không phải mọi hợp đồng đều cần phải được nâng cấp. Như đã trình bày ở trên, có nhiều rủi ro liên quan đến việc sử dụng mẫu proxy. Thuộc tính "có thể nâng cấp" cũng gây ra các vấn đề về niềm tin, vì quản trị viên proxy có thể nâng cấp hợp đồng mà không cần sự đồng ý của cộng đồng. Chúng tôi khuyên bạn chỉ nên tích hợp mẫu proxy vào các dự án khi cần thiết.
(2) Không sửa đổi thư viện proxy
Thư viện hợp đồng proxy rất phức tạp, đặc biệt là phần liên quan đến cơ chế nâng cấp và quản lý lưu trữ. Bất kỳ lỗi nào trong quá trình sửa đổi sẽ ảnh hưởng đến hoạt động của hợp đồng proxy và logic. Một số lượng lớn các lỗi nghiêm trọng liên quan đến tác nhân mà chúng tôi đã tìm thấy trong quá trình kiểm tra của mình là do sửa đổi thư viện tác nhân không chính xác. Sự cố Audius là một ví dụ điển hình về hậu quả của việc sửa đổi hợp đồng đại lý không đúng cách.
Các điểm chính của quản lý và vận hành hợp đồng đại lý
(1) Khởi tạo hợp đồng logic
Kẻ tấn công có thể chiếm lấy một hợp đồng logic chưa được khởi tạo và có khả năng xâm phạm hệ thống hợp đồng ủy quyền. Vì vậy, vui lòng khởi tạo hợp đồng logic sau khi triển khai hoặc sử dụng _disableInitializers() trong hàm tạo của hợp đồng logic để tự động tắt khởi tạo.
(2) Đảm bảo tính bảo mật của tài khoản quản lý đại lý
Hệ thống hợp đồng có thể nâng cấp thường yêu cầu vai trò đặc quyền là "quản trị viên ủy quyền" để quản lý việc nâng cấp hợp đồng. Nếu khóa quản lý bị rò rỉ, kẻ tấn công có thể tự do nâng cấp hợp đồng thành hợp đồng độc hại, có thể đánh cắp tài sản của người dùng. Chúng tôi khuyên bạn nên quản lý cẩn thận các khóa riêng tư của tài khoản quản trị viên proxy để tránh mọi nguy cơ bị hack tiềm ẩn. Ví đa chữ ký có thể được sử dụng để ngăn chặn các lỗi quản lý khóa đơn điểm.
(3) Sử dụng tài khoản riêng để quản lý proxy minh bạch
Quản lý proxy và quản trị logic phải là các địa chỉ riêng biệt để tránh mất tương tác với việc triển khai logic. Nếu quản lý proxy và quản trị logic tham chiếu đến cùng một địa chỉ, thì không có cuộc gọi nào được chuyển tiếp để thực hiện các chức năng đặc quyền, do đó cấm thay đổi các chức năng quản trị.
Liên quan đến lưu trữ hợp đồng proxy
(1) Hãy cẩn thận khi khai báo các biến trạng thái trong hợp đồng ủy quyền
Như đã giải thích trong vụ hack Audius, các hợp đồng ủy quyền phải cẩn thận khi khai báo các biến trạng thái của chính chúng. Các biến trạng thái được khai báo theo cách thông thường trong các hợp đồng proxy có thể gây xung đột dữ liệu khi đọc và ghi dữ liệu. Nếu hợp đồng proxy yêu cầu một biến trạng thái, hãy lưu giá trị vào một vị trí lưu trữ như EIP1967 để tránh xung đột khi thực thi mã hợp đồng logic.
(2) Duy trì thứ tự khai báo biến và loại hợp đồng logic
Mỗi phiên bản của hợp đồng logic phải duy trì cùng một thứ tự và loại biến trạng thái, và các biến trạng thái mới cần được thêm vào cuối các biến hiện có. Mặt khác, các cuộc gọi ủy quyền có thể khiến các hợp đồng proxy đọc hoặc ghi đè lên các giá trị được lưu trữ không chính xác và dữ liệu cũ có thể được liên kết với các biến mới được khai báo, điều này có thể gây ra sự cố nghiêm trọng cho ứng dụng.
(3) Bao gồm các khoảng trống lưu trữ trong hợp đồng cơ sở
Hợp đồng logic cần bao gồm các khoảng trống lưu trữ trong mã hợp đồng để dự đoán các biến trạng thái mới khi triển khai triển khai logic mới. Sau khi thêm một biến trạng thái mới, kích thước của khoảng cách cần được cập nhật một cách thích hợp.
(4) Không đặt giá trị biến trạng thái trong quá trình khởi tạo hoặc khai báo
Việc gán một biến trạng thái trong khi khai báo hoặc trong hàm tạo chỉ ảnh hưởng đến giá trị trong hợp đồng logic, không ảnh hưởng đến hợp đồng proxy. Các tham số không thể thay đổi nên được gán bằng cách sử dụng hàm initize().
Thừa kế hợp đồng
(1) Các hợp đồng có thể nâng cấp chỉ có thể kế thừa từ các hợp đồng có thể nâng cấp khác
Hợp đồng có thể nâng cấp có cấu trúc khác với hợp đồng không thể nâng cấp. Ví dụ, hàm tạo không tương thích với việc thay đổi trạng thái của tác nhân, nó sử dụng hàm khởi tạo () để đặt các biến trạng thái.
Bất kỳ hợp đồng nào kế thừa từ một hợp đồng khác cần sử dụng hàm khởi tạo () của hợp đồng được kế thừa để gán các biến tương ứng. Khi sử dụng thư viện OpenZeppelin hoặc viết mã của riêng bạn, hãy đảm bảo rằng các hợp đồng có thể nâng cấp chỉ có thể kế thừa các hợp đồng có thể nâng cấp khác.
(2) Không khởi tạo hợp đồng mới trong hợp đồng logic
Các hợp đồng được tạo và khởi tạo thông qua Solidity sẽ không thể nâng cấp được. Các hợp đồng nên được triển khai riêng lẻ và chuyển địa chỉ của chúng dưới dạng tham số cho hợp đồng logic có thể nâng cấp để đạt được trạng thái có thể nâng cấp.
(3) Rủi ro khởi tạo hợp đồng gốc
Khi khởi tạo hợp đồng Gốc, hàm __{ContractName}_init sẽ khởi tạo hợp đồng Gốc của nó. Nhiều cuộc gọi đến __{ContractName}_init có thể dẫn đến lần khởi tạo thứ hai của hợp đồng Gốc. Lưu ý rằng __{ContractName}_init_unchained() sẽ chỉ khởi tạo các tham số của {ContractName} và sẽ không gọi trình khởi tạo hợp đồng Mẹ của nó.
Tuy nhiên, đây không phải là phương pháp được khuyến nghị vì tất cả các hợp đồng Chính cần phải được khởi tạo và việc không khởi tạo các hợp đồng được yêu cầu sẽ gây ra các vấn đề thực thi trong tương lai.
Thực hiện hợp đồng logic
Tránh tự hủy() hoặc selegatecall()/call() đối với các hợp đồng không đáng tin cậy
Nếu có chức năng tự hủy () hoặc ủy quyền () trong hợp đồng, kẻ tấn công có thể sử dụng các chức năng này để phá vỡ triển khai logic hoặc thực thi logic tùy chỉnh. Các nhà phát triển nên xác thực đầu vào của người dùng và không cho phép các hợp đồng thực hiện các cuộc gọi/cuộc gọi ủy quyền đến các hợp đồng không đáng tin cậy. Ngoài ra, không nên sử dụng delegatecall() trong các hợp đồng logic vì sẽ rất cồng kềnh khi quản lý bố cục lưu trữ trong chuỗi ủy quyền của nhiều hợp đồng.
** Viết ở cuối **
Các hợp đồng ủy quyền bỏ qua bản chất bất biến của các chuỗi khối bằng cách cho phép các giao thức cập nhật logic mã của chúng sau khi triển khai. Tuy nhiên, việc phát triển các hợp đồng ủy quyền vẫn cần phải rất thận trọng và việc triển khai không đúng cách có thể gây ra các vấn đề về logic và bảo mật của dự án.
Nhìn chung, phương pháp hay nhất là sử dụng các giải pháp có thẩm quyền và đã được thử nghiệm rộng rãi, vì mỗi chế độ Proxy trong suốt, UUPS và Beacon đều có các cơ chế nâng cấp đã được chứng minh cho các trường hợp sử dụng tương ứng. Ngoài ra, các vai trò đặc quyền dành cho tác nhân leo thang cũng phải được quản lý an toàn để ngăn kẻ tấn công thay đổi logic của tác nhân.
Hợp đồng triển khai logic cũng nên cẩn thận không sử dụng ủy nhiệm (), điều này có thể ngăn chặn kẻ tấn công thực thi một số mã độc hại, chẳng hạn như tự hủy ().
Mặc dù việc tuân theo các phương pháp hay nhất đảm bảo triển khai hợp đồng proxy ổn định đồng thời duy trì tính linh hoạt có thể nâng cấp, nhưng tất cả mã đều dễ gặp các vấn đề logic hoặc bảo mật mới có thể gây nguy hiểm cho dự án. Do đó, tất cả mã được kiểm tra tốt nhất bởi một nhóm chuyên gia bảo mật có kinh nghiệm kiểm tra và bảo mật các giao thức hợp đồng proxy.