muaddib-scanner 1.0.7 → 1.0.9
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/CONTRIBUTING.md +98 -0
- package/README.fr.md +310 -0
- package/README.md +118 -93
- package/bin/muaddib.js +33 -26
- package/data/iocs.json +28 -0
- package/package.json +1 -1
- package/src/index.js +73 -15
- package/src/ioc/scraper.js +91 -50
- package/src/rules/index.js +40 -1
- package/vscode-extension/extension.js +12 -5
- package/vscode-extension/package.json +8 -3
package/src/ioc/scraper.js
CHANGED
|
@@ -52,7 +52,6 @@ async function scrapeGitHubAdvisories() {
|
|
|
52
52
|
const packages = [];
|
|
53
53
|
|
|
54
54
|
try {
|
|
55
|
-
// Plusieurs pages
|
|
56
55
|
for (let page = 1; page <= 5; page++) {
|
|
57
56
|
const url = `https://api.github.com/advisories?ecosystem=npm&per_page=100&page=${page}`;
|
|
58
57
|
const { status, data } = await fetchJSON(url);
|
|
@@ -97,7 +96,6 @@ async function scrapeOSV() {
|
|
|
97
96
|
const packages = [];
|
|
98
97
|
|
|
99
98
|
try {
|
|
100
|
-
// Query malware specifique
|
|
101
99
|
const queries = [
|
|
102
100
|
{ package: { ecosystem: 'npm' }, query: 'malware' },
|
|
103
101
|
{ package: { ecosystem: 'npm' }, query: 'malicious' },
|
|
@@ -140,54 +138,24 @@ async function scrapeOSV() {
|
|
|
140
138
|
}
|
|
141
139
|
|
|
142
140
|
// ============================================
|
|
143
|
-
// SOURCE 3:
|
|
144
|
-
// ============================================
|
|
145
|
-
async function scrapeSnyk() {
|
|
146
|
-
console.log('[SCRAPER] Snyk Vulnerability DB...');
|
|
147
|
-
const packages = [];
|
|
148
|
-
|
|
149
|
-
// Packages malveillants connus de Snyk (liste statique car API payante)
|
|
150
|
-
const knownMalicious = [
|
|
151
|
-
'event-stream', 'flatmap-stream', 'eslint-scope', 'eslint-config-eslint',
|
|
152
|
-
'getcookies', 'mailparser', 'nodemailer', 'nodemailer-js', 'node-ipc',
|
|
153
|
-
'peacenotwar', 'colors', 'faker', 'ua-parser-js', 'rc', 'coa',
|
|
154
|
-
'pac-resolver', 'set-value', 'ansi-html', 'ini', 'y18n', 'node-notifier',
|
|
155
|
-
'trim', 'trim-newlines', 'glob-parent', 'is-svg', 'css-what', 'normalize-url',
|
|
156
|
-
'hosted-git-info', 'ssri', 'tar', 'path-parse', 'json-schema',
|
|
157
|
-
'underscore', 'handlebars', 'lodash', 'marked', 'minimist', 'kind-of'
|
|
158
|
-
];
|
|
159
|
-
|
|
160
|
-
// On ne les ajoute que s'ils ont des versions malveillantes specifiques
|
|
161
|
-
// Pour l'instant on skip car c'est trop de faux positifs
|
|
162
|
-
console.log(`[SCRAPER] -> Skip (API payante)`);
|
|
163
|
-
|
|
164
|
-
return packages;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
// ============================================
|
|
168
|
-
// SOURCE 4: Socket.dev (via leur blog/reports)
|
|
141
|
+
// SOURCE 3: Socket.dev reports
|
|
169
142
|
// ============================================
|
|
170
143
|
async function scrapeSocketReports() {
|
|
171
144
|
console.log('[SCRAPER] Socket.dev reports...');
|
|
172
145
|
const packages = [];
|
|
173
146
|
|
|
174
|
-
// Packages malveillants reportes par Socket.dev
|
|
175
147
|
const socketMalicious = [
|
|
176
|
-
// Shai-Hulud variants
|
|
177
148
|
{ name: '@pnpm.exe/pnpm', severity: 'critical', source: 'socket-shai-hulud' },
|
|
178
149
|
{ name: '@nicklason/npm', severity: 'critical', source: 'socket-shai-hulud' },
|
|
179
150
|
{ name: 'bb-builder', severity: 'critical', source: 'socket-shai-hulud' },
|
|
180
151
|
{ name: 'codespaces-blank', severity: 'critical', source: 'socket-shai-hulud' },
|
|
181
|
-
// Crypto stealers
|
|
182
152
|
{ name: 'crypto-browserify-aes', severity: 'critical', source: 'socket-crypto-stealer' },
|
|
183
153
|
{ name: 'eth-wallet-gen', severity: 'critical', source: 'socket-crypto-stealer' },
|
|
184
154
|
{ name: 'solana-wallet-tools', severity: 'critical', source: 'socket-crypto-stealer' },
|
|
185
|
-
// Discord token stealers
|
|
186
155
|
{ name: 'discord-selfbot-tools', severity: 'critical', source: 'socket-discord-stealer' },
|
|
187
156
|
{ name: 'discord-selfbot-v13', severity: 'critical', source: 'socket-discord-stealer' },
|
|
188
157
|
{ name: 'discord-token-grabber', severity: 'critical', source: 'socket-discord-stealer' },
|
|
189
158
|
{ name: 'discordbot-tokens', severity: 'critical', source: 'socket-discord-stealer' },
|
|
190
|
-
// Typosquats recents
|
|
191
159
|
{ name: 'electorn', severity: 'high', source: 'socket-typosquat' },
|
|
192
160
|
{ name: 'electrn', severity: 'high', source: 'socket-typosquat' },
|
|
193
161
|
{ name: 'reqeusts', severity: 'high', source: 'socket-typosquat' },
|
|
@@ -205,7 +173,6 @@ async function scrapeSocketReports() {
|
|
|
205
173
|
{ name: 'reactt', severity: 'high', source: 'socket-typosquat' },
|
|
206
174
|
{ name: 'chalks', severity: 'high', source: 'socket-typosquat' },
|
|
207
175
|
{ name: 'chalkk', severity: 'high', source: 'socket-typosquat' },
|
|
208
|
-
// Protestware
|
|
209
176
|
{ name: 'styled-components-native', severity: 'high', source: 'socket-protestware' },
|
|
210
177
|
{ name: 'es5-ext', severity: 'medium', source: 'socket-protestware' }
|
|
211
178
|
];
|
|
@@ -229,28 +196,22 @@ async function scrapeSocketReports() {
|
|
|
229
196
|
}
|
|
230
197
|
|
|
231
198
|
// ============================================
|
|
232
|
-
// SOURCE
|
|
199
|
+
// SOURCE 4: Phylum Research
|
|
233
200
|
// ============================================
|
|
234
201
|
async function scrapePhylum() {
|
|
235
202
|
console.log('[SCRAPER] Phylum Research...');
|
|
236
203
|
const packages = [];
|
|
237
204
|
|
|
238
|
-
// Packages malveillants reportes par Phylum
|
|
239
205
|
const phylumMalicious = [
|
|
240
|
-
// Shai-Hulud original
|
|
241
206
|
{ name: '@nicklason/npm-register', severity: 'critical' },
|
|
242
207
|
{ name: 'lemaaa', severity: 'critical' },
|
|
243
208
|
{ name: 'badshell', severity: 'critical' },
|
|
244
|
-
// Reverse shells
|
|
245
209
|
{ name: 'node-shell', severity: 'critical' },
|
|
246
210
|
{ name: 'reverse-shell-as-a-service', severity: 'critical' },
|
|
247
|
-
// Data exfiltration
|
|
248
211
|
{ name: 'browserify-sign-steal', severity: 'critical' },
|
|
249
212
|
{ name: 'npm-script-demo', severity: 'high' },
|
|
250
|
-
// Malware loaders
|
|
251
213
|
{ name: 'load-from-cwd-or-npm', severity: 'high' },
|
|
252
214
|
{ name: 'loadyaml-', severity: 'high' },
|
|
253
|
-
// Install scripts malicious
|
|
254
215
|
{ name: 'preinstall-script', severity: 'high' },
|
|
255
216
|
{ name: 'postinstall-script', severity: 'high' }
|
|
256
217
|
];
|
|
@@ -274,13 +235,12 @@ async function scrapePhylum() {
|
|
|
274
235
|
}
|
|
275
236
|
|
|
276
237
|
// ============================================
|
|
277
|
-
// SOURCE
|
|
238
|
+
// SOURCE 5: npm removed packages
|
|
278
239
|
// ============================================
|
|
279
240
|
async function scrapeNpmRemoved() {
|
|
280
241
|
console.log('[SCRAPER] npm removed packages...');
|
|
281
242
|
const packages = [];
|
|
282
243
|
|
|
283
|
-
// Packages retires de npm pour raisons de securite
|
|
284
244
|
const removedPackages = [
|
|
285
245
|
{ name: 'event-stream', version: '3.3.6', reason: 'Malicious code injection' },
|
|
286
246
|
{ name: 'flatmap-stream', version: '0.1.1', reason: 'Bitcoin wallet stealer' },
|
|
@@ -314,13 +274,12 @@ async function scrapeNpmRemoved() {
|
|
|
314
274
|
}
|
|
315
275
|
|
|
316
276
|
// ============================================
|
|
317
|
-
// SOURCE
|
|
277
|
+
// SOURCE 6: Typosquats generator
|
|
318
278
|
// ============================================
|
|
319
279
|
async function generateTyposquats() {
|
|
320
280
|
console.log('[SCRAPER] Typosquats generation...');
|
|
321
281
|
const packages = [];
|
|
322
282
|
|
|
323
|
-
// Top packages npm et leurs typosquats connus
|
|
324
283
|
const typosquatMap = {
|
|
325
284
|
'lodash': ['lodahs', 'lodasg', 'lodash-', '-lodash', 'lodas', 'lodashh'],
|
|
326
285
|
'express': ['expres', 'expresss', 'exprees', 'exprss', 'exppress'],
|
|
@@ -364,6 +323,90 @@ async function generateTyposquats() {
|
|
|
364
323
|
return packages;
|
|
365
324
|
}
|
|
366
325
|
|
|
326
|
+
// ============================================
|
|
327
|
+
// SOURCE 7: AlienVault OTX
|
|
328
|
+
// ============================================
|
|
329
|
+
async function scrapeAlienVault() {
|
|
330
|
+
console.log('[SCRAPER] AlienVault OTX...');
|
|
331
|
+
const packages = [];
|
|
332
|
+
|
|
333
|
+
try {
|
|
334
|
+
const searches = ['npm%20malware', 'nodejs%20malware', 'supply%20chain%20npm'];
|
|
335
|
+
|
|
336
|
+
for (const search of searches) {
|
|
337
|
+
const url = `https://otx.alienvault.com/api/v1/search/pulses?q=${search}&limit=20`;
|
|
338
|
+
const { status, data } = await fetchJSON(url);
|
|
339
|
+
|
|
340
|
+
if (status === 200 && data?.results) {
|
|
341
|
+
for (const pulse of data.results) {
|
|
342
|
+
if (pulse.indicators) {
|
|
343
|
+
for (const indicator of pulse.indicators) {
|
|
344
|
+
if (indicator.type === 'hostname' || indicator.type === 'domain' || indicator.type === 'FileHash-SHA256') {
|
|
345
|
+
const name = indicator.indicator;
|
|
346
|
+
// Filtre pour noms de packages npm potentiels
|
|
347
|
+
if (name && !name.includes('.') && !name.includes('/') && name.length > 2 && name.length < 50) {
|
|
348
|
+
packages.push({
|
|
349
|
+
id: `OTX-${pulse.id}-${name.slice(0, 20)}`,
|
|
350
|
+
name: name,
|
|
351
|
+
version: '*',
|
|
352
|
+
severity: 'high',
|
|
353
|
+
confidence: 'medium',
|
|
354
|
+
source: 'alienvault-otx',
|
|
355
|
+
description: (pulse.name || 'AlienVault OTX threat intelligence').slice(0, 200),
|
|
356
|
+
references: [`https://otx.alienvault.com/pulse/${pulse.id}`],
|
|
357
|
+
mitre: 'T1195.002'
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
console.log(`[SCRAPER] -> ${packages.length} packages trouves`);
|
|
368
|
+
} catch (e) {
|
|
369
|
+
console.log(`[SCRAPER] -> Erreur: ${e.message}`);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
return packages;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// ============================================
|
|
376
|
+
// SOURCE 8: Aikido Intel (leur feed public)
|
|
377
|
+
// ============================================
|
|
378
|
+
async function scrapeAikidoIntel() {
|
|
379
|
+
console.log('[SCRAPER] Aikido Intel...');
|
|
380
|
+
const packages = [];
|
|
381
|
+
|
|
382
|
+
try {
|
|
383
|
+
const url = 'https://intel.aikido.dev/api/v1/malware?ecosystem=npm&limit=100';
|
|
384
|
+
const { status, data } = await fetchJSON(url);
|
|
385
|
+
|
|
386
|
+
if (status === 200 && Array.isArray(data)) {
|
|
387
|
+
for (const pkg of data) {
|
|
388
|
+
packages.push({
|
|
389
|
+
id: `AIKIDO-${pkg.name || pkg.id}`,
|
|
390
|
+
name: pkg.name,
|
|
391
|
+
version: pkg.version || '*',
|
|
392
|
+
severity: pkg.severity || 'high',
|
|
393
|
+
confidence: 'high',
|
|
394
|
+
source: 'aikido-intel',
|
|
395
|
+
description: (pkg.description || 'Malware detected by Aikido Intel').slice(0, 200),
|
|
396
|
+
references: ['https://intel.aikido.dev'],
|
|
397
|
+
mitre: 'T1195.002'
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
console.log(`[SCRAPER] -> ${packages.length} packages trouves`);
|
|
403
|
+
} catch (e) {
|
|
404
|
+
console.log(`[SCRAPER] -> Erreur: ${e.message}`);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
return packages;
|
|
408
|
+
}
|
|
409
|
+
|
|
367
410
|
// ============================================
|
|
368
411
|
// MAIN SCRAPER
|
|
369
412
|
// ============================================
|
|
@@ -372,7 +415,6 @@ async function runScraper() {
|
|
|
372
415
|
console.log('║ MUAD\'DIB IOC Scraper ║');
|
|
373
416
|
console.log('╚════════════════════════════════════════════╝\n');
|
|
374
417
|
|
|
375
|
-
// Charger les IOCs existants
|
|
376
418
|
let existingIOCs = { packages: [], hashes: [], markers: [], files: [] };
|
|
377
419
|
if (fs.existsSync(IOC_FILE)) {
|
|
378
420
|
existingIOCs = JSON.parse(fs.readFileSync(IOC_FILE, 'utf8'));
|
|
@@ -381,17 +423,17 @@ async function runScraper() {
|
|
|
381
423
|
const existingNames = new Set(existingIOCs.packages.map(p => p.name));
|
|
382
424
|
const initialCount = existingIOCs.packages.length;
|
|
383
425
|
|
|
384
|
-
// Scraper toutes les sources
|
|
385
426
|
const results = await Promise.all([
|
|
386
427
|
scrapeGitHubAdvisories(),
|
|
387
428
|
scrapeOSV(),
|
|
388
429
|
scrapeSocketReports(),
|
|
389
430
|
scrapePhylum(),
|
|
390
431
|
scrapeNpmRemoved(),
|
|
391
|
-
generateTyposquats()
|
|
432
|
+
generateTyposquats(),
|
|
433
|
+
scrapeAlienVault(),
|
|
434
|
+
scrapeAikidoIntel()
|
|
392
435
|
]);
|
|
393
436
|
|
|
394
|
-
// Fusionner sans doublons
|
|
395
437
|
let added = 0;
|
|
396
438
|
for (const pkgList of results) {
|
|
397
439
|
for (const pkg of pkgList) {
|
|
@@ -403,7 +445,6 @@ async function runScraper() {
|
|
|
403
445
|
}
|
|
404
446
|
}
|
|
405
447
|
|
|
406
|
-
// Sauvegarder
|
|
407
448
|
fs.writeFileSync(IOC_FILE, JSON.stringify(existingIOCs, null, 2));
|
|
408
449
|
|
|
409
450
|
console.log('\n╔════════════════════════════════════════════╗');
|
package/src/rules/index.js
CHANGED
|
@@ -207,4 +207,43 @@ function getRule(type) {
|
|
|
207
207
|
};
|
|
208
208
|
}
|
|
209
209
|
|
|
210
|
-
|
|
210
|
+
// Paranoid rules (ultra-strict)
|
|
211
|
+
const PARANOID_RULES = {
|
|
212
|
+
network_access: {
|
|
213
|
+
id: 'MUADDIB-PARANOID-001',
|
|
214
|
+
severity: 'HIGH',
|
|
215
|
+
patterns: ['fetch', 'axios', 'http.request', 'https.request', 'net.connect', 'XMLHttpRequest'],
|
|
216
|
+
message: 'Network access detected (paranoid mode)',
|
|
217
|
+
mitre: 'T1071'
|
|
218
|
+
},
|
|
219
|
+
sensitive_file_access: {
|
|
220
|
+
id: 'MUADDIB-PARANOID-002',
|
|
221
|
+
severity: 'HIGH',
|
|
222
|
+
patterns: ['.env', '.npmrc', '.ssh', '.git', 'id_rsa', 'credentials', 'secrets'],
|
|
223
|
+
message: 'Sensitive file access detected (paranoid mode)',
|
|
224
|
+
mitre: 'T1552.001'
|
|
225
|
+
},
|
|
226
|
+
dynamic_execution: {
|
|
227
|
+
id: 'MUADDIB-PARANOID-003',
|
|
228
|
+
severity: 'CRITICAL',
|
|
229
|
+
patterns: ['eval', 'Function', 'vm.runInContext'],
|
|
230
|
+
message: 'Dynamic code execution detected (paranoid mode)',
|
|
231
|
+
mitre: 'T1059'
|
|
232
|
+
},
|
|
233
|
+
subprocess: {
|
|
234
|
+
id: 'MUADDIB-PARANOID-004',
|
|
235
|
+
severity: 'CRITICAL',
|
|
236
|
+
patterns: ['child_process', 'spawn', 'exec', 'execSync', 'spawnSync', 'fork'],
|
|
237
|
+
message: 'Subprocess execution detected (paranoid mode)',
|
|
238
|
+
mitre: 'T1059.004'
|
|
239
|
+
},
|
|
240
|
+
env_access: {
|
|
241
|
+
id: 'MUADDIB-PARANOID-005',
|
|
242
|
+
severity: 'MEDIUM',
|
|
243
|
+
patterns: ['process.env'],
|
|
244
|
+
message: 'Environment variable access detected (paranoid mode)',
|
|
245
|
+
mitre: 'T1552.001'
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
module.exports = { RULES, getRule, PARANOID_RULES };
|
|
@@ -59,16 +59,23 @@ async function scanProject() {
|
|
|
59
59
|
const webhookUrl = config.get('webhookUrl');
|
|
60
60
|
const failLevel = config.get('failLevel');
|
|
61
61
|
|
|
62
|
-
let cmd = `
|
|
62
|
+
let cmd = `npx muaddib-scanner scan "${projectPath}" --json`;
|
|
63
63
|
if (webhookUrl) {
|
|
64
64
|
cmd += ` --webhook "${webhookUrl}"`;
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
exec(cmd, { maxBuffer: 10 * 1024 * 1024 }, (error, stdout, stderr) => {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
68
|
+
try {
|
|
69
|
+
// Si pas de JSON valide, c'est que le scan est propre
|
|
70
|
+
if (!stdout || !stdout.trim().startsWith('{')) {
|
|
71
|
+
vscode.window.showInformationMessage('MUAD\'DIB: Aucune menace detectee');
|
|
72
|
+
diagnosticCollection.clear();
|
|
73
|
+
resolve();
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
const result = JSON.parse(stdout);
|
|
77
|
+
displayResults(result, projectPath);
|
|
78
|
+
} catch (e) {
|
|
72
79
|
if (stdout.includes('Aucune menace')) {
|
|
73
80
|
vscode.window.showInformationMessage('MUAD\'DIB: Aucune menace detectee');
|
|
74
81
|
diagnosticCollection.clear();
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "muaddib-vscode",
|
|
3
3
|
"displayName": "MUAD'DIB Security Scanner",
|
|
4
4
|
"description": "Detecte les attaques supply chain npm directement dans VS Code",
|
|
5
|
-
"version": "1.0.
|
|
5
|
+
"version": "1.0.3",
|
|
6
6
|
"publisher": "dnszlsk",
|
|
7
7
|
"engines": {
|
|
8
8
|
"vscode": "^1.85.0"
|
|
@@ -49,7 +49,12 @@
|
|
|
49
49
|
},
|
|
50
50
|
"muaddib.failLevel": {
|
|
51
51
|
"type": "string",
|
|
52
|
-
"enum": [
|
|
52
|
+
"enum": [
|
|
53
|
+
"critical",
|
|
54
|
+
"high",
|
|
55
|
+
"medium",
|
|
56
|
+
"low"
|
|
57
|
+
],
|
|
53
58
|
"default": "high",
|
|
54
59
|
"description": "Niveau de severite pour afficher les alertes"
|
|
55
60
|
}
|
|
@@ -61,4 +66,4 @@
|
|
|
61
66
|
"url": "https://github.com/DNSZLSK/muad-dib"
|
|
62
67
|
},
|
|
63
68
|
"license": "MIT"
|
|
64
|
-
}
|
|
69
|
+
}
|