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,427 @@
1
+ const BaseDetector = require('./base-detector');
2
+
3
+ /**
4
+ * Time-of-Check to Time-of-Use (TOCTOU) Detector
5
+ * Detects race conditions where contract state changes between a check and its use
6
+ *
7
+ * Detects:
8
+ * - Balance checks before transfers
9
+ * - Allowance checks before transfers
10
+ * - State checks before state-dependent operations
11
+ * - External calls between check and use
12
+ * - Reentrancy-like patterns with state checks
13
+ */
14
+ class TOCTOUDetector extends BaseDetector {
15
+ constructor() {
16
+ super(
17
+ 'Time-of-Check to Time-of-Use (TOCTOU) Vulnerability',
18
+ 'Detects race conditions where state changes between check and use',
19
+ 'HIGH'
20
+ );
21
+ this.currentContract = null;
22
+ this.currentFunction = null;
23
+ this.checkUsePairs = []; // Array of {check, use, hasExternalCall}
24
+ this.cfg = null;
25
+ this.dataFlow = null;
26
+ }
27
+
28
+ async detect(ast, sourceCode, fileName, cfg, dataFlow) {
29
+ this.findings = [];
30
+ this.ast = ast;
31
+ this.sourceCode = sourceCode;
32
+ this.fileName = fileName;
33
+ this.sourceLines = sourceCode.split('\n');
34
+ this.cfg = cfg;
35
+ this.dataFlow = dataFlow;
36
+
37
+ this.traverse(ast);
38
+
39
+ // Post-traversal analysis
40
+ this.analyzeTOCTOUPatterns();
41
+
42
+ return this.findings;
43
+ }
44
+
45
+ visitContractDefinition(node) {
46
+ this.currentContract = node.name;
47
+ this.checkUsePairs = [];
48
+ }
49
+
50
+ visitFunctionDefinition(node) {
51
+ this.currentFunction = node.name || '';
52
+
53
+ if (!node.body || !node.body.statements) return;
54
+
55
+ // Skip private/internal functions
56
+ if (node.visibility === 'private' || node.visibility === 'internal') {
57
+ return;
58
+ }
59
+
60
+ // Analyze function for TOCTOU patterns
61
+ this.analyzeFunctionForTOCTOU(node);
62
+ }
63
+
64
+ /**
65
+ * Analyze function for TOCTOU vulnerabilities
66
+ */
67
+ analyzeFunctionForTOCTOU(node) {
68
+ if (!node.body || !node.body.statements) return;
69
+
70
+ const statements = node.body.statements;
71
+ const checks = []; // Array of check statements
72
+ const uses = []; // Array of use statements
73
+ const externalCalls = []; // Array of external calls
74
+
75
+ for (let i = 0; i < statements.length; i++) {
76
+ const stmt = statements[i];
77
+ const stmtCode = this.getCodeSnippet(stmt.loc);
78
+ const stmtCodeLower = stmtCode.toLowerCase();
79
+
80
+ // Detect check patterns
81
+ if (this.isCheckPattern(stmt, stmtCode)) {
82
+ checks.push({
83
+ statement: stmt,
84
+ code: stmtCode,
85
+ index: i,
86
+ type: this.getCheckType(stmtCode)
87
+ });
88
+ }
89
+
90
+ // Detect use patterns
91
+ if (this.isUsePattern(stmt, stmtCode)) {
92
+ uses.push({
93
+ statement: stmt,
94
+ code: stmtCode,
95
+ index: i,
96
+ type: this.getUseType(stmtCode)
97
+ });
98
+ }
99
+
100
+ // Detect external calls
101
+ if (this.isExternalCall(stmt, stmtCode)) {
102
+ externalCalls.push({
103
+ statement: stmt,
104
+ code: stmtCode,
105
+ index: i
106
+ });
107
+ }
108
+ }
109
+
110
+ // Find check-use pairs with external calls in between
111
+ this.findTOCTOUPatterns(checks, uses, externalCalls, node);
112
+ }
113
+
114
+ /**
115
+ * Check if statement is a check pattern
116
+ */
117
+ isCheckPattern(stmt, code) {
118
+ const codeLower = code.toLowerCase();
119
+
120
+ // Balance checks - look for balance reads/assignments that are used for checks
121
+ const balanceChecks = [
122
+ /balanceOf\s*\(/i,
123
+ /balances\[/i,
124
+ /\.balance\s*>/i,
125
+ /\.balance\s*>=/i,
126
+ /\.balance\s*</i,
127
+ /\.balance\s*<=/i,
128
+ /uint256\s+\w*balance/i, // uint256 balance = ...
129
+ /require\s*\(\s*.*balance/i,
130
+ /if\s*\(\s*.*balance/i
131
+ ];
132
+
133
+ // Allowance checks
134
+ const allowanceChecks = [
135
+ /allowance\s*\(/i,
136
+ /allowance\[/i,
137
+ /uint256\s+\w*allowed/i, // uint256 allowed = ...
138
+ /require\s*\(\s*.*allowance/i,
139
+ /if\s*\(\s*.*allowance/i
140
+ ];
141
+
142
+ // State variable checks
143
+ const stateChecks = [
144
+ /require\s*\(\s*.*==/i,
145
+ /require\s*\(\s*.*!=/i,
146
+ /if\s*\(\s*.*==/i,
147
+ /if\s*\(\s*.*!=/i
148
+ ];
149
+
150
+ // Owner/role checks
151
+ const accessChecks = [
152
+ /require\s*\(\s*.*owner/i,
153
+ /require\s*\(\s*.*role/i,
154
+ /require\s*\(\s*.*hasRole/i
155
+ ];
156
+
157
+ const allChecks = [...balanceChecks, ...allowanceChecks, ...stateChecks, ...accessChecks];
158
+
159
+ return allChecks.some(pattern => pattern.test(code));
160
+ }
161
+
162
+ /**
163
+ * Get type of check
164
+ */
165
+ getCheckType(code) {
166
+ const codeLower = code.toLowerCase();
167
+ // Check for balance reads (including variable assignments)
168
+ if (codeLower.includes('balance') || codeLower.includes('balances[')) return 'balance';
169
+ // Check for allowance reads
170
+ if (codeLower.includes('allowance') || codeLower.includes('allowed')) return 'allowance';
171
+ if (codeLower.includes('owner') || codeLower.includes('role')) return 'access';
172
+ return 'state';
173
+ }
174
+
175
+ /**
176
+ * Check if statement is a use pattern (state modification)
177
+ */
178
+ isUsePattern(stmt, code) {
179
+ const codeLower = code.toLowerCase();
180
+
181
+ // State modifications - assignments to state variables
182
+ const stateModPatterns = [
183
+ /balances\[.*\]\s*=/i, // balances[user] = ...
184
+ /balances\[.*\]\s*-=/i, // balances[user] -= ...
185
+ /balances\[.*\]\s*\+=/i, // balances[user] += ...
186
+ /allowance\[.*\]\s*=/i, // allowance[from][spender] = ...
187
+ /allowance\[.*\]\s*-=/i, // allowance[from][spender] -= ...
188
+ /mapping\[.*\]\s*=/i, // mapping assignments
189
+ /\w+\s*=\s*[^=]/i, // Variable assignment (but not ==)
190
+ /\+\+/i, // Increment
191
+ /--/i // Decrement
192
+ ];
193
+
194
+ // Check if it's an assignment statement
195
+ if (stmt.type === 'ExpressionStatement' && stmt.expression) {
196
+ const expr = stmt.expression;
197
+ if (expr.type === 'Assignment') {
198
+ // This is an assignment - check if it's a state variable
199
+ const leftSide = this.getCodeSnippet(expr.left ? expr.left.loc : null);
200
+ if (leftSide && (leftSide.includes('balances') || leftSide.includes('allowance') || leftSide.includes('mapping'))) {
201
+ return true;
202
+ }
203
+ }
204
+ }
205
+
206
+ // Check patterns in code
207
+ return stateModPatterns.some(pattern => pattern.test(code));
208
+ }
209
+
210
+ /**
211
+ * Get type of use
212
+ */
213
+ getUseType(code) {
214
+ const codeLower = code.toLowerCase();
215
+ // Check for transfer operations
216
+ if (codeLower.includes('transfer') || codeLower.includes('call{value') || codeLower.includes('send')) return 'transfer';
217
+ // Check for state modifications to balances or allowance
218
+ if (codeLower.includes('balances[') && (codeLower.includes('=') || codeLower.includes('-=') || codeLower.includes('+='))) return 'transfer';
219
+ if (codeLower.includes('allowance[') && (codeLower.includes('=') || codeLower.includes('-='))) return 'transfer';
220
+ if (codeLower.includes('mint') || codeLower.includes('burn')) return 'mintburn';
221
+ return 'state';
222
+ }
223
+
224
+ /**
225
+ * Check if statement is an external call
226
+ */
227
+ isExternalCall(stmt, code) {
228
+ const codeLower = code.toLowerCase();
229
+
230
+ const callPatterns = [
231
+ /\.call\s*\(/i,
232
+ /\.delegatecall\s*\(/i,
233
+ /\.send\s*\(/i,
234
+ /\.transfer\s*\(/i,
235
+ /external\s+contract/i
236
+ ];
237
+
238
+ return callPatterns.some(pattern => pattern.test(code));
239
+ }
240
+
241
+ /**
242
+ * Find TOCTOU patterns (check -> external call -> use)
243
+ */
244
+ findTOCTOUPatterns(checks, uses, externalCalls, node) {
245
+ // For each check-use pair, see if there's an external call in between
246
+ for (const check of checks) {
247
+ for (const use of uses) {
248
+ // Use must come after check
249
+ if (use.index <= check.index) continue;
250
+
251
+ // Check if there's an external call between check and use
252
+ const hasExternalCall = externalCalls.some(call =>
253
+ call.index > check.index && call.index < use.index
254
+ );
255
+
256
+ // Check if check and use are related (same variable/operation)
257
+ if (this.areRelated(check, use)) {
258
+ this.checkUsePairs.push({
259
+ check: check,
260
+ use: use,
261
+ hasExternalCall: hasExternalCall,
262
+ function: this.currentFunction,
263
+ node: node
264
+ });
265
+
266
+ // If there's an external call, this is a TOCTOU vulnerability
267
+ if (hasExternalCall) {
268
+ this.reportTOCTOU(check, use, node);
269
+ }
270
+ }
271
+ }
272
+ }
273
+ }
274
+
275
+ /**
276
+ * Check if check and use are related
277
+ */
278
+ areRelated(check, use) {
279
+ const checkCode = check.code.toLowerCase();
280
+ const useCode = use.code.toLowerCase();
281
+
282
+ // Balance check -> transfer or balance state modification
283
+ if (check.type === 'balance' && (use.type === 'transfer' || useCode.includes('balances['))) {
284
+ return true;
285
+ }
286
+
287
+ // Allowance check -> transfer or allowance state modification
288
+ if (check.type === 'allowance' && (use.type === 'transfer' || useCode.includes('allowance['))) {
289
+ return true;
290
+ }
291
+
292
+ // State check -> state modification
293
+ if (check.type === 'state' && use.type === 'state') {
294
+ // Check if they reference the same variable
295
+ const checkVar = this.extractVariable(check.code);
296
+ const useVar = this.extractVariable(use.code);
297
+ return checkVar && useVar && checkVar === useVar;
298
+ }
299
+
300
+ return false;
301
+ }
302
+
303
+ /**
304
+ * Extract variable name from code
305
+ */
306
+ extractVariable(code) {
307
+ // Simple extraction - look for common patterns
308
+ const patterns = [
309
+ /(\w+)\s*\.balance/i,
310
+ /balanceOf\s*\(\s*(\w+)/i,
311
+ /allowance\s*\(\s*(\w+)/i,
312
+ /(\w+)\s*==/i,
313
+ /(\w+)\s*!=/i
314
+ ];
315
+
316
+ for (const pattern of patterns) {
317
+ const match = code.match(pattern);
318
+ if (match && match[1]) {
319
+ return match[1];
320
+ }
321
+ }
322
+
323
+ return null;
324
+ }
325
+
326
+ /**
327
+ * Report TOCTOU vulnerability
328
+ */
329
+ reportTOCTOU(check, use, node) {
330
+ const funcName = this.currentFunction;
331
+ const checkType = check.type;
332
+ const useType = use.type;
333
+
334
+ let title, description, recommendation;
335
+
336
+ if (checkType === 'balance' && useType === 'transfer') {
337
+ title = 'TOCTOU: Balance Check Before Transfer';
338
+ description = `Function '${funcName}' checks balance before transfer, but an external call occurs between the check and use. An attacker can manipulate the balance during the external call, causing the transfer to use stale balance information.`;
339
+ recommendation = 'Cache balance value before external calls. Use Checks-Effects-Interactions pattern: update state first, then make external calls.';
340
+ } else if (checkType === 'allowance' && useType === 'transfer') {
341
+ title = 'TOCTOU: Allowance Check Before Transfer';
342
+ description = `Function '${funcName}' checks allowance before transferFrom, but an external call occurs between the check and use. An attacker can reduce allowance during the external call, causing the transfer to fail or use incorrect allowance.`;
343
+ recommendation = 'Cache allowance value before external calls. Consider using increaseAllowance/decreaseAllowance pattern instead of direct allowance checks.';
344
+ } else {
345
+ title = 'TOCTOU: State Check Before Use';
346
+ description = `Function '${funcName}' checks state before using it, but an external call occurs between the check and use. The state may change during the external call, leading to inconsistent behavior.`;
347
+ recommendation = 'Cache state values before external calls. Update state before making external calls when possible.';
348
+ }
349
+
350
+ this.addFinding({
351
+ title: title,
352
+ description: description,
353
+ location: `Contract: ${this.currentContract}, Function: ${funcName}`,
354
+ line: check.statement.loc ? check.statement.loc.start.line : 0,
355
+ column: check.statement.loc ? check.statement.loc.start.column : 0,
356
+ code: `${check.code}\n...\n${use.code}`,
357
+ severity: 'HIGH',
358
+ confidence: 'HIGH',
359
+ exploitable: true,
360
+ exploitabilityScore: 80,
361
+ attackVector: 'toctou',
362
+ recommendation: recommendation,
363
+ references: [
364
+ 'https://swcregistry.io/docs/SWC-107',
365
+ 'https://consensys.github.io/smart-contract-best-practices/attacks/reentrancy/',
366
+ 'https://fravoll.github.io/solidity-patterns/checks_effects_interactions.html'
367
+ ],
368
+ foundryPoC: this.generateTOCTOUPoC(this.currentContract, funcName, checkType, useType)
369
+ });
370
+ }
371
+
372
+ /**
373
+ * Post-traversal analysis
374
+ */
375
+ analyzeTOCTOUPatterns() {
376
+ // Additional analysis can be done here
377
+ // For example, finding patterns across multiple functions
378
+ }
379
+
380
+ /**
381
+ * Generate Foundry PoC for TOCTOU
382
+ */
383
+ generateTOCTOUPoC(contractName, funcName, checkType, useType) {
384
+ return `// SPDX-License-Identifier: MIT
385
+ pragma solidity ^0.8.0;
386
+
387
+ import "forge-std/Test.sol";
388
+
389
+ /**
390
+ * Proof of Concept: TOCTOU Attack
391
+ * Target: ${contractName}.${funcName}()
392
+ * Attack Vector: State changes between check and use
393
+ */
394
+ contract TOCTOUExploit is Test {
395
+ address constant TARGET = address(0); // ${contractName} address
396
+ AttackerContract attacker;
397
+
398
+ function setUp() public {
399
+ attacker = new AttackerContract();
400
+ }
401
+
402
+ function testExploit() public {
403
+ // 1. Setup: Attacker has some balance/allowance
404
+ // 2. Call vulnerable function which:
405
+ // - Checks balance/allowance (${checkType})
406
+ // - Makes external call (attacker's callback)
407
+ // - Uses cached/stale value for ${useType}
408
+
409
+ // 3. In callback, attacker manipulates state
410
+ // 4. Function continues with stale check value
411
+
412
+ // Assert exploit succeeded
413
+ // assertGt(attacker.balance, initialBalance);
414
+ }
415
+ }
416
+
417
+ contract AttackerContract {
418
+ function onCallback() external {
419
+ // Manipulate state that was checked earlier
420
+ // This changes the state between check and use
421
+ }
422
+ }`;
423
+ }
424
+ }
425
+
426
+ module.exports = TOCTOUDetector;
427
+