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,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;
|