wormclaude 1.0.86 → 1.0.87
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/dist/pentest.js +182 -3
- package/package.json +1 -1
package/dist/pentest.js
CHANGED
|
@@ -6,12 +6,17 @@
|
|
|
6
6
|
//
|
|
7
7
|
// Güvenlik: yalnız PT_ALLOWED'daki ikililer çalıştırılır (ele geçirilmiş/kötü sunucunun
|
|
8
8
|
// istemcide rastgele komut çalıştırmasını engeller). Komutlar SHELL'siz spawn edilir (enjeksiyon yok).
|
|
9
|
-
import { spawn } from 'node:child_process';
|
|
10
|
-
import { statSync } from 'node:fs';
|
|
9
|
+
import { spawn, spawnSync } from 'node:child_process';
|
|
10
|
+
import { statSync, mkdirSync, writeFileSync, readdirSync, renameSync, chmodSync, rmSync } from 'node:fs';
|
|
11
11
|
import * as path from 'node:path';
|
|
12
|
+
import * as os from 'node:os';
|
|
12
13
|
// İstemci-tarafı allowlist (sunucudakiyle aynı; çift güvence).
|
|
13
14
|
export const PT_ALLOWED = new Set(['httpx', 'nuclei', 'dalfox', 'sqlmap', 'subfinder', 'katana', 'nmap', 'ffuf']);
|
|
14
15
|
const MAX_OUT = 200_000;
|
|
16
|
+
// CLI'nin yönettiği araç dizini — PATH'e dokunmadan araçları burada tutarız.
|
|
17
|
+
export function managedBinDir() {
|
|
18
|
+
return path.join(os.homedir(), '.wormclaude', 'bin');
|
|
19
|
+
}
|
|
15
20
|
// İkiliyi PATH üzerinde çöz. Windows'ta Node spawn(shell:false) PATHEXT aramaz →
|
|
16
21
|
// 'dalfox' verilse 'dalfox.exe'yi bulamaz; bu yüzden elle çözüyoruz.
|
|
17
22
|
// Döner: {path, viaCmd} — viaCmd ise .cmd/.bat olduğu için cmd.exe ile sarılır.
|
|
@@ -34,7 +39,8 @@ export function resolveBin(bin) {
|
|
|
34
39
|
return classify(bin + e);
|
|
35
40
|
return null;
|
|
36
41
|
}
|
|
37
|
-
|
|
42
|
+
// Önce CLI'nin yönettiği dizin, sonra sistem PATH.
|
|
43
|
+
const dirs = [managedBinDir(), ...(process.env.PATH || '').split(path.delimiter)].filter(Boolean);
|
|
38
44
|
for (const d of dirs) {
|
|
39
45
|
const base = path.join(d, bin);
|
|
40
46
|
if (!isWin && isFile(base))
|
|
@@ -47,6 +53,170 @@ export function resolveBin(bin) {
|
|
|
47
53
|
}
|
|
48
54
|
return null;
|
|
49
55
|
}
|
|
56
|
+
// ── Otomatik araç kurucu (tek-binary GitHub release'leri) ─────────────────────
|
|
57
|
+
// sqlmap (python) ve nmap (installer) hariç — onlar elle/paketle kurulur.
|
|
58
|
+
const TOOL_REPOS = {
|
|
59
|
+
dalfox: 'hahwul/dalfox',
|
|
60
|
+
nuclei: 'projectdiscovery/nuclei',
|
|
61
|
+
httpx: 'projectdiscovery/httpx',
|
|
62
|
+
subfinder: 'projectdiscovery/subfinder',
|
|
63
|
+
katana: 'projectdiscovery/katana',
|
|
64
|
+
ffuf: 'ffuf/ffuf',
|
|
65
|
+
};
|
|
66
|
+
function platTokens() {
|
|
67
|
+
const p = process.platform, a = process.arch;
|
|
68
|
+
const osSyn = p === 'win32' ? /windows|win/i : p === 'darwin' ? /darwin|macos|mac/i : /linux/i;
|
|
69
|
+
const archSyn = a === 'arm64' ? /arm64|aarch64/i : /amd64|x86_64|x64/i;
|
|
70
|
+
return { osSyn, archSyn };
|
|
71
|
+
}
|
|
72
|
+
async function ghLatestAsset(repo) {
|
|
73
|
+
try {
|
|
74
|
+
const r = await fetch(`https://api.github.com/repos/${repo}/releases/latest`, {
|
|
75
|
+
headers: { 'User-Agent': 'wormclaude', Accept: 'application/vnd.github+json' },
|
|
76
|
+
signal: AbortSignal.timeout(15000),
|
|
77
|
+
});
|
|
78
|
+
if (!r.ok)
|
|
79
|
+
return null;
|
|
80
|
+
const rel = await r.json();
|
|
81
|
+
const { osSyn, archSyn } = platTokens();
|
|
82
|
+
const assets = (rel.assets || []).filter((x) => {
|
|
83
|
+
const n = String(x.name);
|
|
84
|
+
if (!/\.(zip|tar\.gz|tgz)$/i.test(n))
|
|
85
|
+
return false;
|
|
86
|
+
if (/sha256|\.txt$|\.deb$|\.rpm$|checksum|cdx|musl/i.test(n))
|
|
87
|
+
return false;
|
|
88
|
+
return osSyn.test(n) && archSyn.test(n);
|
|
89
|
+
});
|
|
90
|
+
const pref = process.platform === 'win32' ? /\.zip$/i : /\.tar\.gz$|\.tgz$/i;
|
|
91
|
+
assets.sort((x, y) => (pref.test(y.name) ? 1 : 0) - (pref.test(x.name) ? 1 : 0));
|
|
92
|
+
const a0 = assets[0];
|
|
93
|
+
return a0 ? { name: a0.name, url: a0.browser_download_url } : null;
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
function spawnSyncOk(cmd, args) {
|
|
100
|
+
try {
|
|
101
|
+
const r = spawnSync(cmd, args, { stdio: 'ignore', windowsHide: true });
|
|
102
|
+
return !r.error && r.status === 0;
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
function extractArchive(archive, destDir) {
|
|
109
|
+
// tar (Windows 10+ bsdtar) zip'i de açar; unix'te de mevcut.
|
|
110
|
+
if (spawnSyncOk('tar', ['-xf', archive, '-C', destDir]))
|
|
111
|
+
return true;
|
|
112
|
+
if (process.platform === 'win32' && /\.zip$/i.test(archive)) {
|
|
113
|
+
return spawnSyncOk('powershell', ['-NoProfile', '-NonInteractive', '-Command',
|
|
114
|
+
`Expand-Archive -LiteralPath "${archive}" -DestinationPath "${destDir}" -Force`]);
|
|
115
|
+
}
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
function findFile(dir, name) {
|
|
119
|
+
const stack = [dir];
|
|
120
|
+
while (stack.length) {
|
|
121
|
+
const d = stack.pop();
|
|
122
|
+
let entries;
|
|
123
|
+
try {
|
|
124
|
+
entries = readdirSync(d, { withFileTypes: true });
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
for (const e of entries) {
|
|
130
|
+
const full = path.join(d, e.name);
|
|
131
|
+
if (e.isDirectory())
|
|
132
|
+
stack.push(full);
|
|
133
|
+
else if (e.name.toLowerCase() === name.toLowerCase())
|
|
134
|
+
return full;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
// Eksik aracı yönetilen dizine indirir (PATH'e dokunmaz). Döner: kuruldu mu.
|
|
140
|
+
export async function ensureTool(bin, log) {
|
|
141
|
+
if (resolveBin(bin))
|
|
142
|
+
return true;
|
|
143
|
+
const repo = TOOL_REPOS[bin];
|
|
144
|
+
if (!repo)
|
|
145
|
+
return false; // sqlmap/nmap otomatik kurulamaz
|
|
146
|
+
const dest = managedBinDir();
|
|
147
|
+
mkdirSync(dest, { recursive: true });
|
|
148
|
+
const asset = await ghLatestAsset(repo);
|
|
149
|
+
if (!asset) {
|
|
150
|
+
log(` ${bin}: uygun sürüm bulunamadı (GitHub ${repo})`);
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
let buf;
|
|
154
|
+
try {
|
|
155
|
+
const r = await fetch(asset.url, { headers: { 'User-Agent': 'wormclaude' }, signal: AbortSignal.timeout(120000) });
|
|
156
|
+
if (!r.ok) {
|
|
157
|
+
log(` ${bin}: indirme hatası ${r.status}`);
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
buf = Buffer.from(await r.arrayBuffer());
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
log(` ${bin}: indirme zaman aşımı`);
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
166
|
+
// Arşiv uzantısını koru — Expand-Archive fallback'i .zip uzantısına bakar.
|
|
167
|
+
const ext = /\.tar\.gz$/i.test(asset.name) ? '.tar.gz' : /\.tgz$/i.test(asset.name) ? '.tgz' : '.zip';
|
|
168
|
+
const tmp = path.join(dest, `.dl_${bin}${ext}`);
|
|
169
|
+
const exDir = path.join(dest, `.ex_${bin}`);
|
|
170
|
+
try {
|
|
171
|
+
rmSync(exDir, { recursive: true, force: true });
|
|
172
|
+
}
|
|
173
|
+
catch { /* */ }
|
|
174
|
+
writeFileSync(tmp, buf);
|
|
175
|
+
mkdirSync(exDir, { recursive: true });
|
|
176
|
+
const ok = extractArchive(tmp, exDir);
|
|
177
|
+
try {
|
|
178
|
+
rmSync(tmp, { force: true });
|
|
179
|
+
}
|
|
180
|
+
catch { /* */ }
|
|
181
|
+
if (!ok) {
|
|
182
|
+
log(` ${bin}: arşiv açılamadı`);
|
|
183
|
+
try {
|
|
184
|
+
rmSync(exDir, { recursive: true, force: true });
|
|
185
|
+
}
|
|
186
|
+
catch { /* */ }
|
|
187
|
+
return false;
|
|
188
|
+
}
|
|
189
|
+
const want = process.platform === 'win32' ? bin + '.exe' : bin;
|
|
190
|
+
const found = findFile(exDir, want);
|
|
191
|
+
if (!found) {
|
|
192
|
+
log(` ${bin}: arşivde çalıştırılabilir yok`);
|
|
193
|
+
try {
|
|
194
|
+
rmSync(exDir, { recursive: true, force: true });
|
|
195
|
+
}
|
|
196
|
+
catch { /* */ }
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
199
|
+
const target = path.join(dest, want);
|
|
200
|
+
try {
|
|
201
|
+
try {
|
|
202
|
+
rmSync(target, { force: true });
|
|
203
|
+
}
|
|
204
|
+
catch { /* */ }
|
|
205
|
+
renameSync(found, target);
|
|
206
|
+
if (process.platform !== 'win32')
|
|
207
|
+
chmodSync(target, 0o755);
|
|
208
|
+
}
|
|
209
|
+
catch {
|
|
210
|
+
log(` ${bin}: yerleştirme hatası`);
|
|
211
|
+
return false;
|
|
212
|
+
}
|
|
213
|
+
try {
|
|
214
|
+
rmSync(exDir, { recursive: true, force: true });
|
|
215
|
+
}
|
|
216
|
+
catch { /* */ }
|
|
217
|
+
log(` ✓ ${bin} kuruldu → ${target}`);
|
|
218
|
+
return true;
|
|
219
|
+
}
|
|
50
220
|
async function postJson(config, p, body, timeoutMs) {
|
|
51
221
|
try {
|
|
52
222
|
const r = await fetch(`${config.baseUrl}${p}`, {
|
|
@@ -117,6 +287,15 @@ export async function runPentest(config, tool, target, scopeAck, log) {
|
|
|
117
287
|
const plan = await postJson(config, '/pentest/plan', { tool, target, scope_ack: scopeAck }, 15000);
|
|
118
288
|
if (!plan.ok)
|
|
119
289
|
return { ok: false, reason: plan.reason, plan };
|
|
290
|
+
// Eksik araçları yönetilen dizine otomatik kur (PATH'e dokunmadan).
|
|
291
|
+
const needed = [...new Set(plan.steps.map((s) => s.bin))];
|
|
292
|
+
for (const b of needed) {
|
|
293
|
+
if (resolveBin(b))
|
|
294
|
+
continue;
|
|
295
|
+
log(`Araç eksik: ${b} — ${TOOL_REPOS[b] ? 'otomatik kuruluyor…' : 'otomatik kurulamaz (elle kur)'}`);
|
|
296
|
+
if (TOOL_REPOS[b])
|
|
297
|
+
await ensureTool(b, log);
|
|
298
|
+
}
|
|
120
299
|
log(`Plan alındı (${plan.steps.length} adım). Yerelde çalıştırılıyor — trafik senin IP'nden çıkıyor…`);
|
|
121
300
|
const results = [];
|
|
122
301
|
const missing = [];
|