Introduction
Bài báo này tập trung vào việc xác định các design pattern thường được sử dụng để giảm thiểu các mối đe dọa bảo mật.
Background
Có thể xem Ethereum như là một transaction-based state machine bởi vì state của nó được cập nhật sau mỗi transaction.
Development Aspects
Smart contract nên được sử dụng cho các ứng dụng cần tính phi tập trung, xác thực và thực thi công khai.
Do Ethereum và Solidity phát triển rất nhanh nên developer luôn phải đối mặt với các sự chuyển đổi về tính năng và bảo mật.
Security Patterns
Sau đây là một số các security pattern được liệt kê ở trong bài báo:
Checks-Effects-Interaction
Info
Vấn đề: khi contract A gọi đến một contract B, nó sẽ bàn giao quyền kiểm soát cho contract B. Khi đó, contract B có thể gọi lại contract A để thay đổi state hoặc thay đổi luồng thực thi bằng các đoạn code độc hại.
Giải pháp: chỉ gọi đến contract bên ngoài ở bước cuối cùng trong hàm.
Pattern này gồm ba bước theo thứ tự sau:
- Kiểm tra các điều kiện
- Thực thi các sự thay đổi
- Gọi đến contract khác
Ví dụ:
Lý do mà lời gọi đến contract khác nên được đặt ở cuối function là để đảm bảo contract khác không thể thực thi những hành vi nguy hiểm. Cụ thể, trong reentrancy attack, contract được gọi gọi lại contract hiện tại trước khi lời gọi thực thi function trước đó hoàn thành. Việc gọi lại contract hiện tại chính là một trong số những hành vi nguy hiểm đó. Hành vi này có thể dẫn đến việc thay đổi state variable hoặc thực hiện một số hành động (chẳng hạn như gửi tiền) được thực hiện nhiều lần.
Trong ví dụ bên dưới, việc gọi đến contract bên ngoài (lệnh msg.sender.call.value(amount)()
) được thực hiện trước khi gán số dư của địa chỉ msg.sender
là 0. Kẻ xấu có thể viết một contract để liên tục gọi đến hàm withdrawBalance
trước khi số dư được gán bằng 0 nhằm rút hết tiền từ contract. Đây chính là reentrancy attack.
Emergency Stop (Circuit Breaker)
Info
Vấn đề: contract sau khi deploy thì được thực thi một cách tự động ở trên Ethereum network và không có cách nào để ngừng quá trình thực thi khi có bug hoặc vấn đề bảo mật xảy ra.
Giải pháp: thêm vào cơ chế cho phép một tổ chức được xác thực vô hiệu hóa một số hàm nhạy cảm.
Một kịch bản được khuyến khích sử dụng là: khi có bug xảy ra, tất cả các hàm quan trọng đều bị ngừng hoạt động trừ hàm rút tiền.
Ví dụ:
Trong ví dụ trên, state variale contractStopped
cho biết contract có ngừng hoạt động hay chưa. Có hai function modifier là haltInEmergency
và enableInEmergency
phụ thuộc và state variable này. Cụ thể, haltInEmergency
sẽ cho phép hàm deposit
được thực thi bất cứ khi nào contract đang hiệu lực và enableInEmergency
sẽ cho phép hàm withdraw
được thực thi bất cứ khi nào contract bị vô hiệu hóa.
Việc toggle giá trị của biến contractStopped
chỉ có thể được thực hiện bởi chủ sở hữu contract. Điều này được thể hiện thông qua modifier onlyOwner
.
Speed Bump
Info
Vấn đề: việc một lượng lớn các tổ chức thực thi các tác vụ nhạy cảm tại cùng một thời điểm có thể làm smart contract bị chậm (tương tự với DDoS).
Giải pháp: kéo dài thời gian thực thi các tác vụ nhạy cảm.
Việc kéo dài thời gian giúp chúng ta có nhiều thời gian hơn để đối phó với cuộc tấn công. Một ví dụ trong đời sống là “bank run”. Đây là một hiện tượng xảy ra khi có một lượng lớn các người dùng rút tiền khỏi ngân hàng trong cùng một thời điểm bởi vì sự quan ngại về khả năng thanh toán của ngân hàng. Ngân hàng thường đối phó bằng cách trì hoãn, dừng lại hoặc giới hạn lượng tiền rút ra.
Ví dụ:
Phân tích ví dụ trên:
- Struct
Withdrawal
được dùng để thể hiện một yêu cầu rút tiền. - Có hai mapping là
balances
(thể hiện cho số dư của các người dùng) vàwithdrawals
(thể hiện cho các yêu cầu rút tiền của các người dùng). - Có một hằng số là
WAIT_PERIOD
với giá trị là 7 ngày. Đây chính là khoảng thời gian delay giữa hai lần rút tiền. - Hàm
deposit
cho phép gửi tiền vào contract khi người dùng đang không có yêu cầu rút tiền nào. - Hàm
requestWithdrawal
cho phép tạo yêu cầu rút tiền với điều kiện là số dư của người dùng phải lớn hơn 0. Nếu điều kiện được thỏa mãn, số dư của người dùng sẽ được gán bằng 0 và sẽ có một yêu cầu rút tiền tương ứng với tài khoản của người dùng được tạo ra. - Hàm
withdraw
cho phép rút tiền nếu người dùng có yêu cầu rút tiền và đã đến thời điểm rút tiền (sau thời điểm yêu cầu rút tiền 7 ngày). Nếu điều kiện được thỏa mãn, lượng tiền trong yêu cầu rút tiền sẽ được gán bằng 0 và người dùng sẽ nhận được lượng tiền mà họ mong muốn rút.
Rate Limit
Info
Vấn đề: việc thực thi một hàm nào đó quá nhiều lần có thể làm giảm hiệu năng của smart contract.
Giải pháp: quy định tần suất mà một hàm có thể được thực thi.
Ví dụ:
Trong ví dụ trên, modifier enabledEvery
giúp giới hạn lại việc thực thi của một hàm bất kỳ với tần suất là 1 lần thực thi mỗi 1 phút.
Mutex
Info
Vấn đề: các reentrancy attack có thể thao túng state của contract và chiếm quyền kiểm soát của luồng thực thi.
Giải pháp: sử dụng mutex để ngăn chặn việc re-entering của contract bên ngoài.
Mutex là một cơ chế đồng bộ nhằm giới hạn lại các truy cập đồng thời vào tài nguyên.
Ví dụ áp dụng mutex:
Modifier noReentrancy
ở trong ví dụ trên giúp đảm bảo một hàm bất kỳ sẽ không được gọi lại khi nó chưa được thực thi xong bằng cách sử dụng cờ locked
.
Balance Limit
Info
Vấn đề: contract sẽ luôn có rủi ro bị tấn công thông qua một bug hoặc một vấn đề nào đó của platform. Điều này có thể gây ra thất thoát tiền được lưu trữ trong contract.
Giải pháp: giới hạn lại lượng tiền tối đa mà một contract nắm giữ.
Hiện thực pattern này bằng cách từ chối các lệnh chuyển tiền vào contract khi lượng tiền mà contract đang nắm giữ vượt quá một hạn ngạch nào đó. Cách tiếp cận này có thể không ngăn chặn được các lệnh chuyển tiền ép buộc chẳng hạn như lời gọi hàm selfdestruct(address)
hoặc nhận tiền reward tự việc mining.
Ví dụ:
Trong ví dụ trên, modifier limitedPayable
sẽ được áp dụng cho các hàm có modifier là payable
để đảm bảo rằng số dư của contract không vượt quá giá trị của limit
.