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
package/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "web3crit-scanner",
|
|
3
|
+
"version": "7.0.1",
|
|
4
|
+
"description": "Top-tier exploit-driven DeFi scanner for Immunefi High/Critical payouts. Models flash loans, MEV, and adversarial capabilities.",
|
|
5
|
+
"main": "src/scanner-enhanced.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"web3crit": "./bin/web3crit"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"test": "node test/test.js",
|
|
11
|
+
"install-global": "npm install -g .",
|
|
12
|
+
"scan": "node src/cli.js scan",
|
|
13
|
+
"scan:immunefi": "node src/cli.js scan --immunefi-only"
|
|
14
|
+
},
|
|
15
|
+
"preferGlobal": true,
|
|
16
|
+
"files": [
|
|
17
|
+
"bin/",
|
|
18
|
+
"src/",
|
|
19
|
+
"README.md",
|
|
20
|
+
"INSTALL.md"
|
|
21
|
+
],
|
|
22
|
+
"keywords": [
|
|
23
|
+
"solidity",
|
|
24
|
+
"security",
|
|
25
|
+
"audit",
|
|
26
|
+
"vulnerability",
|
|
27
|
+
"smart-contract",
|
|
28
|
+
"ethereum",
|
|
29
|
+
"defi",
|
|
30
|
+
"reentrancy",
|
|
31
|
+
"flash-loan",
|
|
32
|
+
"immunefi",
|
|
33
|
+
"bug-bounty",
|
|
34
|
+
"exploit",
|
|
35
|
+
"governance",
|
|
36
|
+
"vault",
|
|
37
|
+
"static-analysis"
|
|
38
|
+
],
|
|
39
|
+
"author": "critfinds <jsbtc1@proton.me>",
|
|
40
|
+
"license": "MIT",
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"@solidity-parser/parser": "^0.20.2",
|
|
43
|
+
"commander": "^9.4.1",
|
|
44
|
+
"chalk": "^4.1.2",
|
|
45
|
+
"ora": "^5.4.1",
|
|
46
|
+
"boxen": "^5.1.2",
|
|
47
|
+
"cli-table3": "^0.6.3",
|
|
48
|
+
"gradient-string": "^2.0.2",
|
|
49
|
+
"figures": "^3.2.0"
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {},
|
|
52
|
+
"engines": {
|
|
53
|
+
"node": ">=14.0.0"
|
|
54
|
+
},
|
|
55
|
+
"repository": {
|
|
56
|
+
"type": "git",
|
|
57
|
+
"url": "https://github.com/critfinds/Web3CRIT-Scanner"
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
const parser = require('@solidity-parser/parser');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Control Flow Graph (CFG) Analyzer (Rewritten)
|
|
5
|
+
* Tracks execution paths, function calls, and state changes using the official parser visitor
|
|
6
|
+
*/
|
|
7
|
+
class ControlFlowAnalyzer {
|
|
8
|
+
constructor() {
|
|
9
|
+
this.reset();
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
analyze(ast, sourceCode) {
|
|
13
|
+
this.sourceCode = sourceCode;
|
|
14
|
+
this.reset();
|
|
15
|
+
|
|
16
|
+
let currentContract = null;
|
|
17
|
+
let currentFunction = null;
|
|
18
|
+
let currentModifier = null;
|
|
19
|
+
|
|
20
|
+
parser.visit(ast, {
|
|
21
|
+
ContractDefinition: node => {
|
|
22
|
+
currentContract = {
|
|
23
|
+
name: node.name,
|
|
24
|
+
kind: node.kind,
|
|
25
|
+
baseContracts: node.baseContracts || [],
|
|
26
|
+
stateVariables: [],
|
|
27
|
+
functions: [],
|
|
28
|
+
modifiers: []
|
|
29
|
+
};
|
|
30
|
+
this.contracts.set(node.name, currentContract);
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
'ContractDefinition:exit': () => {
|
|
34
|
+
currentContract = null;
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
ModifierDefinition: node => {
|
|
38
|
+
if (!currentContract) return;
|
|
39
|
+
const modKey = `${currentContract.name}.${node.name}`;
|
|
40
|
+
currentModifier = {
|
|
41
|
+
name: node.name,
|
|
42
|
+
contract: currentContract.name,
|
|
43
|
+
node: node,
|
|
44
|
+
requireStatements: [],
|
|
45
|
+
checksAccess: false,
|
|
46
|
+
checksOwnership: false,
|
|
47
|
+
checksRole: false
|
|
48
|
+
};
|
|
49
|
+
this.modifiers.set(modKey, currentModifier);
|
|
50
|
+
currentContract.modifiers.push(node.name);
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
'ModifierDefinition:exit': () => {
|
|
54
|
+
// Analyze collected require statements to determine access control type
|
|
55
|
+
if (currentModifier) {
|
|
56
|
+
this.analyzeModifierAccessControl(currentModifier);
|
|
57
|
+
}
|
|
58
|
+
currentModifier = null;
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
FunctionDefinition: node => {
|
|
62
|
+
if (!currentContract) return;
|
|
63
|
+
const funcName = node.name || (node.isConstructor ? 'constructor' : (node.isReceiveEther ? 'receive' : 'fallback'));
|
|
64
|
+
const funcKey = `${currentContract.name}.${funcName}`;
|
|
65
|
+
currentFunction = {
|
|
66
|
+
name: funcName,
|
|
67
|
+
contract: currentContract.name,
|
|
68
|
+
visibility: node.visibility || 'public',
|
|
69
|
+
stateMutability: node.stateMutability,
|
|
70
|
+
modifiers: (node.modifiers || []).map(m => m.name),
|
|
71
|
+
parameters: (node.parameters || []).map(p => ({
|
|
72
|
+
name: p.name,
|
|
73
|
+
type: this.getTypeName(p.typeName)
|
|
74
|
+
})),
|
|
75
|
+
isConstructor: node.isConstructor,
|
|
76
|
+
isFallback: !node.name && !node.isConstructor && !node.isReceiveEther,
|
|
77
|
+
isReceive: node.isReceiveEther,
|
|
78
|
+
externalCalls: [],
|
|
79
|
+
stateWrites: [],
|
|
80
|
+
stateReads: [],
|
|
81
|
+
node: node
|
|
82
|
+
};
|
|
83
|
+
this.functions.set(funcKey, currentFunction);
|
|
84
|
+
currentContract.functions.push(funcName);
|
|
85
|
+
},
|
|
86
|
+
|
|
87
|
+
'FunctionDefinition:exit': () => {
|
|
88
|
+
currentFunction = null;
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
StateVariableDeclaration: node => {
|
|
92
|
+
if (!currentContract) return;
|
|
93
|
+
node.variables.forEach(variable => {
|
|
94
|
+
const varInfo = {
|
|
95
|
+
name: variable.name,
|
|
96
|
+
type: this.getTypeName(variable.typeName),
|
|
97
|
+
visibility: variable.visibility || 'internal',
|
|
98
|
+
isConstant: variable.isDeclaredConst,
|
|
99
|
+
isImmutable: variable.isImmutable,
|
|
100
|
+
contract: currentContract.name
|
|
101
|
+
};
|
|
102
|
+
this.stateVariables.set(`${currentContract.name}.${variable.name}`, varInfo);
|
|
103
|
+
currentContract.stateVariables.push(variable.name);
|
|
104
|
+
});
|
|
105
|
+
},
|
|
106
|
+
|
|
107
|
+
FunctionCall: node => {
|
|
108
|
+
// Track require/assert statements in modifiers
|
|
109
|
+
if (currentModifier && node.expression && node.expression.type === 'Identifier') {
|
|
110
|
+
const funcName = node.expression.name;
|
|
111
|
+
if (funcName === 'require' || funcName === 'assert') {
|
|
112
|
+
const requireCode = this.getSourceFromNode(node);
|
|
113
|
+
currentModifier.requireStatements.push(requireCode);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (!currentFunction) return;
|
|
118
|
+
|
|
119
|
+
// Handle direct MemberAccess: .send(), .transfer()
|
|
120
|
+
if (node.expression && node.expression.type === 'MemberAccess') {
|
|
121
|
+
const memberName = node.expression.memberName;
|
|
122
|
+
if (['call', 'delegatecall', 'staticcall', 'send', 'transfer'].includes(memberName)) {
|
|
123
|
+
currentFunction.externalCalls.push({
|
|
124
|
+
type: memberName,
|
|
125
|
+
target: this.getSourceFromNode(node.expression.expression),
|
|
126
|
+
loc: node.loc
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Handle NameValueExpression: .call{value: x}(), .call{gas: x}()
|
|
132
|
+
if (node.expression && node.expression.type === 'NameValueExpression') {
|
|
133
|
+
const innerExpr = node.expression.expression;
|
|
134
|
+
if (innerExpr && innerExpr.type === 'MemberAccess') {
|
|
135
|
+
const memberName = innerExpr.memberName;
|
|
136
|
+
if (['call', 'delegatecall', 'staticcall'].includes(memberName)) {
|
|
137
|
+
currentFunction.externalCalls.push({
|
|
138
|
+
type: memberName,
|
|
139
|
+
target: this.getSourceFromNode(innerExpr.expression),
|
|
140
|
+
loc: node.loc
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
|
|
147
|
+
Identifier: node => {
|
|
148
|
+
// Track state variable reads
|
|
149
|
+
if (currentFunction && currentContract && this.isStateVariable(node, currentContract.name)) {
|
|
150
|
+
currentFunction.stateReads.push({
|
|
151
|
+
variable: node.name,
|
|
152
|
+
loc: node.loc
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
|
|
157
|
+
BinaryOperation: node => {
|
|
158
|
+
if (!currentFunction || !currentContract) return;
|
|
159
|
+
// Track state writes for assignment operators
|
|
160
|
+
if (node.operator === '=' || node.operator === '+=' || node.operator === '-=' ||
|
|
161
|
+
node.operator === '*=' || node.operator === '/=') {
|
|
162
|
+
if (this.isStateVariable(node.left, currentContract.name)) {
|
|
163
|
+
currentFunction.stateWrites.push({
|
|
164
|
+
variable: this.getVariableName(node.left),
|
|
165
|
+
loc: node.loc
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
return {
|
|
173
|
+
contracts: this.contracts,
|
|
174
|
+
functions: this.functions,
|
|
175
|
+
modifiers: this.modifiers,
|
|
176
|
+
stateVariables: this.stateVariables
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Analyze modifier require statements to determine what kind of access control it provides
|
|
182
|
+
*/
|
|
183
|
+
analyzeModifierAccessControl(modInfo) {
|
|
184
|
+
const allRequires = modInfo.requireStatements.join(' ').toLowerCase();
|
|
185
|
+
|
|
186
|
+
// Check for ownership patterns
|
|
187
|
+
if (allRequires.includes('msg.sender') &&
|
|
188
|
+
(allRequires.includes('owner') || allRequires.includes('admin'))) {
|
|
189
|
+
modInfo.checksOwnership = true;
|
|
190
|
+
modInfo.checksAccess = true;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Check for role-based patterns
|
|
194
|
+
if (allRequires.includes('hasrole') || allRequires.includes('role') ||
|
|
195
|
+
allRequires.includes('isauthorized') || allRequires.includes('onlyrole')) {
|
|
196
|
+
modInfo.checksRole = true;
|
|
197
|
+
modInfo.checksAccess = true;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Check for general access control patterns
|
|
201
|
+
if (allRequires.includes('msg.sender') || allRequires.includes('tx.origin')) {
|
|
202
|
+
modInfo.checksAccess = true;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Check for reentrancy guard patterns
|
|
206
|
+
if (allRequires.includes('_status') || allRequires.includes('locked') ||
|
|
207
|
+
allRequires.includes('_notentered') || allRequires.includes('reentrancy')) {
|
|
208
|
+
modInfo.checksReentrancy = true;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Check modifier name for hints
|
|
212
|
+
const modNameLower = modInfo.name.toLowerCase();
|
|
213
|
+
if (modNameLower.includes('onlyowner') || modNameLower.includes('onlyadmin')) {
|
|
214
|
+
modInfo.checksOwnership = true;
|
|
215
|
+
modInfo.checksAccess = true;
|
|
216
|
+
}
|
|
217
|
+
if (modNameLower.includes('nonreentrant') || modNameLower.includes('lock')) {
|
|
218
|
+
modInfo.checksReentrancy = true;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
reset() {
|
|
223
|
+
this.contracts = new Map();
|
|
224
|
+
this.functions = new Map();
|
|
225
|
+
this.modifiers = new Map();
|
|
226
|
+
this.stateVariables = new Map();
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
isStateVariable(node, currentContract) {
|
|
230
|
+
const varName = this.getVariableName(node);
|
|
231
|
+
return this.stateVariables.has(`${currentContract}.${varName}`);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
getVariableName(node) {
|
|
235
|
+
if (node.type === 'Identifier') return node.name;
|
|
236
|
+
if (node.type === 'MemberAccess') return node.memberName;
|
|
237
|
+
if (node.type === 'IndexAccess') return this.getVariableName(node.base);
|
|
238
|
+
return null;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
getSourceFromNode(node) {
|
|
242
|
+
if (!node || !node.range) return '';
|
|
243
|
+
return this.sourceCode.substring(node.range[0], node.range[1] + 1);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
getTypeName(typeNode) {
|
|
248
|
+
if (!typeNode) return 'unknown';
|
|
249
|
+
if (typeNode.type === 'ElementaryTypeName') return typeNode.name;
|
|
250
|
+
if (typeNode.type === 'UserDefinedTypeName') return typeNode.namePath;
|
|
251
|
+
if (typeNode.type === 'Mapping') return 'mapping';
|
|
252
|
+
return 'complex';
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
module.exports = ControlFlowAnalyzer;
|