audit-system 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +351 -0
- package/agents/AGENT_REGISTRY.md +150 -0
- package/agents/assumption-analyzer.json +7 -0
- package/agents/assumption-analyzer.md +37 -0
- package/agents/composition-attacker.json +7 -0
- package/agents/composition-attacker.md +46 -0
- package/agents/economic-attacker.json +7 -0
- package/agents/economic-attacker.md +43 -0
- package/agents/exploit-writer.json +7 -0
- package/agents/exploit-writer.md +48 -0
- package/agents/orchestrator.json +16 -0
- package/agents/orchestrator.md +46 -0
- package/agents/report-writer.json +7 -0
- package/agents/report-writer.md +52 -0
- package/agents/state-machine-hacker.json +7 -0
- package/agents/state-machine-hacker.md +43 -0
- package/agents/test-generator.json +7 -0
- package/agents/test-generator.md +49 -0
- package/cli.js +93 -0
- package/config.json +74 -0
- package/lib/detect-lang.js +109 -0
- package/lib/install.js +229 -0
- package/lib/utils.js +41 -0
- package/obsidian-vault/README.md +103 -0
- package/obsidian-vault/attack-patterns/state-inconsistency.md +90 -0
- package/obsidian-vault/exploits/_index.md +109 -0
- package/obsidian-vault/exploits/beanstalk-2022.md +334 -0
- package/obsidian-vault/exploits/nomad-2022.md +295 -0
- package/obsidian-vault/exploits/ronin-2022.md +251 -0
- package/obsidian-vault/exploits/wormhole-2022.md +284 -0
- package/obsidian-vault/failed-hypotheses/_template.md +77 -0
- package/obsidian-vault/hypotheses/_template.md +43 -0
- package/obsidian-vault/hypotheses/bridge-protocol-template.md +254 -0
- package/obsidian-vault/hypotheses/dex-protocol-template.md +185 -0
- package/obsidian-vault/hypotheses/governance-protocol-template.md +263 -0
- package/obsidian-vault/hypotheses/lending-protocol-template.md +218 -0
- package/obsidian-vault/hypotheses/staking-protocol-template.md +223 -0
- package/obsidian-vault/invariant-catalog/defi-invariants.md +307 -0
- package/obsidian-vault/invariant-catalog/solana-invariants.md +213 -0
- package/obsidian-vault/novel-patterns/pattern-mutation-framework.md +316 -0
- package/obsidian-vault/reports/_template.md +92 -0
- package/obsidian-vault/research/cross-protocol-analysis/.gitkeep +0 -0
- package/obsidian-vault/research/emerging-threats/.gitkeep +0 -0
- package/obsidian-vault/research/protocol-specific/.gitkeep +0 -0
- package/obsidian-vault/test-strategies/fuzzing.md +75 -0
- package/obsidian-vault/vulnerabilities/access-control.md +122 -0
- package/obsidian-vault/vulnerabilities/flash-loan-attack.md +66 -0
- package/obsidian-vault/vulnerabilities/oracle-manipulation.md +135 -0
- package/obsidian-vault/vulnerabilities/reentrancy.md +141 -0
- package/obsidian-vault/vulnerabilities/rust-unsafe-deserialization.md +128 -0
- package/obsidian-vault/vulnerabilities/solana-account-confusion.md +125 -0
- package/obsidian-vault/vulnerabilities/solana-close-account.md +141 -0
- package/obsidian-vault/vulnerabilities/solana-cpi-attacks.md +131 -0
- package/obsidian-vault/vulnerabilities/solana-signer-authorization.md +119 -0
- package/package.json +56 -0
- package/skills/audit-connect.md +385 -0
- package/skills/auditor.md +280 -0
- package/skills/exploit-generator.md +394 -0
- package/skills/novel-discovery.md +551 -0
- package/skills/test-generator.md +511 -0
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
# Ronin Bridge Exploit (2022)
|
|
2
|
+
|
|
3
|
+
**Date:** March 23, 2022
|
|
4
|
+
**Loss:** $625 million (173,600 ETH + 25.5M USDC)
|
|
5
|
+
**Type:** Validator Compromise / Multi-sig Attack
|
|
6
|
+
**Severity:** CRITICAL
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Summary
|
|
11
|
+
|
|
12
|
+
The Ronin Bridge was a cross-chain bridge connecting Ethereum to the Ronin sidechain (built for Axie Infinity). The attacker gained control of 5 out of 9 validator keys, allowing them to approve fraudulent withdrawals.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Technical Details
|
|
17
|
+
|
|
18
|
+
### Vulnerability Root Cause
|
|
19
|
+
|
|
20
|
+
The Ronin bridge used a **5-of-9 multi-signature scheme** for validating withdrawals. The attacker compromised:
|
|
21
|
+
|
|
22
|
+
1. **4 validator keys** from Sky Mavis (Ronin operator)
|
|
23
|
+
2. **1 validator key** from Axie DAO (governance validator)
|
|
24
|
+
|
|
25
|
+
With 5 of 9 keys, the attacker could create valid withdrawal proofs.
|
|
26
|
+
|
|
27
|
+
### Attack Vector
|
|
28
|
+
|
|
29
|
+
```solidity
|
|
30
|
+
// Simplified Ronin Bridge Logic
|
|
31
|
+
contract RoninBridge {
|
|
32
|
+
mapping(bytes32 => bool) public processedWithdrawals;
|
|
33
|
+
address[] public validators; // 9 validators
|
|
34
|
+
uint256 public constant VALIDATOR_THRESHOLD = 5;
|
|
35
|
+
|
|
36
|
+
function withdraw(
|
|
37
|
+
address recipient,
|
|
38
|
+
uint256 amount,
|
|
39
|
+
bytes memory signatures
|
|
40
|
+
) external {
|
|
41
|
+
bytes32 withdrawalHash = keccak256(
|
|
42
|
+
abi.encode(recipient, amount, nonce)
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
require(!processedWithdrawals[withdrawalHash], "Already processed");
|
|
46
|
+
|
|
47
|
+
// Verify 5 of 9 validator signatures
|
|
48
|
+
require(
|
|
49
|
+
verifySignatures(withdrawalHash, signatures, validators),
|
|
50
|
+
"Invalid signatures"
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
processedWithdrawals[withdrawalHash] = true;
|
|
54
|
+
|
|
55
|
+
// Transfer tokens to attacker
|
|
56
|
+
IERC20(token).transfer(recipient, amount);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Attack Sequence
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
1. Attacker gains control of 5 validator private keys
|
|
65
|
+
- 4 keys from Sky Mavis (compromised via social engineering/malware)
|
|
66
|
+
- 1 key from Axie DAO validator
|
|
67
|
+
|
|
68
|
+
2. Attacker creates fraudulent withdrawal request:
|
|
69
|
+
- Recipient: attacker address
|
|
70
|
+
- Amount: 173,600 ETH + 25.5M USDC
|
|
71
|
+
|
|
72
|
+
3. Attacker signs withdrawal with 5 compromised keys
|
|
73
|
+
|
|
74
|
+
4. Bridge verifies signatures (5 of 9 = valid)
|
|
75
|
+
|
|
76
|
+
5. Tokens transferred to attacker
|
|
77
|
+
|
|
78
|
+
6. Attacker launders funds through Tornado Cash
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## Why Standard Audits Missed This
|
|
84
|
+
|
|
85
|
+
| Audit Focus | What Was Missed |
|
|
86
|
+
|-------------|-----------------|
|
|
87
|
+
| Smart contract code | Off-chain key management |
|
|
88
|
+
| Signature verification | Validator selection/security |
|
|
89
|
+
| Reentrancy, overflow | Centralization of validator keys |
|
|
90
|
+
| Economic incentives | Single entity controlled 4/9 keys |
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## Key Lessons
|
|
95
|
+
|
|
96
|
+
### 1. Validator Centralization Risk
|
|
97
|
+
```
|
|
98
|
+
PROBLEM: 4 of 9 validators controlled by single entity (Sky Mavis)
|
|
99
|
+
SOLUTION: Distribute validators across independent entities/jurisdictions
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### 2. Threshold Selection
|
|
103
|
+
```
|
|
104
|
+
PROBLEM: 5/9 = 55.5% threshold too low for high-value bridge
|
|
105
|
+
SOLUTION: Higher thresholds (e.g., 7/9 = 77.8%) for large withdrawals
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### 3. Withdrawal Rate Limits
|
|
109
|
+
```
|
|
110
|
+
PROBLEM: No daily/monthly withdrawal caps
|
|
111
|
+
SOLUTION: Implement time-based limits with emergency escalation
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### 4. Multi-Sig Upgrade Mechanism
|
|
115
|
+
```
|
|
116
|
+
PROBLEM: Validator set could not be changed quickly
|
|
117
|
+
SOLUTION: Emergency multi-sig to rotate compromised validators
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## Exploit Code (Simplified PoC)
|
|
123
|
+
|
|
124
|
+
```solidity
|
|
125
|
+
// Foundry PoC for Ronin-style attack
|
|
126
|
+
contract RoninExploitTest is Test {
|
|
127
|
+
RoninBridge bridge;
|
|
128
|
+
address[9] validators;
|
|
129
|
+
address attacker = makeAddr("attacker");
|
|
130
|
+
|
|
131
|
+
function test_validatorCompromiseAttack() public {
|
|
132
|
+
// Setup: Deploy bridge with 9 validators, 5 threshold
|
|
133
|
+
bridge = new RoninBridge(validators, 5);
|
|
134
|
+
|
|
135
|
+
// Fund bridge
|
|
136
|
+
deal(address(bridge), 200_000 ether);
|
|
137
|
+
|
|
138
|
+
// Attacker controls 5 validators
|
|
139
|
+
address[5] memory compromisedValidators = [
|
|
140
|
+
validators[0], validators[1], validators[2],
|
|
141
|
+
validators[3], validators[4]
|
|
142
|
+
];
|
|
143
|
+
|
|
144
|
+
// Create withdrawal hash
|
|
145
|
+
bytes32 withdrawalHash = keccak256(
|
|
146
|
+
abi.encode(attacker, 173_600 ether, block.number)
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
// Sign with 5 compromised keys
|
|
150
|
+
bytes memory signatures = createSignatures(
|
|
151
|
+
withdrawalHash,
|
|
152
|
+
compromisedValidators
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
// Execute fraudulent withdrawal
|
|
156
|
+
uint256 balanceBefore = attacker.balance;
|
|
157
|
+
bridge.withdraw(attacker, 173_600 ether, signatures);
|
|
158
|
+
uint256 balanceAfter = attacker.balance;
|
|
159
|
+
|
|
160
|
+
// Verify attack succeeded
|
|
161
|
+
assertEq(balanceAfter - balanceBefore, 173_600 ether);
|
|
162
|
+
console.log("Attack successful: drained 173,600 ETH");
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function createSignatures(
|
|
166
|
+
bytes32 hash,
|
|
167
|
+
address[5] memory signers
|
|
168
|
+
) internal pure returns (bytes memory) {
|
|
169
|
+
// Simplified signature aggregation
|
|
170
|
+
// In reality, would use ECDSA signatures
|
|
171
|
+
return abi.encodePacked(hash, signers);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## Detection Checklist for Auditors
|
|
179
|
+
|
|
180
|
+
When auditing bridges, verify:
|
|
181
|
+
|
|
182
|
+
- [ ] Validator set is sufficiently decentralized
|
|
183
|
+
- [ ] No single entity controls threshold number of validators
|
|
184
|
+
- [ ] Withdrawal rate limits exist (daily/monthly caps)
|
|
185
|
+
- [ ] Emergency pause mechanism with multi-sig
|
|
186
|
+
- [ ] Validator rotation mechanism exists
|
|
187
|
+
- [ ] Slashing conditions for malicious validators
|
|
188
|
+
- [ ] Time delays for large withdrawals
|
|
189
|
+
- [ ] Multi-chain confirmation requirements
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
## Invariants That Should Have Been Tested
|
|
194
|
+
|
|
195
|
+
```solidity
|
|
196
|
+
// INVARIANT: No single entity should control threshold validators
|
|
197
|
+
function invariant_validatorDecentralization() public view {
|
|
198
|
+
mapping(address => uint256) entityValidators;
|
|
199
|
+
|
|
200
|
+
for (uint i = 0; i < validators.length; i++) {
|
|
201
|
+
address entity = getValidatorEntity(validators[i]);
|
|
202
|
+
entityValidators[entity]++;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
for (address entity : allEntities) {
|
|
206
|
+
assert(
|
|
207
|
+
entityValidators[entity] < THRESHOLD,
|
|
208
|
+
"Single entity controls threshold"
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// INVARIANT: Daily withdrawals should not exceed limit
|
|
214
|
+
function invariant_dailyWithdrawalLimit() public {
|
|
215
|
+
uint256 dailyTotal = getWithdrawalsToday();
|
|
216
|
+
assert(dailyTotal <= MAX_DAILY_WITHDRAWAL);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// INVARIANT: Large withdrawals require additional confirmation
|
|
220
|
+
function invariant_largeWithdrawalDelay() public {
|
|
221
|
+
if (withdrawalAmount > LARGE_WITHDRAWAL_THRESHOLD) {
|
|
222
|
+
assert(block.timestamp >= withdrawalRequestTime + DELAY);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
---
|
|
228
|
+
|
|
229
|
+
## Aftermath
|
|
230
|
+
|
|
231
|
+
| Action | Result |
|
|
232
|
+
|--------|--------|
|
|
233
|
+
| Bridge paused | March 23, 2022 |
|
|
234
|
+
| FBI investigation | Attributed to Lazarus Group (North Korea) |
|
|
235
|
+
| $30M recovered | From USDC freeze |
|
|
236
|
+
| $625M+ lost | Remainder in Tornado Cash |
|
|
237
|
+
| Sky Mavis raise | Raised $150M to rebuild |
|
|
238
|
+
|
|
239
|
+
---
|
|
240
|
+
|
|
241
|
+
## Related Exploits
|
|
242
|
+
- [[wormhole-2022]] - Similar signature verification bypass
|
|
243
|
+
- [[nomad-2022]] - Replay attack on merkle proofs
|
|
244
|
+
- [[harmony-2022]] - Multi-sig compromise
|
|
245
|
+
|
|
246
|
+
---
|
|
247
|
+
|
|
248
|
+
## References
|
|
249
|
+
- [Ronin Bridge Post-Mortem](https://roninblockchain.substack.com/p/ronin-network-validator-compromise)
|
|
250
|
+
- [Chainalysis Report](https://blog.chainalysis.com/reports/north-korea-lazarus-group-cryptocurrency-thefts-2022-update/)
|
|
251
|
+
- [FBI Press Release](https://www.justice.gov/opa/pr/north-korean-lazarus-group-members-charged-conspiring-launder-2021-cyberheist-proceeds)
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
# Wormhole Bridge Exploit (2022)
|
|
2
|
+
|
|
3
|
+
**Date:** February 2, 2022
|
|
4
|
+
**Loss:** $325 million (120,000 wETH)
|
|
5
|
+
**Type:** Signature Verification Bypass
|
|
6
|
+
**Severity:** CRITICAL
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Summary
|
|
11
|
+
|
|
12
|
+
The Wormhole bridge (Solana ↔ Ethereum) was exploited due to a bug in the signature verification logic. The attacker was able to create valid withdrawal proofs without proper validator signatures.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Technical Details
|
|
17
|
+
|
|
18
|
+
### Vulnerability Root Cause
|
|
19
|
+
|
|
20
|
+
The vulnerability was in the **signature verification function** on the Solana side. The code failed to properly verify that the validator signatures matched the withdrawal data.
|
|
21
|
+
|
|
22
|
+
```rust
|
|
23
|
+
// Simplified Wormhole Guardian Logic (VULNERABLE)
|
|
24
|
+
fn verify_signatures(
|
|
25
|
+
message: &Message,
|
|
26
|
+
signatures: &[Signature],
|
|
27
|
+
guardians: &[Pubkey]
|
|
28
|
+
) -> Result<()> {
|
|
29
|
+
// BUG: Function verified signatures were valid
|
|
30
|
+
// But did NOT verify signatures were for THIS message
|
|
31
|
+
|
|
32
|
+
for (sig, guardian) in signatures.iter().zip(guardians.iter()) {
|
|
33
|
+
// Checked: Is this a valid signature from this guardian?
|
|
34
|
+
// Missing: Is this signature for THIS specific message?
|
|
35
|
+
if !verify_ed25519(sig, guardian, message) {
|
|
36
|
+
return Err(InvalidSignature);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
Ok(())
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### The Actual Bug
|
|
45
|
+
|
|
46
|
+
In the real code, the issue was more subtle. The guardian set verification had a logic error where:
|
|
47
|
+
|
|
48
|
+
1. The code checked if signatures were from valid guardians
|
|
49
|
+
2. But did not properly bind the signatures to the specific withdrawal hash
|
|
50
|
+
3. This allowed reuse of signatures or creation of fake proofs
|
|
51
|
+
|
|
52
|
+
### Attack Sequence
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
1. Attacker identifies signature verification bug
|
|
56
|
+
|
|
57
|
+
2. Creates fraudulent withdrawal instruction:
|
|
58
|
+
- Amount: 120,000 wETH
|
|
59
|
+
- Recipient: attacker-controlled address on Ethereum
|
|
60
|
+
|
|
61
|
+
3. Bypasses signature verification:
|
|
62
|
+
- Either reuses old valid signatures
|
|
63
|
+
- Or creates signatures that verify without guardian keys
|
|
64
|
+
|
|
65
|
+
4. Submits proof to Wormhole contract
|
|
66
|
+
|
|
67
|
+
5. Contract verifies (buggy) signatures - PASSES
|
|
68
|
+
|
|
69
|
+
6. 120,000 wETH minted and sent to attacker
|
|
70
|
+
|
|
71
|
+
7. Attacker bridges to Ethereum and cashes out
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## Exploit Code Analysis
|
|
77
|
+
|
|
78
|
+
### Vulnerable Pattern
|
|
79
|
+
|
|
80
|
+
```rust
|
|
81
|
+
// BEFORE FIX - Vulnerable
|
|
82
|
+
fn post_vaa(
|
|
83
|
+
&mut self,
|
|
84
|
+
vaa: Vec<u8>,
|
|
85
|
+
) -> Result<()> {
|
|
86
|
+
let parsed_vaa = parse_vaa(&vaa)?;
|
|
87
|
+
|
|
88
|
+
// BUG: Signature verification didn't properly bind to VAA body
|
|
89
|
+
verify_signatures(
|
|
90
|
+
&parsed_vaa.signatures,
|
|
91
|
+
&self.guardian_set
|
|
92
|
+
)?;
|
|
93
|
+
|
|
94
|
+
// Process withdrawal based on unverified VAA
|
|
95
|
+
self.process_withdrawal(&parsed_vaa.body)?;
|
|
96
|
+
|
|
97
|
+
Ok(())
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// AFTER FIX - Correct
|
|
101
|
+
fn post_vaa_fixed(
|
|
102
|
+
&mut self,
|
|
103
|
+
vaa: Vec<u8>,
|
|
104
|
+
) -> Result<()> {
|
|
105
|
+
let parsed_vaa = parse_vaa(&vaa)?;
|
|
106
|
+
|
|
107
|
+
// FIX: Hash includes full VAA body
|
|
108
|
+
let message_hash = keccak256(&parsed_vaa.body);
|
|
109
|
+
|
|
110
|
+
// Signatures must match THIS specific hash
|
|
111
|
+
verify_signatures_bound_to_hash(
|
|
112
|
+
&parsed_vaa.signatures,
|
|
113
|
+
&self.guardian_set,
|
|
114
|
+
&message_hash // <-- Added hash binding
|
|
115
|
+
)?;
|
|
116
|
+
|
|
117
|
+
self.process_withdrawal(&parsed_vaa.body)?;
|
|
118
|
+
Ok(())
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## Foundry PoC Template
|
|
125
|
+
|
|
126
|
+
```solidity
|
|
127
|
+
// Simplified PoC for Wormhole-style signature bypass
|
|
128
|
+
contract WormholeExploitTest is Test {
|
|
129
|
+
WormholeBridge bridge;
|
|
130
|
+
address[] guardians;
|
|
131
|
+
|
|
132
|
+
function test_signatureVerificationBypass() public {
|
|
133
|
+
// Setup: Bridge with guardian set
|
|
134
|
+
guardians = createGuardians(19);
|
|
135
|
+
bridge = new WormholeBridge(guardians, 13); // 13/19 threshold
|
|
136
|
+
|
|
137
|
+
// Fund bridge
|
|
138
|
+
deal(address(bridge.weth()), 200_000 ether);
|
|
139
|
+
|
|
140
|
+
// Create withdrawal message
|
|
141
|
+
Withdrawal memory withdrawal = Withdrawal({
|
|
142
|
+
recipient: address(this),
|
|
143
|
+
amount: 120_000 ether,
|
|
144
|
+
chainId: 1,
|
|
145
|
+
nonce: 1
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// BUG: Create signatures that verify but aren't bound to message
|
|
149
|
+
bytes memory fakeSignatures = createUnboundSignatures();
|
|
150
|
+
|
|
151
|
+
// Submit fraudulent VAA (Verifiable Action Approval)
|
|
152
|
+
bytes memory vaa = abi.encode(withdrawal, fakeSignatures);
|
|
153
|
+
|
|
154
|
+
uint256 balanceBefore = address(this).balance;
|
|
155
|
+
|
|
156
|
+
// This should fail but doesn't due to bug
|
|
157
|
+
bridge.postVAA(vaa);
|
|
158
|
+
|
|
159
|
+
uint256 balanceAfter = address(this).balance;
|
|
160
|
+
|
|
161
|
+
// Verify exploit succeeded
|
|
162
|
+
assertEq(balanceAfter - balanceBefore, 120_000 ether);
|
|
163
|
+
console.log("Wormhole exploit successful: 120,000 ETH drained");
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function createUnboundSignatures() internal pure returns (bytes memory) {
|
|
167
|
+
// Simulates creating signatures that verify
|
|
168
|
+
// but aren't properly bound to message hash
|
|
169
|
+
return new bytes(65 * 13); // 13 signatures
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## Why This Was Missed
|
|
177
|
+
|
|
178
|
+
| Audit Gap | Explanation |
|
|
179
|
+
|-----------|-------------|
|
|
180
|
+
| Assumed crypto correctness | Auditors assumed signature lib was correct |
|
|
181
|
+
| Focus on business logic | Didn't deep-dive into crypto primitives |
|
|
182
|
+
| Cross-chain complexity | Solana + Ethereum made review harder |
|
|
183
|
+
| Time pressure | Rapid deployment for competitive reasons |
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
## Key Lessons for Auditors
|
|
188
|
+
|
|
189
|
+
### 1. Never Assume Crypto Correctness
|
|
190
|
+
```
|
|
191
|
+
ALWAYS verify:
|
|
192
|
+
- Signature binding to specific message hash
|
|
193
|
+
- Nonce/replay protection
|
|
194
|
+
- Chain ID inclusion
|
|
195
|
+
- Domain separation
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### 2. Cross-Chain State Consistency
|
|
199
|
+
```
|
|
200
|
+
VERIFY:
|
|
201
|
+
- State on Chain A matches state on Chain B
|
|
202
|
+
- Wrapped supply = locked supply
|
|
203
|
+
- No double-spend vectors
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### 3. Guardian/Validator Security
|
|
207
|
+
```
|
|
208
|
+
CHECK:
|
|
209
|
+
- Guardian key management
|
|
210
|
+
- Threshold selection rationale
|
|
211
|
+
- Guardian rotation mechanism
|
|
212
|
+
- Slashing for malicious guardians
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
## Detection Checklist
|
|
218
|
+
|
|
219
|
+
When auditing bridges with signature verification:
|
|
220
|
+
|
|
221
|
+
- [ ] Signatures are bound to specific message hash
|
|
222
|
+
- [ ] Message hash includes: amount, recipient, nonce, chainId
|
|
223
|
+
- [ ] Replay protection implemented (nonce or unique ID)
|
|
224
|
+
- [ ] Domain separation prevents cross-chain replay
|
|
225
|
+
- [ ] Guardian set is immutable or has secure rotation
|
|
226
|
+
- [ ] Threshold is appropriate for value at risk
|
|
227
|
+
- [ ] Rate limits on large withdrawals
|
|
228
|
+
- [ ] Emergency pause mechanism
|
|
229
|
+
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
## Invariants to Test
|
|
233
|
+
|
|
234
|
+
```solidity
|
|
235
|
+
// INVARIANT: Every withdrawal must have valid guardian signatures
|
|
236
|
+
function invariant_withdrawalSignatures() public {
|
|
237
|
+
for (uint i = 0; i < withdrawalCount; i++) {
|
|
238
|
+
Withdrawal memory w = withdrawals[i];
|
|
239
|
+
assertTrue(
|
|
240
|
+
verifySignaturesBoundToMessage(w.signatures, w.hash),
|
|
241
|
+
"Signature not bound to message"
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// INVARIANT: Total wrapped supply equals locked supply
|
|
247
|
+
function invariant_wrappedSupply() public view {
|
|
248
|
+
assertEq(
|
|
249
|
+
wrappedToken.totalSupply(),
|
|
250
|
+
lockedToken.balanceOf(address(bridge))
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// INVARIANT: No duplicate nonces
|
|
255
|
+
function invariant_nonceUniqueness() public {
|
|
256
|
+
// Should revert if any nonce is reused
|
|
257
|
+
}
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
---
|
|
261
|
+
|
|
262
|
+
## Aftermath
|
|
263
|
+
|
|
264
|
+
| Action | Result |
|
|
265
|
+
|--------|--------|
|
|
266
|
+
| Bridge paused | February 2, 2022 |
|
|
267
|
+
| Jump Crypto bailout | Covered $325M loss |
|
|
268
|
+
| Bridge reopened | February 23, 2022 |
|
|
269
|
+
| Security audit | Multiple firms engaged |
|
|
270
|
+
| Code open sourced | Full transparency |
|
|
271
|
+
|
|
272
|
+
---
|
|
273
|
+
|
|
274
|
+
## Related Exploits
|
|
275
|
+
- [[ronin-2022]] - Validator compromise
|
|
276
|
+
- [[nomad-2022]] - Replay attack
|
|
277
|
+
- [[multichain-2023]] - Similar bridge exploit
|
|
278
|
+
|
|
279
|
+
---
|
|
280
|
+
|
|
281
|
+
## References
|
|
282
|
+
- [Wormhole Post-Mortem](https://wormhole.com/post-mortem)
|
|
283
|
+
- [Neodyme Analysis](https://neodyme.io/en/blog/wormhole)
|
|
284
|
+
- [OtterSec Report](https://osec.io/blog/wormhole-exploit)
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Failed Hypothesis Template
|
|
2
|
+
|
|
3
|
+
tags: #failed-hypothesis #template #learning
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Hypothesis
|
|
8
|
+
[One sentence describing the attempted attack]
|
|
9
|
+
|
|
10
|
+
## Contract
|
|
11
|
+
[Nome do Contrato] — [Address if deployed]
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## What Was Tested
|
|
16
|
+
[Description of the attack vector that was attempted]
|
|
17
|
+
|
|
18
|
+
## Attack Steps Attempted
|
|
19
|
+
```
|
|
20
|
+
1. [Step taken]
|
|
21
|
+
2. [Step taken]
|
|
22
|
+
3. [Step taken]
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Why It Failed
|
|
28
|
+
|
|
29
|
+
### Technical Reason
|
|
30
|
+
[The specific code, check, or condition that prevented the attack]
|
|
31
|
+
|
|
32
|
+
### Code Reference
|
|
33
|
+
```solidity
|
|
34
|
+
// The line or function that blocked the attack
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Root Cause Category
|
|
38
|
+
- [ ] Validation check present
|
|
39
|
+
- [ ] Access control blocked
|
|
40
|
+
- [ ] State transition invalid
|
|
41
|
+
- [ ] Economic assumptions wrong
|
|
42
|
+
- [ ] Timing constraints
|
|
43
|
+
- [ ] Protocol design prevents it
|
|
44
|
+
- [ ] Other: [specify]
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## Lessons Learned
|
|
49
|
+
|
|
50
|
+
### About This Contract
|
|
51
|
+
[What this tells us about the contract's security]
|
|
52
|
+
|
|
53
|
+
### Pattern Recognition
|
|
54
|
+
[What to look for in future audits — "this pattern prevents X"]
|
|
55
|
+
|
|
56
|
+
### Alternative Angles
|
|
57
|
+
[Ways this could still be exploitable or related attack vectors to try]
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## Related Hypotheses
|
|
62
|
+
- [[hypotheses/]] — Successful hypothesis on same contract
|
|
63
|
+
- [[attack-patterns/]] — Pattern this relates to
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## Metadata
|
|
68
|
+
- **Date:** [When tested]
|
|
69
|
+
- **Auditor:** [Name]
|
|
70
|
+
- **Time Invested:** [How long spent on this hypothesis]
|
|
71
|
+
- **Confidence Before:** [High/Medium/Low]
|
|
72
|
+
- **Confidence After:** [Confirmed refuted]
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## Notes
|
|
77
|
+
[Any additional observations, traces, or context]
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Hypothesis Template
|
|
2
|
+
|
|
3
|
+
tags: #hypothesis #template
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Contract
|
|
8
|
+
[Contract name and address if deployed]
|
|
9
|
+
|
|
10
|
+
## Hypothesis
|
|
11
|
+
[One sentence: IF [condition] THEN [consequence]]
|
|
12
|
+
|
|
13
|
+
## Attack Pattern
|
|
14
|
+
[Which pattern does this relate to? Link to attack-patterns/]
|
|
15
|
+
|
|
16
|
+
## Preconditions
|
|
17
|
+
- [ ] Condition 1
|
|
18
|
+
- [ ] Condition 2
|
|
19
|
+
|
|
20
|
+
## Attack Steps
|
|
21
|
+
```
|
|
22
|
+
1.
|
|
23
|
+
2.
|
|
24
|
+
3.
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Expected Outcome
|
|
28
|
+
[What happens if hypothesis is correct?]
|
|
29
|
+
|
|
30
|
+
## Test Strategy
|
|
31
|
+
[How to test this hypothesis in Foundry?]
|
|
32
|
+
|
|
33
|
+
## Result
|
|
34
|
+
- [ ] Confirmed — vulnerability exists
|
|
35
|
+
- [ ] Refuted — not exploitable
|
|
36
|
+
- [ ] Partial — exists but limited impact
|
|
37
|
+
|
|
38
|
+
## Notes
|
|
39
|
+
[Observations during testing]
|
|
40
|
+
|
|
41
|
+
## Links
|
|
42
|
+
- [[vulnerabilities/]]
|
|
43
|
+
- [[attack-patterns/]]
|