Skip to content

Commit cfe8d66

Browse files
committed
Add study notes for 2025-08-15
1 parent de4b9ed commit cfe8d66

1 file changed

Lines changed: 79 additions & 0 deletions

File tree

KKisacat.md

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,85 @@ timezone: UTC+8
1515
## Notes
1616

1717
<!-- Content_START -->
18+
# 2025-08-15
19+
20+
## 安全實踐 -- 常見攻擊手段
21+
22+
### 重入攻擊(Reentrancy Attack)
23+
合約呼叫外部合約或地址(例如 call 發送 ETH)時,對方在交易完成前又回頭呼叫原本合約的同一個函數,導致邏輯重複執行,造成意料外的狀況(通常是多次提款)。
24+
25+
26+
典型範例(易受攻擊)
27+
```solidity=
28+
function withdraw(uint amount) public {
29+
require(balances[msg.sender] >= amount, "Not enough balance");
30+
31+
// 1. 轉錢給使用者
32+
(bool success, ) = msg.sender.call{value: amount}("");
33+
require(success, "Transfer failed");
34+
35+
// 2. 更新餘額
36+
balances[msg.sender] -= amount;
37+
}
38+
```
39+
攻擊方式:
40+
41+
1. 攻擊者先存入一些錢。
42+
2. 攻擊者寫一個惡意合約,在 receive() 或 fallback() 函數中再次呼叫 withdraw()。
43+
3. 當 withdraw() 在第 1 步 call 發送 ETH 時,攻擊者的合約立刻觸發 fallback,再次執行 withdraw()。
44+
4. 因為餘額尚未在第 2 步更新(狀態改變太晚),所以檢查依然通過,可以重複領錢。
45+
5. 直到合約的錢被領光。
46+
47+
#### 如何防範
48+
49+
##### 1. CEI Pattern(Checks-Effects-Interactions 模式)
50+
1. 檢查條件(Check)
51+
2. 更新狀態(Effects)
52+
3. 最後與外部互動(Interactions)
53+
```solidity=
54+
function withdraw(uint amount) public {
55+
require(balances[msg.sender] >= amount, "Not enough balance");
56+
57+
// 先更新餘額(防止重入)
58+
balances[msg.sender] -= amount;
59+
60+
// 再轉錢
61+
(bool success, ) = msg.sender.call{value: amount}("");
62+
require(success, "Transfer failed");
63+
}
64+
```
65+
66+
##### 2. 重入鎖(Reentrancy Guard)
67+
透過一個 狀態變數 來記錄「現在這個函數是不是正在執行中」。
68+
```solidity=
69+
contract ReentrancyGuard {
70+
bool private locked;
71+
72+
modifier noReentrant() {
73+
require(!locked, "Reentrant call");
74+
locked = true;
75+
_;
76+
locked = false;
77+
}
78+
}
79+
// 也可以import OpenZeppelin 提供的 ReentrancyGuard
80+
// import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
81+
82+
83+
contract SecureWithGuard is ReentrancyGuard {
84+
mapping(address => uint256) public balances;
85+
86+
function withdraw() external noReentrant {
87+
uint256 amount = balances[msg.sender];
88+
require(amount > 0, "No balance");
89+
90+
balances[msg.sender] = 0;
91+
(bool success,) = msg.sender.call{value: amount}("");
92+
require(success, "Transfer failed");
93+
}
94+
}
95+
```
96+
1897
# 2025-08-14
1998

2099
### 介面 (Interfaces) (接口)

0 commit comments

Comments
 (0)