web3crit-scanner 7.0.1
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/README.md +685 -0
- package/bin/web3crit +10 -0
- package/package.json +59 -0
- package/src/analyzers/control-flow.js +256 -0
- package/src/analyzers/data-flow.js +720 -0
- package/src/analyzers/exploit-chain.js +751 -0
- package/src/analyzers/immunefi-classifier.js +515 -0
- package/src/analyzers/poc-validator.js +396 -0
- package/src/analyzers/solodit-enricher.js +1122 -0
- package/src/cli.js +546 -0
- package/src/detectors/access-control-enhanced.js +458 -0
- package/src/detectors/base-detector.js +213 -0
- package/src/detectors/callback-reentrancy.js +362 -0
- package/src/detectors/cross-contract-reentrancy.js +697 -0
- package/src/detectors/delegatecall.js +167 -0
- package/src/detectors/deprecated-functions.js +62 -0
- package/src/detectors/flash-loan.js +408 -0
- package/src/detectors/frontrunning.js +553 -0
- package/src/detectors/gas-griefing.js +701 -0
- package/src/detectors/governance-attacks.js +366 -0
- package/src/detectors/integer-overflow.js +487 -0
- package/src/detectors/oracle-manipulation.js +524 -0
- package/src/detectors/permit-exploits.js +368 -0
- package/src/detectors/precision-loss.js +408 -0
- package/src/detectors/price-manipulation-advanced.js +548 -0
- package/src/detectors/proxy-vulnerabilities.js +651 -0
- package/src/detectors/readonly-reentrancy.js +473 -0
- package/src/detectors/rebasing-token-vault.js +416 -0
- package/src/detectors/reentrancy-enhanced.js +359 -0
- package/src/detectors/selfdestruct.js +259 -0
- package/src/detectors/share-manipulation.js +412 -0
- package/src/detectors/signature-replay.js +409 -0
- package/src/detectors/storage-collision.js +446 -0
- package/src/detectors/timestamp-dependence.js +494 -0
- package/src/detectors/toctou.js +427 -0
- package/src/detectors/token-standard-compliance.js +465 -0
- package/src/detectors/unchecked-call.js +214 -0
- package/src/detectors/vault-inflation.js +421 -0
- package/src/index.js +42 -0
- package/src/package-lock.json +2874 -0
- package/src/package.json +39 -0
- package/src/scanner-enhanced.js +816 -0
|
@@ -0,0 +1,515 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Immunefi Severity Classifier
|
|
3
|
+
* Maps vulnerability findings to Immunefi bug bounty payout categories
|
|
4
|
+
* Only reports findings that qualify for High/Critical payouts
|
|
5
|
+
*
|
|
6
|
+
* Immunefi Severity Levels:
|
|
7
|
+
* - Critical: Direct theft of funds, permanent freezing, protocol insolvency, governance takeover
|
|
8
|
+
* - High: Theft of unclaimed yield, temporary freezing, griefing with capital requirements
|
|
9
|
+
* - Medium/Low: Filtered out for high-TVL focus
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const IMMUNEFI_CATEGORIES = {
|
|
13
|
+
CRITICAL: {
|
|
14
|
+
DIRECT_THEFT: {
|
|
15
|
+
id: 'direct-theft',
|
|
16
|
+
name: 'Direct Theft of Funds',
|
|
17
|
+
description: 'Attacker can steal user deposits, protocol reserves, or treasury funds',
|
|
18
|
+
minScore: 90,
|
|
19
|
+
requiredElements: ['value_extraction', 'no_auth_bypass_needed_OR_auth_bypass_present']
|
|
20
|
+
},
|
|
21
|
+
PERMANENT_FREEZE: {
|
|
22
|
+
id: 'permanent-freeze',
|
|
23
|
+
name: 'Permanent Freezing of Funds',
|
|
24
|
+
description: 'Funds become permanently inaccessible (not temporary DoS)',
|
|
25
|
+
minScore: 85,
|
|
26
|
+
requiredElements: ['fund_lock', 'no_recovery_path']
|
|
27
|
+
},
|
|
28
|
+
PROTOCOL_INSOLVENCY: {
|
|
29
|
+
id: 'protocol-insolvency',
|
|
30
|
+
name: 'Protocol Insolvency',
|
|
31
|
+
description: 'Protocol becomes unable to honor withdrawals or obligations',
|
|
32
|
+
minScore: 90,
|
|
33
|
+
requiredElements: ['accounting_manipulation', 'systemic_impact']
|
|
34
|
+
},
|
|
35
|
+
GOVERNANCE_TAKEOVER: {
|
|
36
|
+
id: 'governance-takeover',
|
|
37
|
+
name: 'Governance Takeover',
|
|
38
|
+
description: 'Attacker gains control of protocol governance/admin functions',
|
|
39
|
+
minScore: 85,
|
|
40
|
+
requiredElements: ['admin_access', 'persistent_control']
|
|
41
|
+
},
|
|
42
|
+
UNAUTHORIZED_MINTING: {
|
|
43
|
+
id: 'unauthorized-minting',
|
|
44
|
+
name: 'Unauthorized Token Minting',
|
|
45
|
+
description: 'Attacker can mint tokens without proper authorization',
|
|
46
|
+
minScore: 90,
|
|
47
|
+
requiredElements: ['mint_capability', 'value_extraction']
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
HIGH: {
|
|
51
|
+
YIELD_THEFT: {
|
|
52
|
+
id: 'yield-theft',
|
|
53
|
+
name: 'Theft of Unclaimed Yield',
|
|
54
|
+
description: 'Attacker can steal yield/rewards that belong to other users',
|
|
55
|
+
minScore: 75,
|
|
56
|
+
requiredElements: ['yield_extraction', 'other_user_impact']
|
|
57
|
+
},
|
|
58
|
+
TEMPORARY_FREEZE: {
|
|
59
|
+
id: 'temporary-freeze',
|
|
60
|
+
name: 'Temporary Freezing of Funds',
|
|
61
|
+
description: 'Funds locked temporarily but recoverable (DoS with fund impact)',
|
|
62
|
+
minScore: 70,
|
|
63
|
+
requiredElements: ['fund_lock', 'recovery_possible']
|
|
64
|
+
},
|
|
65
|
+
MANIPULATION_PROFIT: {
|
|
66
|
+
id: 'manipulation-profit',
|
|
67
|
+
name: 'Price/State Manipulation for Profit',
|
|
68
|
+
description: 'Attacker profits by manipulating prices or protocol state',
|
|
69
|
+
minScore: 75,
|
|
70
|
+
requiredElements: ['state_manipulation', 'profit_extraction']
|
|
71
|
+
},
|
|
72
|
+
PRIVILEGE_ESCALATION: {
|
|
73
|
+
id: 'privilege-escalation',
|
|
74
|
+
name: 'Privilege Escalation',
|
|
75
|
+
description: 'Attacker gains elevated permissions without authorization',
|
|
76
|
+
minScore: 70,
|
|
77
|
+
requiredElements: ['auth_bypass', 'elevated_access']
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
// Maps vulnerability types to potential Immunefi categories
|
|
83
|
+
const VULNERABILITY_TO_CATEGORY_MAP = {
|
|
84
|
+
// Reentrancy variants
|
|
85
|
+
'reentrancy': ['DIRECT_THEFT', 'PROTOCOL_INSOLVENCY'],
|
|
86
|
+
'cross-function-reentrancy': ['DIRECT_THEFT', 'PROTOCOL_INSOLVENCY'],
|
|
87
|
+
'cross-contract-reentrancy': ['DIRECT_THEFT', 'PROTOCOL_INSOLVENCY'],
|
|
88
|
+
'read-only-reentrancy': ['MANIPULATION_PROFIT'],
|
|
89
|
+
|
|
90
|
+
// Access control (multiple name variants)
|
|
91
|
+
'access-control': ['DIRECT_THEFT', 'GOVERNANCE_TAKEOVER', 'UNAUTHORIZED_MINTING'],
|
|
92
|
+
'missing-access-control': ['DIRECT_THEFT', 'GOVERNANCE_TAKEOVER', 'UNAUTHORIZED_MINTING'],
|
|
93
|
+
'broken-access-control': ['DIRECT_THEFT', 'GOVERNANCE_TAKEOVER'],
|
|
94
|
+
'tx-origin': ['DIRECT_THEFT', 'PRIVILEGE_ESCALATION'],
|
|
95
|
+
|
|
96
|
+
// Oracle/Price manipulation
|
|
97
|
+
'flash-loan': ['DIRECT_THEFT', 'PROTOCOL_INSOLVENCY', 'MANIPULATION_PROFIT'],
|
|
98
|
+
'flash-loan-oracle': ['DIRECT_THEFT', 'PROTOCOL_INSOLVENCY', 'MANIPULATION_PROFIT'],
|
|
99
|
+
'flash-loan-oracle-manipulation': ['DIRECT_THEFT', 'PROTOCOL_INSOLVENCY'],
|
|
100
|
+
'flash-loan-balance-manipulation': ['DIRECT_THEFT', 'MANIPULATION_PROFIT'],
|
|
101
|
+
'flash-loan-governance': ['GOVERNANCE_TAKEOVER'],
|
|
102
|
+
'balance-manipulation': ['DIRECT_THEFT', 'MANIPULATION_PROFIT'],
|
|
103
|
+
'spot-price-manipulation': ['DIRECT_THEFT', 'MANIPULATION_PROFIT'],
|
|
104
|
+
'oracle-manipulation': ['DIRECT_THEFT', 'MANIPULATION_PROFIT'],
|
|
105
|
+
'stale-price': ['MANIPULATION_PROFIT', 'DIRECT_THEFT'],
|
|
106
|
+
'stale-price-exploitation': ['MANIPULATION_PROFIT', 'DIRECT_THEFT'],
|
|
107
|
+
|
|
108
|
+
// Proxy/Upgrade
|
|
109
|
+
'unprotected-initializer': ['GOVERNANCE_TAKEOVER', 'DIRECT_THEFT'],
|
|
110
|
+
'unauthorized-upgrade': ['GOVERNANCE_TAKEOVER', 'DIRECT_THEFT'],
|
|
111
|
+
'storage-collision': ['DIRECT_THEFT', 'PROTOCOL_INSOLVENCY'],
|
|
112
|
+
'proxy': ['GOVERNANCE_TAKEOVER', 'DIRECT_THEFT'],
|
|
113
|
+
|
|
114
|
+
// Signature/Replay
|
|
115
|
+
'signature-replay': ['DIRECT_THEFT', 'PRIVILEGE_ESCALATION'],
|
|
116
|
+
'permit-dos': ['TEMPORARY_FREEZE'],
|
|
117
|
+
'permit-replay': ['DIRECT_THEFT'],
|
|
118
|
+
'missing-deadline': ['MANIPULATION_PROFIT'],
|
|
119
|
+
|
|
120
|
+
// Fund handling
|
|
121
|
+
'unchecked-call': ['DIRECT_THEFT', 'PERMANENT_FREEZE'],
|
|
122
|
+
'unchecked-transfer': ['DIRECT_THEFT'],
|
|
123
|
+
'delegatecall': ['DIRECT_THEFT', 'GOVERNANCE_TAKEOVER'],
|
|
124
|
+
'delegatecall-injection': ['DIRECT_THEFT', 'GOVERNANCE_TAKEOVER'],
|
|
125
|
+
'selfdestruct': ['PERMANENT_FREEZE', 'DIRECT_THEFT'],
|
|
126
|
+
|
|
127
|
+
// TOCTOU
|
|
128
|
+
'toctou': ['DIRECT_THEFT', 'MANIPULATION_PROFIT'],
|
|
129
|
+
|
|
130
|
+
// DeFi specific
|
|
131
|
+
'vault-inflation': ['DIRECT_THEFT', 'YIELD_THEFT'],
|
|
132
|
+
'share-manipulation': ['DIRECT_THEFT', 'YIELD_THEFT'],
|
|
133
|
+
'first-depositor': ['DIRECT_THEFT'],
|
|
134
|
+
'first-depositor-inflation': ['DIRECT_THEFT'],
|
|
135
|
+
'donation-attack': ['MANIPULATION_PROFIT', 'YIELD_THEFT'],
|
|
136
|
+
'sandwich-donation': ['MANIPULATION_PROFIT'],
|
|
137
|
+
'rounding-exploit': ['YIELD_THEFT'],
|
|
138
|
+
|
|
139
|
+
// Governance
|
|
140
|
+
'governance': ['GOVERNANCE_TAKEOVER'],
|
|
141
|
+
'governance-reentrancy': ['GOVERNANCE_TAKEOVER'],
|
|
142
|
+
|
|
143
|
+
// Fallback for unknown (still try to classify based on elements)
|
|
144
|
+
'unknown': ['DIRECT_THEFT', 'MANIPULATION_PROFIT']
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
class ImmunefiClassifier {
|
|
148
|
+
constructor() {
|
|
149
|
+
this.categories = IMMUNEFI_CATEGORIES;
|
|
150
|
+
this.vulnMap = VULNERABILITY_TO_CATEGORY_MAP;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Classify a finding into Immunefi payout category
|
|
155
|
+
* Returns null if finding doesn't qualify for High/Critical
|
|
156
|
+
*/
|
|
157
|
+
classifyFinding(finding) {
|
|
158
|
+
const attackVector = this.normalizeAttackVector(finding.attackVector || finding.detector);
|
|
159
|
+
const potentialCategories = this.vulnMap[attackVector] || [];
|
|
160
|
+
|
|
161
|
+
if (potentialCategories.length === 0) {
|
|
162
|
+
return null; // No mapping, doesn't qualify
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Analyze finding for exploit chain elements
|
|
166
|
+
const exploitElements = this.analyzeExploitElements(finding);
|
|
167
|
+
|
|
168
|
+
// Try to match to highest severity category first
|
|
169
|
+
for (const categoryId of potentialCategories) {
|
|
170
|
+
const category = this.findCategory(categoryId);
|
|
171
|
+
if (!category) continue;
|
|
172
|
+
|
|
173
|
+
const match = this.matchCategory(finding, category, exploitElements);
|
|
174
|
+
if (match.qualifies) {
|
|
175
|
+
return {
|
|
176
|
+
severity: this.getCategorySeverity(categoryId),
|
|
177
|
+
category: category,
|
|
178
|
+
categoryId: categoryId,
|
|
179
|
+
exploitChain: match.exploitChain,
|
|
180
|
+
confidence: match.confidence,
|
|
181
|
+
payoutTier: this.getCategorySeverity(categoryId) === 'CRITICAL' ? 'Critical' : 'High',
|
|
182
|
+
impactDescription: this.generateImpactDescription(category, finding, match)
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return null; // Doesn't qualify for High/Critical
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Analyze finding for exploit chain elements
|
|
192
|
+
*/
|
|
193
|
+
analyzeExploitElements(finding) {
|
|
194
|
+
const elements = new Set();
|
|
195
|
+
const desc = (finding.description || '').toLowerCase();
|
|
196
|
+
const title = (finding.title || '').toLowerCase();
|
|
197
|
+
const combined = desc + ' ' + title;
|
|
198
|
+
|
|
199
|
+
// Value extraction indicators
|
|
200
|
+
if (/drain|steal|extract|profit|arbitrage|theft/.test(combined)) {
|
|
201
|
+
elements.add('value_extraction');
|
|
202
|
+
}
|
|
203
|
+
if (/transfer|withdraw|claim|redeem/.test(combined)) {
|
|
204
|
+
elements.add('value_extraction');
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Auth bypass indicators
|
|
208
|
+
if (/no.*access.*control|missing.*modifier|bypass|unauthorized/.test(combined)) {
|
|
209
|
+
elements.add('auth_bypass');
|
|
210
|
+
elements.add('auth_bypass_present');
|
|
211
|
+
}
|
|
212
|
+
// High confidence exploitable = no auth bypass needed (directly callable)
|
|
213
|
+
if (finding.exploitable === true && finding.confidence === 'HIGH') {
|
|
214
|
+
elements.add('no_auth_bypass_needed');
|
|
215
|
+
}
|
|
216
|
+
// Also support reentrant attacks that don't need auth bypass
|
|
217
|
+
if (/reentran|drain|steal|exploit/.test(combined) && finding.exploitable === true) {
|
|
218
|
+
elements.add('no_auth_bypass_needed');
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Fund lock indicators
|
|
222
|
+
if (/lock|freeze|stuck|inaccessible|brick/.test(combined)) {
|
|
223
|
+
elements.add('fund_lock');
|
|
224
|
+
}
|
|
225
|
+
if (/permanent|forever|irrecoverable|destroy/.test(combined)) {
|
|
226
|
+
elements.add('no_recovery_path');
|
|
227
|
+
}
|
|
228
|
+
if (/temporary|dos|delay|block/.test(combined)) {
|
|
229
|
+
elements.add('recovery_possible');
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Accounting manipulation
|
|
233
|
+
if (/inflation|deflation|accounting|balance.*manipul|share.*manipul/.test(combined)) {
|
|
234
|
+
elements.add('accounting_manipulation');
|
|
235
|
+
}
|
|
236
|
+
if (/insolvency|insolvent|undercollateral/.test(combined)) {
|
|
237
|
+
elements.add('systemic_impact');
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Admin/Governance
|
|
241
|
+
if (/owner|admin|governance|upgrade|initializ/.test(combined)) {
|
|
242
|
+
elements.add('admin_access');
|
|
243
|
+
}
|
|
244
|
+
if (/takeover|control|persist|permanent.*access/.test(combined)) {
|
|
245
|
+
elements.add('persistent_control');
|
|
246
|
+
}
|
|
247
|
+
if (/elevat|escalat|privilege/.test(combined)) {
|
|
248
|
+
elements.add('elevated_access');
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Minting
|
|
252
|
+
if (/mint|create.*token|issue.*token/.test(combined)) {
|
|
253
|
+
elements.add('mint_capability');
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Yield/Rewards
|
|
257
|
+
if (/yield|reward|interest|dividend|fee/.test(combined)) {
|
|
258
|
+
elements.add('yield_extraction');
|
|
259
|
+
}
|
|
260
|
+
if (/other.*user|victim|deposit/.test(combined)) {
|
|
261
|
+
elements.add('other_user_impact');
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// State manipulation
|
|
265
|
+
if (/manipulat|flash.*loan|oracle|price|state/.test(combined)) {
|
|
266
|
+
elements.add('state_manipulation');
|
|
267
|
+
}
|
|
268
|
+
if (/profit|gain|extract|arbitrage/.test(combined)) {
|
|
269
|
+
elements.add('profit_extraction');
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return elements;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Match finding against category requirements
|
|
277
|
+
*/
|
|
278
|
+
matchCategory(finding, category, exploitElements) {
|
|
279
|
+
const required = category.requiredElements || [];
|
|
280
|
+
const matched = [];
|
|
281
|
+
const missing = [];
|
|
282
|
+
|
|
283
|
+
for (const element of required) {
|
|
284
|
+
// Handle OR conditions (element1_OR_element2)
|
|
285
|
+
if (element.includes('_OR_')) {
|
|
286
|
+
const alternatives = element.split('_OR_');
|
|
287
|
+
const hasAny = alternatives.some(alt => exploitElements.has(alt));
|
|
288
|
+
if (hasAny) {
|
|
289
|
+
matched.push(element);
|
|
290
|
+
} else {
|
|
291
|
+
missing.push(element);
|
|
292
|
+
}
|
|
293
|
+
} else {
|
|
294
|
+
if (exploitElements.has(element)) {
|
|
295
|
+
matched.push(element);
|
|
296
|
+
} else {
|
|
297
|
+
missing.push(element);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Calculate match quality
|
|
303
|
+
const matchRatio = required.length > 0 ? matched.length / required.length : 0;
|
|
304
|
+
const scoreThreshold = category.minScore || 70;
|
|
305
|
+
const findingScore = finding.exploitabilityScore || 50;
|
|
306
|
+
|
|
307
|
+
// Qualifies if:
|
|
308
|
+
// 1. All required elements present OR high match ratio with high score
|
|
309
|
+
// 2. Finding score meets minimum threshold
|
|
310
|
+
const qualifies = (
|
|
311
|
+
(missing.length === 0 || (matchRatio >= 0.7 && findingScore >= 80)) &&
|
|
312
|
+
findingScore >= scoreThreshold &&
|
|
313
|
+
finding.exploitable !== false
|
|
314
|
+
);
|
|
315
|
+
|
|
316
|
+
return {
|
|
317
|
+
qualifies,
|
|
318
|
+
matchedElements: matched,
|
|
319
|
+
missingElements: missing,
|
|
320
|
+
matchRatio,
|
|
321
|
+
confidence: this.calculateConfidence(matchRatio, finding),
|
|
322
|
+
exploitChain: this.buildExploitChain(finding, category, exploitElements)
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Build concrete exploit chain description
|
|
328
|
+
*/
|
|
329
|
+
buildExploitChain(finding, category, elements) {
|
|
330
|
+
const chain = {
|
|
331
|
+
attackerCapabilities: this.inferAttackerCapabilities(finding, elements),
|
|
332
|
+
entryPoint: this.extractEntryPoint(finding),
|
|
333
|
+
vulnerableOperation: this.extractVulnerableOp(finding),
|
|
334
|
+
impact: category.description,
|
|
335
|
+
profitMechanism: this.inferProfitMechanism(finding, elements),
|
|
336
|
+
steps: []
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
// Build step-by-step exploit
|
|
340
|
+
const attackVector = (finding.attackVector || '').toLowerCase();
|
|
341
|
+
|
|
342
|
+
if (attackVector.includes('reentrancy')) {
|
|
343
|
+
chain.steps = [
|
|
344
|
+
'Attacker deploys malicious contract with fallback/receive function',
|
|
345
|
+
`Attacker calls ${chain.entryPoint || 'vulnerable function'}`,
|
|
346
|
+
'Vulnerable contract makes external call to attacker',
|
|
347
|
+
'Attacker reenters before state update completes',
|
|
348
|
+
'Attacker drains funds through repeated reentry',
|
|
349
|
+
'State finally updates but funds already extracted'
|
|
350
|
+
];
|
|
351
|
+
} else if (attackVector.includes('flash-loan') || attackVector.includes('oracle')) {
|
|
352
|
+
chain.steps = [
|
|
353
|
+
'Attacker obtains flash loan (Aave/dYdX/Balancer)',
|
|
354
|
+
'Attacker manipulates price oracle (large swap/donation)',
|
|
355
|
+
`Attacker calls ${chain.entryPoint || 'vulnerable function'} at manipulated price`,
|
|
356
|
+
'Attacker extracts value at favorable rate',
|
|
357
|
+
'Attacker reverses manipulation (swap back)',
|
|
358
|
+
'Attacker repays flash loan, keeps profit'
|
|
359
|
+
];
|
|
360
|
+
} else if (attackVector.includes('access-control')) {
|
|
361
|
+
chain.steps = [
|
|
362
|
+
'Attacker identifies unprotected privileged function',
|
|
363
|
+
`Attacker calls ${chain.entryPoint || 'admin function'} directly`,
|
|
364
|
+
'Function executes without authorization check',
|
|
365
|
+
chain.profitMechanism || 'Attacker gains unauthorized access/funds'
|
|
366
|
+
];
|
|
367
|
+
} else if (attackVector.includes('initializer')) {
|
|
368
|
+
chain.steps = [
|
|
369
|
+
'Attacker identifies uninitialized proxy or reinitializable contract',
|
|
370
|
+
'Attacker calls initialize() with attacker-controlled parameters',
|
|
371
|
+
'Attacker sets themselves as owner/admin',
|
|
372
|
+
'Attacker uses admin privileges to drain funds or upgrade maliciously'
|
|
373
|
+
];
|
|
374
|
+
} else if (attackVector.includes('signature') || attackVector.includes('replay')) {
|
|
375
|
+
chain.steps = [
|
|
376
|
+
'Attacker obtains valid signature from previous transaction',
|
|
377
|
+
'Attacker replays signature (missing nonce/deadline/chainId)',
|
|
378
|
+
`Signature validates and executes ${chain.entryPoint || 'privileged action'}`,
|
|
379
|
+
'Attacker profits from unauthorized repeated execution'
|
|
380
|
+
];
|
|
381
|
+
} else {
|
|
382
|
+
chain.steps = [
|
|
383
|
+
`Identify vulnerable operation in ${chain.entryPoint || 'target function'}`,
|
|
384
|
+
'Prepare attack transaction',
|
|
385
|
+
'Execute exploit',
|
|
386
|
+
'Extract value or gain unauthorized access'
|
|
387
|
+
];
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
return chain;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Infer required attacker capabilities
|
|
395
|
+
*/
|
|
396
|
+
inferAttackerCapabilities(finding, elements) {
|
|
397
|
+
const capabilities = [];
|
|
398
|
+
const desc = (finding.description || '').toLowerCase();
|
|
399
|
+
|
|
400
|
+
if (elements.has('state_manipulation') || /flash.*loan|manipulat/.test(desc)) {
|
|
401
|
+
capabilities.push('Flash loan access (Aave, dYdX, Balancer)');
|
|
402
|
+
}
|
|
403
|
+
if (/mev|sandwich|frontrun/.test(desc)) {
|
|
404
|
+
capabilities.push('MEV infrastructure (Flashbots, private mempool)');
|
|
405
|
+
}
|
|
406
|
+
if (/reentran/.test(desc)) {
|
|
407
|
+
capabilities.push('Ability to deploy malicious contract');
|
|
408
|
+
}
|
|
409
|
+
if (capabilities.length === 0) {
|
|
410
|
+
capabilities.push('Standard EOA with gas');
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
return capabilities;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
extractEntryPoint(finding) {
|
|
417
|
+
let loc = finding.location || '';
|
|
418
|
+
if (typeof loc !== 'string') {
|
|
419
|
+
loc = String(loc) || '';
|
|
420
|
+
}
|
|
421
|
+
const funcMatch = loc.match(/Function:\s*(\w+)/);
|
|
422
|
+
return funcMatch ? funcMatch[1] + '()' : null;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
extractVulnerableOp(finding) {
|
|
426
|
+
return finding.title || finding.detector;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
inferProfitMechanism(finding, elements) {
|
|
430
|
+
if (elements.has('value_extraction')) {
|
|
431
|
+
return 'Direct fund extraction via vulnerable function';
|
|
432
|
+
}
|
|
433
|
+
if (elements.has('yield_extraction')) {
|
|
434
|
+
return 'Theft of accumulated yield/rewards';
|
|
435
|
+
}
|
|
436
|
+
if (elements.has('profit_extraction')) {
|
|
437
|
+
return 'Arbitrage profit from price manipulation';
|
|
438
|
+
}
|
|
439
|
+
if (elements.has('mint_capability')) {
|
|
440
|
+
return 'Minting tokens and selling on market';
|
|
441
|
+
}
|
|
442
|
+
return 'Unauthorized value extraction';
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
calculateConfidence(matchRatio, finding) {
|
|
446
|
+
const baseConfidence = finding.confidence === 'HIGH' ? 0.9 :
|
|
447
|
+
finding.confidence === 'MEDIUM' ? 0.7 : 0.5;
|
|
448
|
+
return Math.min(1, baseConfidence * (0.5 + matchRatio * 0.5));
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
findCategory(categoryId) {
|
|
452
|
+
for (const severity of ['CRITICAL', 'HIGH']) {
|
|
453
|
+
if (this.categories[severity][categoryId]) {
|
|
454
|
+
return this.categories[severity][categoryId];
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
return null;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
getCategorySeverity(categoryId) {
|
|
461
|
+
if (this.categories.CRITICAL[categoryId]) return 'CRITICAL';
|
|
462
|
+
if (this.categories.HIGH[categoryId]) return 'HIGH';
|
|
463
|
+
return 'MEDIUM';
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
normalizeAttackVector(vector) {
|
|
467
|
+
if (!vector) return '';
|
|
468
|
+
return vector.toLowerCase()
|
|
469
|
+
.replace(/[^a-z0-9-]/g, '-')
|
|
470
|
+
.replace(/-+/g, '-')
|
|
471
|
+
.replace(/^-|-$/g, '');
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
generateImpactDescription(category, finding, match) {
|
|
475
|
+
const entryPoint = this.extractEntryPoint(finding);
|
|
476
|
+
const func = entryPoint || 'the vulnerable function';
|
|
477
|
+
|
|
478
|
+
return `${category.name}: An attacker can exploit ${func} to ${category.description.toLowerCase()}. ` +
|
|
479
|
+
`This qualifies as Immunefi ${this.getCategorySeverity(category.id) || 'High'} severity. ` +
|
|
480
|
+
`Exploit confidence: ${Math.round(match.confidence * 100)}%.`;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
/**
|
|
484
|
+
* Filter findings to only Immunefi High/Critical
|
|
485
|
+
*/
|
|
486
|
+
filterToPayoutLevel(findings) {
|
|
487
|
+
const qualified = [];
|
|
488
|
+
|
|
489
|
+
for (const finding of findings) {
|
|
490
|
+
const classification = this.classifyFinding(finding);
|
|
491
|
+
|
|
492
|
+
if (classification) {
|
|
493
|
+
qualified.push({
|
|
494
|
+
...finding,
|
|
495
|
+
immunefiClassification: classification,
|
|
496
|
+
severity: classification.severity, // Upgrade severity based on classification
|
|
497
|
+
payoutTier: classification.payoutTier,
|
|
498
|
+
exploitChain: classification.exploitChain
|
|
499
|
+
});
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// Sort by severity then by exploitability score
|
|
504
|
+
qualified.sort((a, b) => {
|
|
505
|
+
const sevOrder = { 'CRITICAL': 2, 'HIGH': 1 };
|
|
506
|
+
const sevDiff = (sevOrder[b.severity] || 0) - (sevOrder[a.severity] || 0);
|
|
507
|
+
if (sevDiff !== 0) return sevDiff;
|
|
508
|
+
return (b.exploitabilityScore || 0) - (a.exploitabilityScore || 0);
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
return qualified;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
module.exports = ImmunefiClassifier;
|