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.
Files changed (42) hide show
  1. package/README.md +685 -0
  2. package/bin/web3crit +10 -0
  3. package/package.json +59 -0
  4. package/src/analyzers/control-flow.js +256 -0
  5. package/src/analyzers/data-flow.js +720 -0
  6. package/src/analyzers/exploit-chain.js +751 -0
  7. package/src/analyzers/immunefi-classifier.js +515 -0
  8. package/src/analyzers/poc-validator.js +396 -0
  9. package/src/analyzers/solodit-enricher.js +1122 -0
  10. package/src/cli.js +546 -0
  11. package/src/detectors/access-control-enhanced.js +458 -0
  12. package/src/detectors/base-detector.js +213 -0
  13. package/src/detectors/callback-reentrancy.js +362 -0
  14. package/src/detectors/cross-contract-reentrancy.js +697 -0
  15. package/src/detectors/delegatecall.js +167 -0
  16. package/src/detectors/deprecated-functions.js +62 -0
  17. package/src/detectors/flash-loan.js +408 -0
  18. package/src/detectors/frontrunning.js +553 -0
  19. package/src/detectors/gas-griefing.js +701 -0
  20. package/src/detectors/governance-attacks.js +366 -0
  21. package/src/detectors/integer-overflow.js +487 -0
  22. package/src/detectors/oracle-manipulation.js +524 -0
  23. package/src/detectors/permit-exploits.js +368 -0
  24. package/src/detectors/precision-loss.js +408 -0
  25. package/src/detectors/price-manipulation-advanced.js +548 -0
  26. package/src/detectors/proxy-vulnerabilities.js +651 -0
  27. package/src/detectors/readonly-reentrancy.js +473 -0
  28. package/src/detectors/rebasing-token-vault.js +416 -0
  29. package/src/detectors/reentrancy-enhanced.js +359 -0
  30. package/src/detectors/selfdestruct.js +259 -0
  31. package/src/detectors/share-manipulation.js +412 -0
  32. package/src/detectors/signature-replay.js +409 -0
  33. package/src/detectors/storage-collision.js +446 -0
  34. package/src/detectors/timestamp-dependence.js +494 -0
  35. package/src/detectors/toctou.js +427 -0
  36. package/src/detectors/token-standard-compliance.js +465 -0
  37. package/src/detectors/unchecked-call.js +214 -0
  38. package/src/detectors/vault-inflation.js +421 -0
  39. package/src/index.js +42 -0
  40. package/src/package-lock.json +2874 -0
  41. package/src/package.json +39 -0
  42. package/src/scanner-enhanced.js +816 -0
@@ -0,0 +1,487 @@
1
+ const BaseDetector = require('./base-detector');
2
+
3
+ /**
4
+ * Integer Overflow/Underflow Detector (Enhanced)
5
+ * Detects arithmetic operations that may overflow or underflow
6
+ * with context awareness to reduce false positives for safe patterns.
7
+ *
8
+ * Note: Solidity 0.8.0+ has built-in overflow checks except in unchecked blocks
9
+ */
10
+ class IntegerOverflowDetector extends BaseDetector {
11
+ constructor() {
12
+ super(
13
+ 'Integer Overflow/Underflow',
14
+ 'Detects arithmetic operations vulnerable to overflow or underflow',
15
+ 'HIGH'
16
+ );
17
+ this.solidityVersion = null;
18
+ this.inUncheckedBlock = false;
19
+ this.uncheckedDepth = 0;
20
+ this.currentContract = null;
21
+ this.currentFunction = null;
22
+ this.currentFunctionNode = null;
23
+ this.functionParameters = new Set();
24
+ this.hasSafeMath = false;
25
+ }
26
+
27
+ async detect(ast, sourceCode, fileName, cfg, dataFlow) {
28
+ this.findings = [];
29
+ this.ast = ast;
30
+ this.sourceCode = sourceCode;
31
+ this.fileName = fileName;
32
+ this.sourceLines = sourceCode.split('\n');
33
+ this.cfg = cfg;
34
+ this.dataFlow = dataFlow;
35
+
36
+ // Extract Solidity version from pragma
37
+ this.extractSolidityVersion(ast);
38
+
39
+ // Check for SafeMath usage
40
+ this.hasSafeMath = /using\s+SafeMath\s+for/i.test(sourceCode) ||
41
+ /import.*SafeMath/i.test(sourceCode);
42
+
43
+ // Traverse the AST
44
+ this.traverse(ast);
45
+
46
+ return this.findings;
47
+ }
48
+
49
+ extractSolidityVersion(ast) {
50
+ for (const node of ast.children || []) {
51
+ if (node.type === 'PragmaDirective' && node.name === 'solidity') {
52
+ // Handle version ranges like ^0.8.0, >=0.8.0, 0.8.0
53
+ const versionPatterns = [
54
+ /(\d+)\.(\d+)\.(\d+)/, // Exact version
55
+ /\^(\d+)\.(\d+)\.(\d+)/, // Caret version
56
+ />=\s*(\d+)\.(\d+)\.(\d+)/, // Greater than or equal
57
+ /(\d+)\.(\d+)/ // Major.minor only
58
+ ];
59
+
60
+ for (const pattern of versionPatterns) {
61
+ const match = node.value.match(pattern);
62
+ if (match) {
63
+ this.solidityVersion = {
64
+ major: parseInt(match[1]),
65
+ minor: parseInt(match[2]),
66
+ patch: match[3] ? parseInt(match[3]) : 0
67
+ };
68
+ break;
69
+ }
70
+ }
71
+ }
72
+ }
73
+ }
74
+
75
+ isVersionBelow080() {
76
+ if (!this.solidityVersion) {
77
+ // Try to infer from code patterns
78
+ if (this.hasSafeMath) {
79
+ return true; // SafeMath typically means pre-0.8
80
+ }
81
+ return false; // Default to safe (0.8+) if unknown
82
+ }
83
+ return this.solidityVersion.major === 0 && this.solidityVersion.minor < 8;
84
+ }
85
+
86
+ visitContractDefinition(node) {
87
+ this.currentContract = node.name;
88
+ }
89
+
90
+ visitFunctionDefinition(node) {
91
+ this.currentFunction = node.name || 'constructor';
92
+ this.currentFunctionNode = node;
93
+
94
+ // Track function parameters for user input detection
95
+ this.functionParameters = new Set();
96
+ if (node.parameters) {
97
+ for (const param of node.parameters) {
98
+ if (param.name) {
99
+ this.functionParameters.add(param.name);
100
+ }
101
+ }
102
+ }
103
+ }
104
+
105
+ visitUncheckedStatement(node) {
106
+ // Mark that we're entering an unchecked block
107
+ this.uncheckedDepth++;
108
+ this.inUncheckedBlock = true;
109
+
110
+ // Analyze the unchecked block with context
111
+ this.analyzeUncheckedBlock(node);
112
+ }
113
+
114
+ /**
115
+ * Analyze unchecked block for actual vulnerabilities
116
+ */
117
+ analyzeUncheckedBlock(node) {
118
+ const code = this.getCodeSnippet(node.loc);
119
+
120
+ // Check if this is a safe unchecked pattern
121
+ const safePattern = this.identifySafeUncheckedPattern(code);
122
+
123
+ if (safePattern.isSafe) {
124
+ // Don't report - this is intentional and safe
125
+ return;
126
+ }
127
+
128
+ // Find arithmetic operations in the block
129
+ this.findArithmeticInBlock(node, code);
130
+
131
+ // Reset after processing
132
+ this.uncheckedDepth--;
133
+ if (this.uncheckedDepth === 0) {
134
+ this.inUncheckedBlock = false;
135
+ }
136
+ }
137
+
138
+ /**
139
+ * Identify safe unchecked patterns that are intentional
140
+ */
141
+ identifySafeUncheckedPattern(code) {
142
+ const result = { isSafe: false, reason: '' };
143
+
144
+ // Pattern 1: Loop counter increment (i++ in for loops)
145
+ // Counters are bounded by array length, so overflow is impossible
146
+ if (/\+\+\s*\}|i\s*\+\+|counter\s*\+\+/i.test(code) &&
147
+ code.split('\n').length <= 3) {
148
+ result.isSafe = true;
149
+ result.reason = 'Loop counter increment (bounded by iteration)';
150
+ return result;
151
+ }
152
+
153
+ // Pattern 2: Intentional wrapping arithmetic for circular buffers/counters
154
+ if (/circular|wrap|ring|mod|%/i.test(code)) {
155
+ result.isSafe = true;
156
+ result.reason = 'Intentional wrapping arithmetic';
157
+ return result;
158
+ }
159
+
160
+ // Pattern 3: Gas optimization for known-safe operations
161
+ // e.g., unchecked { balances[from] -= amount; balances[to] += amount; }
162
+ // where amount was already validated
163
+ const hasValidation = this.checkPriorValidation(code);
164
+ if (hasValidation) {
165
+ result.isSafe = true;
166
+ result.reason = 'Validated before unchecked operation';
167
+ return result;
168
+ }
169
+
170
+ // Pattern 4: Timestamp/block number differences (always positive, bounded)
171
+ if (/block\.timestamp|block\.number/i.test(code) &&
172
+ /-/.test(code) &&
173
+ !this.involvesUserInputInCode(code)) {
174
+ result.isSafe = true;
175
+ result.reason = 'Block timestamp/number difference (bounded)';
176
+ return result;
177
+ }
178
+
179
+ return result;
180
+ }
181
+
182
+ /**
183
+ * Check if there's validation before the unchecked block
184
+ */
185
+ checkPriorValidation(code) {
186
+ // Look for require/if statements before arithmetic
187
+ const funcCode = this.currentFunctionNode ?
188
+ this.getCodeSnippet(this.currentFunctionNode.loc) : code;
189
+
190
+ // Check for balance >= amount pattern before subtraction
191
+ if (/-=/.test(code)) {
192
+ const hasBalanceCheck = /require\s*\([^)]*>=|if\s*\([^)]*>=/i.test(funcCode);
193
+ if (hasBalanceCheck) return true;
194
+ }
195
+
196
+ // Check for overflow prevention require
197
+ const hasOverflowCheck = /require\s*\([^)]*\+[^)]*[<>]/i.test(funcCode);
198
+ if (hasOverflowCheck) return true;
199
+
200
+ return false;
201
+ }
202
+
203
+ /**
204
+ * Find arithmetic operations within a block
205
+ */
206
+ findArithmeticInBlock(node, blockCode) {
207
+ if (!node) return;
208
+
209
+ if (node.type === 'BinaryOperation') {
210
+ const vulnerableOps = ['+', '-', '*', '**'];
211
+ if (vulnerableOps.includes(node.operator)) {
212
+ this.analyzeUncheckedArithmetic(node, blockCode);
213
+ }
214
+ }
215
+
216
+ // Traverse children
217
+ for (const key in node) {
218
+ if (key === 'loc' || key === 'range') continue;
219
+ const child = node[key];
220
+ if (Array.isArray(child)) {
221
+ child.forEach(c => this.findArithmeticInBlock(c, blockCode));
222
+ } else if (child && typeof child === 'object' && child.type) {
223
+ this.findArithmeticInBlock(child, blockCode);
224
+ }
225
+ }
226
+ }
227
+
228
+ /**
229
+ * Analyze a specific arithmetic operation in unchecked block
230
+ */
231
+ analyzeUncheckedArithmetic(node, blockCode) {
232
+ const operator = node.operator;
233
+
234
+ // Check if operands involve user input
235
+ const leftIsUserInput = this.isUserControlled(node.left);
236
+ const rightIsUserInput = this.isUserControlled(node.right);
237
+ const hasUserInput = leftIsUserInput || rightIsUserInput;
238
+
239
+ // Assess risk based on operation and input source
240
+ const risk = this.assessOverflowRisk(operator, hasUserInput, node, blockCode);
241
+
242
+ if (risk.shouldReport) {
243
+ this.reportUncheckedOverflow(node, risk);
244
+ }
245
+ }
246
+
247
+ visitBinaryOperation(node) {
248
+ // Only check if in pre-0.8 code (outside of unchecked handling)
249
+ if (!this.isVersionBelow080()) return;
250
+ if (this.inUncheckedBlock) return; // Handled separately
251
+
252
+ const vulnerableOps = ['+', '-', '*', '**'];
253
+ if (!vulnerableOps.includes(node.operator)) return;
254
+
255
+ // Check if SafeMath is being used
256
+ if (this.hasSafeMath) {
257
+ // Check if this specific operation uses SafeMath
258
+ const code = this.getCodeSnippet(node.loc);
259
+ if (/\.add\(|\.sub\(|\.mul\(|\.div\(/i.test(code)) {
260
+ return; // Using SafeMath - safe
261
+ }
262
+ }
263
+
264
+ // Analyze the operation
265
+ const hasUserInput = this.isUserControlled(node.left) || this.isUserControlled(node.right);
266
+ const risk = this.assessOverflowRisk(node.operator, hasUserInput, node, '');
267
+
268
+ if (risk.shouldReport) {
269
+ this.reportLegacyOverflow(node, risk);
270
+ }
271
+ }
272
+
273
+ /**
274
+ * Check if a node represents user-controlled input
275
+ */
276
+ isUserControlled(node) {
277
+ if (!node) return false;
278
+
279
+ // Direct function parameter
280
+ if (node.type === 'Identifier') {
281
+ if (this.functionParameters.has(node.name)) {
282
+ return true;
283
+ }
284
+ // Common user-input naming patterns
285
+ const name = node.name.toLowerCase();
286
+ if (name.includes('amount') || name.includes('value') ||
287
+ name.includes('input') || name.includes('qty') ||
288
+ name.includes('quantity')) {
289
+ return true;
290
+ }
291
+ }
292
+
293
+ // msg.value
294
+ if (node.type === 'MemberAccess') {
295
+ if (node.expression && node.expression.name === 'msg') {
296
+ return node.memberName === 'value';
297
+ }
298
+ }
299
+
300
+ // Array/mapping access with user index
301
+ if (node.type === 'IndexAccess') {
302
+ return this.isUserControlled(node.index);
303
+ }
304
+
305
+ // Function call result (external data)
306
+ if (node.type === 'FunctionCall') {
307
+ const callCode = this.getCodeSnippet(node.loc);
308
+ // External calls returning values
309
+ if (/\.balanceOf\(|\.totalSupply\(|\.decimals\(/i.test(callCode)) {
310
+ return true; // External contract data
311
+ }
312
+ }
313
+
314
+ return false;
315
+ }
316
+
317
+ /**
318
+ * Check if code string contains user-controlled values
319
+ */
320
+ involvesUserInputInCode(code) {
321
+ const userInputPatterns = [
322
+ /msg\.value/,
323
+ /\(amount|Amount\)/,
324
+ /\(value|Value\)/,
325
+ /param|Param/,
326
+ /input|Input/,
327
+ ];
328
+ return userInputPatterns.some(p => p.test(code));
329
+ }
330
+
331
+ /**
332
+ * Assess the overflow risk of an operation
333
+ */
334
+ assessOverflowRisk(operator, hasUserInput, node, contextCode) {
335
+ const result = {
336
+ shouldReport: false,
337
+ severity: 'MEDIUM',
338
+ confidence: 'MEDIUM',
339
+ reason: '',
340
+ exploitabilityScore: 50
341
+ };
342
+
343
+ // Multiplication with user input - highest risk
344
+ if (operator === '*' && hasUserInput) {
345
+ result.shouldReport = true;
346
+ result.severity = 'CRITICAL';
347
+ result.confidence = 'HIGH';
348
+ result.reason = 'Multiplication with user-controlled value can easily overflow';
349
+ result.exploitabilityScore = 85;
350
+ return result;
351
+ }
352
+
353
+ // Exponentiation - always risky with any variable
354
+ if (operator === '**') {
355
+ result.shouldReport = true;
356
+ result.severity = 'HIGH';
357
+ result.confidence = 'HIGH';
358
+ result.reason = 'Exponentiation grows extremely fast and can overflow with small inputs';
359
+ result.exploitabilityScore = 80;
360
+ return result;
361
+ }
362
+
363
+ // Addition with user input
364
+ if (operator === '+' && hasUserInput) {
365
+ result.shouldReport = true;
366
+ result.severity = 'HIGH';
367
+ result.confidence = 'MEDIUM';
368
+ result.reason = 'Addition with user-controlled value can overflow near uint max';
369
+ result.exploitabilityScore = 70;
370
+ return result;
371
+ }
372
+
373
+ // Subtraction - underflow risk
374
+ if (operator === '-') {
375
+ if (hasUserInput) {
376
+ result.shouldReport = true;
377
+ result.severity = 'HIGH';
378
+ result.confidence = 'HIGH';
379
+ result.reason = 'Subtraction can underflow if user provides value larger than minuend';
380
+ result.exploitabilityScore = 75;
381
+ return result;
382
+ }
383
+ // Even without direct user input, subtraction is risky
384
+ result.shouldReport = true;
385
+ result.severity = 'MEDIUM';
386
+ result.confidence = 'MEDIUM';
387
+ result.reason = 'Subtraction can underflow if not properly bounded';
388
+ result.exploitabilityScore = 50;
389
+ return result;
390
+ }
391
+
392
+ // Multiplication without direct user input - lower confidence
393
+ if (operator === '*') {
394
+ result.shouldReport = true;
395
+ result.severity = 'MEDIUM';
396
+ result.confidence = 'LOW';
397
+ result.reason = 'Multiplication may overflow with large values';
398
+ result.exploitabilityScore = 40;
399
+ return result;
400
+ }
401
+
402
+ return result;
403
+ }
404
+
405
+ reportUncheckedOverflow(node, risk) {
406
+ this.addFinding({
407
+ title: 'Unchecked Arithmetic Overflow/Underflow',
408
+ description: `Arithmetic operation '${node.operator}' in unchecked block may overflow or underflow. ${risk.reason}. Unchecked blocks bypass Solidity 0.8+ overflow protection.`,
409
+ location: `Contract: ${this.currentContract}, Function: ${this.currentFunction}`,
410
+ line: node.loc ? node.loc.start.line : 0,
411
+ column: node.loc ? node.loc.start.column : 0,
412
+ code: this.getCodeSnippet(node.loc),
413
+ severity: risk.severity,
414
+ confidence: risk.confidence,
415
+ exploitable: true,
416
+ exploitabilityScore: risk.exploitabilityScore,
417
+ attackVector: node.operator === '-' ? 'integer-underflow' : 'integer-overflow',
418
+ recommendation: 'Add explicit bounds validation before the unchecked arithmetic, or remove the unchecked block if overflow protection is needed. Example: require(a + b >= a) for addition.',
419
+ references: [
420
+ 'https://swcregistry.io/docs/SWC-101',
421
+ 'https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic'
422
+ ],
423
+ foundryPoC: risk.exploitabilityScore >= 70 ? this.generateOverflowPoC(node.operator) : undefined
424
+ });
425
+ }
426
+
427
+ reportLegacyOverflow(node, risk) {
428
+ this.addFinding({
429
+ title: 'Integer Overflow/Underflow (Pre-0.8 Solidity)',
430
+ description: `Arithmetic operation '${node.operator}' in Solidity < 0.8 without SafeMath. ${risk.reason}. Pre-0.8 Solidity has no built-in overflow protection.`,
431
+ location: `Contract: ${this.currentContract}, Function: ${this.currentFunction}`,
432
+ line: node.loc ? node.loc.start.line : 0,
433
+ column: node.loc ? node.loc.start.column : 0,
434
+ code: this.getCodeSnippet(node.loc),
435
+ severity: risk.severity,
436
+ confidence: risk.confidence,
437
+ exploitable: true,
438
+ exploitabilityScore: risk.exploitabilityScore,
439
+ attackVector: node.operator === '-' ? 'integer-underflow' : 'integer-overflow',
440
+ recommendation: 'Upgrade to Solidity 0.8.0+ for built-in overflow checks, OR use OpenZeppelin SafeMath library for ALL arithmetic operations.',
441
+ references: [
442
+ 'https://swcregistry.io/docs/SWC-101',
443
+ 'https://docs.openzeppelin.com/contracts/4.x/api/utils#SafeMath'
444
+ ],
445
+ foundryPoC: risk.exploitabilityScore >= 70 ? this.generateOverflowPoC(node.operator) : undefined
446
+ });
447
+ }
448
+
449
+ generateOverflowPoC(operator) {
450
+ const isUnderflow = operator === '-';
451
+ const title = isUnderflow ? 'Integer Underflow' : 'Integer Overflow';
452
+ const exploit = isUnderflow ?
453
+ `// Underflow: 0 - 1 = MAX_UINT256
454
+ uint256 balance = 0;
455
+ uint256 amount = 1;
456
+ // After: balance = 115792089237316195423570985008687907853269984665640564039457584007913129639935` :
457
+ `// Overflow: MAX_UINT256 + 1 = 0
458
+ uint256 balance = type(uint256).max;
459
+ uint256 amount = 1;
460
+ // After: balance = 0`;
461
+
462
+ return `// SPDX-License-Identifier: MIT
463
+ pragma solidity ^0.8.0;
464
+
465
+ import "forge-std/Test.sol";
466
+
467
+ /**
468
+ * Proof of Concept: ${title}
469
+ */
470
+ contract IntegerOverflowExploit is Test {
471
+ address constant TARGET = address(0);
472
+
473
+ function testExploit() public {
474
+ ${exploit}
475
+
476
+ // Call vulnerable function with crafted input
477
+ // TARGET.vulnerableFunction(amount);
478
+
479
+ // Verify exploitation:
480
+ // - Balance check bypassed OR
481
+ // - Received more tokens than should be possible
482
+ }
483
+ }`;
484
+ }
485
+ }
486
+
487
+ module.exports = IntegerOverflowDetector;