muaddib-scanner 1.0.0

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 (41) hide show
  1. package/.github/workflows/scan.yml +33 -0
  2. package/LICENSE +21 -0
  3. package/MUADDIBLOGO.png +0 -0
  4. package/README.md +218 -0
  5. package/action/action.yml +28 -0
  6. package/bin/muaddib.js +84 -0
  7. package/data/iocs.json +38 -0
  8. package/docs/threat-model.md +116 -0
  9. package/iocs/hashes.yaml +220 -0
  10. package/iocs/packages.yaml +265 -0
  11. package/package.json +43 -0
  12. package/results.sarif +379 -0
  13. package/src/index.js +142 -0
  14. package/src/ioc/feeds.js +42 -0
  15. package/src/ioc/updater.js +244 -0
  16. package/src/ioc/yaml-loader.js +96 -0
  17. package/src/report.js +152 -0
  18. package/src/response/playbooks.js +115 -0
  19. package/src/rules/index.js +197 -0
  20. package/src/sarif.js +74 -0
  21. package/src/scanner/ast.js +175 -0
  22. package/src/scanner/dataflow.js +167 -0
  23. package/src/scanner/dependencies.js +110 -0
  24. package/src/scanner/hash.js +68 -0
  25. package/src/scanner/obfuscation.js +99 -0
  26. package/src/scanner/package.js +60 -0
  27. package/src/scanner/shell.js +63 -0
  28. package/src/watch.js +37 -0
  29. package/test/samples/malicious.js +20 -0
  30. package/tests/run-tests.js +363 -0
  31. package/tests/samples/ast/malicious.js +20 -0
  32. package/tests/samples/clean/safe.js +14 -0
  33. package/tests/samples/dataflow/exfiltration.js +20 -0
  34. package/tests/samples/edge/empty/empty.js +0 -0
  35. package/tests/samples/edge/invalid-syntax/broken.js +5 -0
  36. package/tests/samples/edge/large-file/large.js +6 -0
  37. package/tests/samples/edge/non-js/readme.txt +3 -0
  38. package/tests/samples/markers/shai-hulud.js +10 -0
  39. package/tests/samples/obfuscation/obfuscated.js +1 -0
  40. package/tests/samples/package/package.json +9 -0
  41. package/tests/samples/shell/malicious.sh +13 -0
package/results.sarif ADDED
@@ -0,0 +1,379 @@
1
+ {
2
+ "$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json",
3
+ "version": "2.1.0",
4
+ "runs": [
5
+ {
6
+ "tool": {
7
+ "driver": {
8
+ "name": "MUADDIB",
9
+ "version": "1.0.0",
10
+ "informationUri": "https://github.com/DNSZLSK/muad-dib",
11
+ "rules": [
12
+ {
13
+ "id": "MUADDIB-AST-001",
14
+ "name": "Sensitive String Reference",
15
+ "shortDescription": {
16
+ "text": "Reference a un chemin ou identifiant sensible (.npmrc, .ssh, tokens)"
17
+ },
18
+ "fullDescription": {
19
+ "text": "Reference a un chemin ou identifiant sensible (.npmrc, .ssh, tokens)"
20
+ },
21
+ "helpUri": "https://blog.phylum.io/shai-hulud-npm-worm",
22
+ "properties": {
23
+ "severity": "HIGH",
24
+ "confidence": "medium",
25
+ "mitre": "T1552.001"
26
+ }
27
+ },
28
+ {
29
+ "id": "MUADDIB-AST-002",
30
+ "name": "Sensitive Environment Variable Access",
31
+ "shortDescription": {
32
+ "text": "Acces a une variable d'environnement sensible (GITHUB_TOKEN, NPM_TOKEN, AWS_*)"
33
+ },
34
+ "fullDescription": {
35
+ "text": "Acces a une variable d'environnement sensible (GITHUB_TOKEN, NPM_TOKEN, AWS_*)"
36
+ },
37
+ "helpUri": "https://blog.phylum.io/shai-hulud-npm-worm",
38
+ "properties": {
39
+ "severity": "HIGH",
40
+ "confidence": "high",
41
+ "mitre": "T1552.001"
42
+ }
43
+ },
44
+ {
45
+ "id": "MUADDIB-AST-003",
46
+ "name": "Dangerous Function Call",
47
+ "shortDescription": {
48
+ "text": "Appel a une fonction dangereuse (exec, spawn, eval, Function)"
49
+ },
50
+ "fullDescription": {
51
+ "text": "Appel a une fonction dangereuse (exec, spawn, eval, Function)"
52
+ },
53
+ "helpUri": "https://owasp.org/www-community/attacks/Command_Injection",
54
+ "properties": {
55
+ "severity": "MEDIUM",
56
+ "confidence": "medium",
57
+ "mitre": "T1059"
58
+ }
59
+ },
60
+ {
61
+ "id": "MUADDIB-AST-004",
62
+ "name": "Eval Usage",
63
+ "shortDescription": {
64
+ "text": "Utilisation de eval() ou new Function() - execution de code dynamique"
65
+ },
66
+ "fullDescription": {
67
+ "text": "Utilisation de eval() ou new Function() - execution de code dynamique"
68
+ },
69
+ "helpUri": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval#never_use_eval!",
70
+ "properties": {
71
+ "severity": "HIGH",
72
+ "confidence": "high",
73
+ "mitre": "T1059.007"
74
+ }
75
+ },
76
+ {
77
+ "id": "MUADDIB-SHELL-001",
78
+ "name": "Remote Code Execution via Curl",
79
+ "shortDescription": {
80
+ "text": "Telecharge et execute du code distant via curl | sh"
81
+ },
82
+ "fullDescription": {
83
+ "text": "Telecharge et execute du code distant via curl | sh"
84
+ },
85
+ "helpUri": "https://blog.phylum.io/shai-hulud-npm-worm",
86
+ "properties": {
87
+ "severity": "CRITICAL",
88
+ "confidence": "high",
89
+ "mitre": "T1105"
90
+ }
91
+ },
92
+ {
93
+ "id": "MUADDIB-SHELL-002",
94
+ "name": "Reverse Shell",
95
+ "shortDescription": {
96
+ "text": "Tentative de connexion reverse shell"
97
+ },
98
+ "fullDescription": {
99
+ "text": "Tentative de connexion reverse shell"
100
+ },
101
+ "helpUri": "https://attack.mitre.org/techniques/T1059/004/",
102
+ "properties": {
103
+ "severity": "CRITICAL",
104
+ "confidence": "high",
105
+ "mitre": "T1059.004"
106
+ }
107
+ },
108
+ {
109
+ "id": "MUADDIB-SHELL-003",
110
+ "name": "Dead Man's Switch",
111
+ "shortDescription": {
112
+ "text": "Suppression du repertoire home - dead man's switch de Shai-Hulud"
113
+ },
114
+ "fullDescription": {
115
+ "text": "Suppression du repertoire home - dead man's switch de Shai-Hulud"
116
+ },
117
+ "helpUri": "https://www.wiz.io/blog/shai-hulud-npm-supply-chain-attack",
118
+ "properties": {
119
+ "severity": "CRITICAL",
120
+ "confidence": "high",
121
+ "mitre": "T1485"
122
+ }
123
+ },
124
+ {
125
+ "id": "MUADDIB-PKG-001",
126
+ "name": "Suspicious Lifecycle Script",
127
+ "shortDescription": {
128
+ "text": "Script preinstall/postinstall suspect dans package.json"
129
+ },
130
+ "fullDescription": {
131
+ "text": "Script preinstall/postinstall suspect dans package.json"
132
+ },
133
+ "helpUri": "https://blog.npmjs.org/post/141577284765/kik-left-pad-and-npm",
134
+ "properties": {
135
+ "severity": "MEDIUM",
136
+ "confidence": "medium",
137
+ "mitre": "T1195.002"
138
+ }
139
+ },
140
+ {
141
+ "id": "MUADDIB-OBF-001",
142
+ "name": "Code Obfuscation Detected",
143
+ "shortDescription": {
144
+ "text": "Code fortement obfusque detecte - probablement malveillant"
145
+ },
146
+ "fullDescription": {
147
+ "text": "Code fortement obfusque detecte - probablement malveillant"
148
+ },
149
+ "helpUri": "https://blog.phylum.io/shai-hulud-npm-worm",
150
+ "properties": {
151
+ "severity": "HIGH",
152
+ "confidence": "medium",
153
+ "mitre": "T1027"
154
+ }
155
+ },
156
+ {
157
+ "id": "MUADDIB-DEP-001",
158
+ "name": "Known Malicious Package",
159
+ "shortDescription": {
160
+ "text": "Package present dans la base IOC de packages malveillants connus"
161
+ },
162
+ "fullDescription": {
163
+ "text": "Package present dans la base IOC de packages malveillants connus"
164
+ },
165
+ "helpUri": "https://socket.dev/npm/issue",
166
+ "properties": {
167
+ "severity": "CRITICAL",
168
+ "confidence": "high",
169
+ "mitre": "T1195.002"
170
+ }
171
+ },
172
+ {
173
+ "id": "MUADDIB-DEP-002",
174
+ "name": "Suspicious File in Dependency",
175
+ "shortDescription": {
176
+ "text": "Fichier suspect detecte dans une dependance (setup_bun.js, etc.)"
177
+ },
178
+ "fullDescription": {
179
+ "text": "Fichier suspect detecte dans une dependance (setup_bun.js, etc.)"
180
+ },
181
+ "helpUri": "https://blog.phylum.io/shai-hulud-npm-worm",
182
+ "properties": {
183
+ "severity": "CRITICAL",
184
+ "confidence": "high",
185
+ "mitre": "T1195.002"
186
+ }
187
+ },
188
+ {
189
+ "id": "MUADDIB-DEP-003",
190
+ "name": "Shai-Hulud Marker Detected",
191
+ "shortDescription": {
192
+ "text": "Marqueur Shai-Hulud detecte dans le code"
193
+ },
194
+ "fullDescription": {
195
+ "text": "Marqueur Shai-Hulud detecte dans le code"
196
+ },
197
+ "helpUri": "https://blog.phylum.io/shai-hulud-npm-worm",
198
+ "properties": {
199
+ "severity": "CRITICAL",
200
+ "confidence": "high",
201
+ "mitre": "T1195.002"
202
+ }
203
+ },
204
+ {
205
+ "id": "MUADDIB-DEP-004",
206
+ "name": "Lifecycle Script in Dependency",
207
+ "shortDescription": {
208
+ "text": "Une dependance a un script preinstall/postinstall"
209
+ },
210
+ "fullDescription": {
211
+ "text": "Une dependance a un script preinstall/postinstall"
212
+ },
213
+ "helpUri": "https://docs.npmjs.com/cli/v9/using-npm/scripts#life-cycle-scripts",
214
+ "properties": {
215
+ "severity": "MEDIUM",
216
+ "confidence": "low",
217
+ "mitre": "T1195.002"
218
+ }
219
+ },
220
+ {
221
+ "id": "MUADDIB-HASH-001",
222
+ "name": "Known Malicious File Hash",
223
+ "shortDescription": {
224
+ "text": "Hash SHA256 correspond a un fichier malveillant connu"
225
+ },
226
+ "fullDescription": {
227
+ "text": "Hash SHA256 correspond a un fichier malveillant connu"
228
+ },
229
+ "helpUri": "https://www.virustotal.com",
230
+ "properties": {
231
+ "severity": "CRITICAL",
232
+ "confidence": "high",
233
+ "mitre": "T1195.002"
234
+ }
235
+ },
236
+ {
237
+ "id": "MUADDIB-FLOW-001",
238
+ "name": "Suspicious Data Flow",
239
+ "shortDescription": {
240
+ "text": "Flux de donnees suspect: lecture de credentials puis envoi reseau"
241
+ },
242
+ "fullDescription": {
243
+ "text": "Flux de donnees suspect: lecture de credentials puis envoi reseau"
244
+ },
245
+ "helpUri": "https://blog.phylum.io/shai-hulud-npm-worm",
246
+ "properties": {
247
+ "severity": "CRITICAL",
248
+ "confidence": "high",
249
+ "mitre": "T1041"
250
+ }
251
+ }
252
+ ]
253
+ }
254
+ },
255
+ "results": [
256
+ {
257
+ "ruleId": "MUADDIB-AST-001",
258
+ "level": "error",
259
+ "message": {
260
+ "text": "Reference a \".npmrc\" detectee."
261
+ },
262
+ "locations": [
263
+ {
264
+ "physicalLocation": {
265
+ "artifactLocation": {
266
+ "uri": "malicious.js",
267
+ "uriBaseId": "%SRCROOT%"
268
+ },
269
+ "region": {
270
+ "startLine": 1
271
+ }
272
+ }
273
+ }
274
+ ],
275
+ "properties": {
276
+ "confidence": "medium",
277
+ "mitre": "T1552.001"
278
+ }
279
+ },
280
+ {
281
+ "ruleId": "MUADDIB-AST-002",
282
+ "level": "error",
283
+ "message": {
284
+ "text": "Acces a variable sensible process.env.GITHUB_TOKEN."
285
+ },
286
+ "locations": [
287
+ {
288
+ "physicalLocation": {
289
+ "artifactLocation": {
290
+ "uri": "malicious.js",
291
+ "uriBaseId": "%SRCROOT%"
292
+ },
293
+ "region": {
294
+ "startLine": 1
295
+ }
296
+ }
297
+ }
298
+ ],
299
+ "properties": {
300
+ "confidence": "high",
301
+ "mitre": "T1552.001"
302
+ }
303
+ },
304
+ {
305
+ "ruleId": "MUADDIB-AST-001",
306
+ "level": "error",
307
+ "message": {
308
+ "text": "Reference a \"api.github.com\" detectee."
309
+ },
310
+ "locations": [
311
+ {
312
+ "physicalLocation": {
313
+ "artifactLocation": {
314
+ "uri": "malicious.js",
315
+ "uriBaseId": "%SRCROOT%"
316
+ },
317
+ "region": {
318
+ "startLine": 1
319
+ }
320
+ }
321
+ }
322
+ ],
323
+ "properties": {
324
+ "confidence": "medium",
325
+ "mitre": "T1552.001"
326
+ }
327
+ },
328
+ {
329
+ "ruleId": "MUADDIB-AST-003",
330
+ "level": "warning",
331
+ "message": {
332
+ "text": "Appel dangereux \"exec\" detecte."
333
+ },
334
+ "locations": [
335
+ {
336
+ "physicalLocation": {
337
+ "artifactLocation": {
338
+ "uri": "malicious.js",
339
+ "uriBaseId": "%SRCROOT%"
340
+ },
341
+ "region": {
342
+ "startLine": 1
343
+ }
344
+ }
345
+ }
346
+ ],
347
+ "properties": {
348
+ "confidence": "medium",
349
+ "mitre": "T1059"
350
+ }
351
+ },
352
+ {
353
+ "ruleId": "MUADDIB-FLOW-001",
354
+ "level": "error",
355
+ "message": {
356
+ "text": "Flux suspect: lecture credentials (readFileSync, GITHUB_TOKEN) + envoi reseau (request, exec)"
357
+ },
358
+ "locations": [
359
+ {
360
+ "physicalLocation": {
361
+ "artifactLocation": {
362
+ "uri": "malicious.js",
363
+ "uriBaseId": "%SRCROOT%"
364
+ },
365
+ "region": {
366
+ "startLine": 1
367
+ }
368
+ }
369
+ }
370
+ ],
371
+ "properties": {
372
+ "confidence": "high",
373
+ "mitre": "T1041"
374
+ }
375
+ }
376
+ ]
377
+ }
378
+ ]
379
+ }
package/src/index.js ADDED
@@ -0,0 +1,142 @@
1
+ const { scanPackageJson } = require('./scanner/package.js');
2
+ const { scanShellScripts } = require('./scanner/shell.js');
3
+ const { analyzeAST } = require('./scanner/ast.js');
4
+ const { detectObfuscation } = require('./scanner/obfuscation.js');
5
+ const { scanDependencies } = require('./scanner/dependencies.js');
6
+ const { scanHashes } = require('./scanner/hash.js');
7
+ const { analyzeDataFlow } = require('./scanner/dataflow.js');
8
+ const { getPlaybook } = require('./response/playbooks.js');
9
+ const { getRule } = require('./rules/index.js');
10
+ const { saveReport } = require('./report.js');
11
+ const { saveSARIF } = require('./sarif.js');
12
+
13
+ async function run(targetPath, options = {}) {
14
+ const threats = [];
15
+
16
+ const packageThreats = await scanPackageJson(targetPath);
17
+ threats.push(...packageThreats);
18
+
19
+ const shellThreats = await scanShellScripts(targetPath);
20
+ threats.push(...shellThreats);
21
+
22
+ const astThreats = await analyzeAST(targetPath);
23
+ threats.push(...astThreats);
24
+
25
+ const obfuscationThreats = detectObfuscation(targetPath);
26
+ threats.push(...obfuscationThreats);
27
+
28
+ const dependencyThreats = await scanDependencies(targetPath);
29
+ threats.push(...dependencyThreats);
30
+
31
+ const hashThreats = await scanHashes(targetPath);
32
+ threats.push(...hashThreats);
33
+
34
+ const dataflowThreats = await analyzeDataFlow(targetPath);
35
+ threats.push(...dataflowThreats);
36
+
37
+ // Enrichir chaque menace avec les regles
38
+ const enrichedThreats = threats.map(t => {
39
+ const rule = getRule(t.type);
40
+ return {
41
+ ...t,
42
+ rule_id: rule.id,
43
+ rule_name: rule.name,
44
+ confidence: rule.confidence,
45
+ references: rule.references,
46
+ mitre: rule.mitre,
47
+ playbook: getPlaybook(t.type)
48
+ };
49
+ });
50
+
51
+ const result = {
52
+ target: targetPath,
53
+ timestamp: new Date().toISOString(),
54
+ threats: enrichedThreats,
55
+ summary: {
56
+ total: threats.length,
57
+ critical: threats.filter(t => t.severity === 'CRITICAL').length,
58
+ high: threats.filter(t => t.severity === 'HIGH').length,
59
+ medium: threats.filter(t => t.severity === 'MEDIUM').length
60
+ }
61
+ };
62
+
63
+ // Sortie JSON
64
+ if (options.json) {
65
+ console.log(JSON.stringify(result, null, 2));
66
+ }
67
+ // Sortie HTML
68
+ else if (options.html) {
69
+ saveReport(result, options.html);
70
+ console.log(`[OK] Rapport HTML genere: ${options.html}`);
71
+ }
72
+ // Sortie SARIF
73
+ else if (options.sarif) {
74
+ saveSARIF(result, options.sarif);
75
+ console.log(`[OK] Rapport SARIF genere: ${options.sarif}`);
76
+ }
77
+ // Sortie explain
78
+ else if (options.explain) {
79
+ console.log(`\n[MUADDIB] Scan de ${targetPath}\n`);
80
+
81
+ if (enrichedThreats.length === 0) {
82
+ console.log('[OK] Aucune menace detectee.\n');
83
+ } else {
84
+ console.log(`[ALERTE] ${enrichedThreats.length} menace(s) detectee(s):\n`);
85
+ enrichedThreats.forEach((t, i) => {
86
+ console.log(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`);
87
+ console.log(` ${i + 1}. [${t.severity}] ${t.rule_name}`);
88
+ console.log(` Rule ID: ${t.rule_id}`);
89
+ console.log(` Fichier: ${t.file}`);
90
+ if (t.line) console.log(` Ligne: ${t.line}`);
91
+ console.log(` Confidence: ${t.confidence}`);
92
+ console.log(` Message: ${t.message}`);
93
+ if (t.mitre) console.log(` MITRE: ${t.mitre} (https://attack.mitre.org/techniques/${t.mitre.replace('.', '/')})`);
94
+ if (t.references && t.references.length > 0) {
95
+ console.log(` References:`);
96
+ t.references.forEach(ref => console.log(` - ${ref}`));
97
+ }
98
+ console.log(` Playbook: ${t.playbook}`);
99
+ console.log('');
100
+ });
101
+ }
102
+ }
103
+ // Sortie normale
104
+ else {
105
+ console.log(`\n[MUADDIB] Scan de ${targetPath}\n`);
106
+
107
+ if (threats.length === 0) {
108
+ console.log('[OK] Aucune menace detectee.\n');
109
+ } else {
110
+ console.log(`[ALERTE] ${threats.length} menace(s) detectee(s):\n`);
111
+ threats.forEach((t, i) => {
112
+ console.log(` ${i + 1}. [${t.severity}] ${t.type}`);
113
+ console.log(` ${t.message}`);
114
+ console.log(` Fichier: ${t.file}\n`);
115
+ });
116
+
117
+ console.log('[REPONSE] Recommandations:\n');
118
+ threats.forEach(t => {
119
+ const playbook = getPlaybook(t.type);
120
+ if (playbook) {
121
+ console.log(` -> ${playbook}\n`);
122
+ }
123
+ });
124
+ }
125
+ }
126
+
127
+ // Calculer exit code selon le niveau de fail
128
+ const failLevel = options.failLevel || 'high';
129
+ const severityLevels = {
130
+ critical: ['CRITICAL'],
131
+ high: ['CRITICAL', 'HIGH'],
132
+ medium: ['CRITICAL', 'HIGH', 'MEDIUM'],
133
+ low: ['CRITICAL', 'HIGH', 'MEDIUM', 'LOW']
134
+ };
135
+
136
+ const levelsToCheck = severityLevels[failLevel] || severityLevels.high;
137
+ const failingThreats = threats.filter(t => levelsToCheck.includes(t.severity));
138
+
139
+ return failingThreats.length;
140
+ }
141
+
142
+ module.exports = { run };
@@ -0,0 +1,42 @@
1
+ const KNOWN_MALICIOUS_PACKAGES = [
2
+ 'setup_bun.js',
3
+ 'bun_environment.js',
4
+ '@ctrl/tinycolor',
5
+ 'flatmap-stream',
6
+ 'event-stream'
7
+ ];
8
+
9
+ const KNOWN_MALICIOUS_HASHES = [
10
+ '62ee164b9b306250c1172583f138c9614139264f889fa99614903c12755468d0',
11
+ 'cbb9bc5a8496243e02f3cc080efbe3e4a1430ba0671f2e43a202bf45b05479cd',
12
+ 'f099c5d9ec417d4445a0328ac0ada9cde79fc37410914103ae9c609cbc0ee068'
13
+ ];
14
+
15
+ const SUSPICIOUS_REPO_MARKERS = [
16
+ 'Sha1-Hulud',
17
+ 'Shai-Hulud',
18
+ 'The Second Coming'
19
+ ];
20
+
21
+ function isKnownMalicious(packageName) {
22
+ return KNOWN_MALICIOUS_PACKAGES.includes(packageName);
23
+ }
24
+
25
+ function isKnownMaliciousHash(hash) {
26
+ return KNOWN_MALICIOUS_HASHES.includes(hash.toLowerCase());
27
+ }
28
+
29
+ function hasSuspiciousMarker(text) {
30
+ return SUSPICIOUS_REPO_MARKERS.some(marker =>
31
+ text.toLowerCase().includes(marker.toLowerCase())
32
+ );
33
+ }
34
+
35
+ module.exports = {
36
+ isKnownMalicious,
37
+ isKnownMaliciousHash,
38
+ hasSuspiciousMarker,
39
+ KNOWN_MALICIOUS_PACKAGES,
40
+ KNOWN_MALICIOUS_HASHES,
41
+ SUSPICIOUS_REPO_MARKERS
42
+ };