wormclaude 1.0.87 → 1.0.89
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/commands.js +37 -13
- package/dist/pentest.js +59 -286
- package/package.json +1 -1
package/dist/commands.js
CHANGED
|
@@ -45,10 +45,11 @@ export const COMMANDS = [
|
|
|
45
45
|
{ name: '/izinler', desc: 'onayli shell komutlarini yonet (list/add/remove/clear)' },
|
|
46
46
|
{ name: '/program', desc: 'Doğrulanmış Araştırmacı Programı — güven seviyeni göster' },
|
|
47
47
|
{ name: '/dogrula', desc: 'Doğrulanmış Araştırmacı başvurusu gönder: /dogrula <profil/şirket/gerekçe>' },
|
|
48
|
-
{ name: '/
|
|
49
|
-
{ name: '/
|
|
50
|
-
{ name: '/
|
|
51
|
-
{ name: '/
|
|
48
|
+
{ name: '/skill', desc: 'güvenlik tarama skill\'leri: /skill xss|sqli|recon <hedef> (seviye 3+)' },
|
|
49
|
+
{ name: '/recon', desc: '[seviye 3+] yetkili hedefte keşif (alt-alan/host/başlık/ifşa): /recon <alan>' },
|
|
50
|
+
{ name: '/scan', desc: '[seviye 3+] yetkili hedefte genel tarama (keşif+başlıklar): /scan <alan>' },
|
|
51
|
+
{ name: '/xss', desc: '[seviye 3+] yetkili hedefte XSS taraması (kendi motorumuz): /xss <url>' },
|
|
52
|
+
{ name: '/sqli', desc: '[seviye 3+] yetkili hedefte SQLi taraması (kendi motorumuz): /sqli <url>' },
|
|
52
53
|
{ name: '/export', desc: 'sohbeti dosyaya kaydet' },
|
|
53
54
|
{ name: '/resume', desc: 'en son kaydedilen oturumu yükle' },
|
|
54
55
|
{ name: '/quit', desc: 'çıkış' },
|
|
@@ -123,23 +124,33 @@ const PT_REASON = {
|
|
|
123
124
|
};
|
|
124
125
|
function formatFinding(x) {
|
|
125
126
|
const sev = x.severity ? `[${String(x.severity).toUpperCase()}] ` : '';
|
|
126
|
-
if (x.type === 'vuln')
|
|
127
|
-
return `${sev}${x.name || ''} — ${x.url || ''}`;
|
|
128
127
|
if (x.type === 'xss')
|
|
129
|
-
return `${sev}XSS ${x.
|
|
128
|
+
return `${sev}XSS (${x.context || '?'}) · param ${x.param || '?'} — ${x.url || ''}`;
|
|
130
129
|
if (x.type === 'sqli')
|
|
131
|
-
return `${sev}SQLi ${x.
|
|
130
|
+
return `${sev}SQLi (${x.technique || '?'}) · param ${x.param || '?'} — ${x.evidence || ''}`;
|
|
131
|
+
if (x.type === 'subdomain')
|
|
132
|
+
return `alt-alan: ${x.value}`;
|
|
132
133
|
if (x.type === 'host')
|
|
133
134
|
return `${x.status || ''} ${x.url || ''}${x.title ? ' · ' + x.title : ''}${x.server ? ' · ' + x.server : ''}`;
|
|
135
|
+
if (x.type === 'tech')
|
|
136
|
+
return `teknoloji: ${x.value}`;
|
|
137
|
+
if (x.type === 'weak_headers')
|
|
138
|
+
return `${sev}eksik güvenlik başlıkları: ${(x.missing || []).join(', ')}`;
|
|
139
|
+
if (x.type === 'cors')
|
|
140
|
+
return `${sev}CORS yanlış yapılandırma: ${x.url || ''} — ${x.evidence || ''}`;
|
|
141
|
+
if (x.type === 'exposure')
|
|
142
|
+
return `${sev}ifşa: ${x.url || ''} — ${x.evidence || ''}`;
|
|
134
143
|
if (x.value)
|
|
135
144
|
return `${x.value}`;
|
|
136
145
|
return JSON.stringify(x);
|
|
137
146
|
}
|
|
138
147
|
// /recon /scan /xss /sqli — ince runner'ı sürer. Zorunlu yetki onayı + trust 3+ teaser.
|
|
139
148
|
async function pentestCmd(tool, arg, ctx) {
|
|
149
|
+
if (tool === 'scan')
|
|
150
|
+
tool = 'recon'; // genel tarama = keşif+başlıklar
|
|
140
151
|
const parts = (arg || '').trim().split(/\s+/).filter(Boolean);
|
|
141
152
|
let scopeAck = false;
|
|
142
|
-
if (parts.length && /^(onayla|--yes|-y|yes|evet)$/i.test(parts[parts.length - 1])) {
|
|
153
|
+
if (parts.length && /^(run|onayla|çalıştır|calistir|--yes|-y|yes|evet)$/i.test(parts[parts.length - 1])) {
|
|
143
154
|
scopeAck = true;
|
|
144
155
|
parts.pop();
|
|
145
156
|
}
|
|
@@ -154,7 +165,7 @@ async function pentestCmd(tool, arg, ctx) {
|
|
|
154
165
|
`Hedef: ${target}\n\n` +
|
|
155
166
|
`Bu tarama hedefe GERÇEK istekler gönderir ve trafik SENİN IP'nden çıkar. Yalnız sahibi olduğun ya da\n` +
|
|
156
167
|
`yazılı izin/angajman bulunan sistemlerde çalıştır. Yetkisiz tarama yasa dışıdır ve kayıt altına alınır.\n\n` +
|
|
157
|
-
|
|
168
|
+
`Çalıştırmak için sonuna "run" ekle: /${tool} ${target} run`);
|
|
158
169
|
return;
|
|
159
170
|
}
|
|
160
171
|
ctx.note(`${label} başlatılıyor — ${target}`);
|
|
@@ -169,9 +180,6 @@ async function pentestCmd(tool, arg, ctx) {
|
|
|
169
180
|
}
|
|
170
181
|
const rep = out.report || {};
|
|
171
182
|
const lines = [`✓ ${label} tamamlandı — ${rep.target || target}`];
|
|
172
|
-
if (out.missing && out.missing.length) {
|
|
173
|
-
lines.push(`⚠ Kurulu olmayan araç(lar) atlandı: ${out.missing.join(', ')}`);
|
|
174
|
-
}
|
|
175
183
|
const f = rep.findings || [];
|
|
176
184
|
if (!f.length) {
|
|
177
185
|
lines.push('Bulgu yok (hedef yanıt vermedi, temiz, ya da araç kurulu değil).');
|
|
@@ -732,6 +740,22 @@ export async function runSlashCommand(input, ctx) {
|
|
|
732
740
|
case '/sqli':
|
|
733
741
|
await pentestCmd(cmd.slice(1), arg, ctx);
|
|
734
742
|
return true;
|
|
743
|
+
case '/skill': {
|
|
744
|
+
const m = (arg || '').trim().split(/\s+/).filter(Boolean);
|
|
745
|
+
const sub = (m.shift() || '').toLowerCase();
|
|
746
|
+
if (['xss', 'sqli', 'recon', 'scan'].includes(sub)) {
|
|
747
|
+
await pentestCmd(sub, m.join(' '), ctx);
|
|
748
|
+
return true;
|
|
749
|
+
}
|
|
750
|
+
ctx.note('Güvenlik tarama skill\'leri (seviye 3+ · ayrı kurulum gerekmez, motor sunucuda):\n' +
|
|
751
|
+
' /skill xss <url> reflected XSS\n' +
|
|
752
|
+
' /skill sqli <url> SQL injection (error/boolean/time)\n' +
|
|
753
|
+
' /skill recon <alan> alt-alan + başlık + ifşa\n' +
|
|
754
|
+
'Çalıştırmak için sonuna "run" ekle:\n' +
|
|
755
|
+
' /skill xss https://site/p?id=1 run\n' +
|
|
756
|
+
'(kısa yol: /xss /sqli /recon)');
|
|
757
|
+
return true;
|
|
758
|
+
}
|
|
735
759
|
case '/export': {
|
|
736
760
|
fs.mkdirSync(SESSION_DIR, { recursive: true });
|
|
737
761
|
const file = path.join(SESSION_DIR, `session-${tsStamp()}.json`);
|
package/dist/pentest.js
CHANGED
|
@@ -1,222 +1,4 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
// Bu modül JENERİK bir çalıştırıcıdır: hiç payload/şablon/araç-seçimi içermez.
|
|
4
|
-
// Sunucudan bir komut PLANI alır, komutları YEREL makinede çalıştırır (trafik kullanıcının
|
|
5
|
-
// IP'sinden çıkar), ham çıktıyı sunucuya geri gönderir. Tüm zeka sunucuda kalır.
|
|
6
|
-
//
|
|
7
|
-
// Güvenlik: yalnız PT_ALLOWED'daki ikililer çalıştırılır (ele geçirilmiş/kötü sunucunun
|
|
8
|
-
// istemcide rastgele komut çalıştırmasını engeller). Komutlar SHELL'siz spawn edilir (enjeksiyon yok).
|
|
9
|
-
import { spawn, spawnSync } from 'node:child_process';
|
|
10
|
-
import { statSync, mkdirSync, writeFileSync, readdirSync, renameSync, chmodSync, rmSync } from 'node:fs';
|
|
11
|
-
import * as path from 'node:path';
|
|
12
|
-
import * as os from 'node:os';
|
|
13
|
-
// İstemci-tarafı allowlist (sunucudakiyle aynı; çift güvence).
|
|
14
|
-
export const PT_ALLOWED = new Set(['httpx', 'nuclei', 'dalfox', 'sqlmap', 'subfinder', 'katana', 'nmap', 'ffuf']);
|
|
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
|
-
}
|
|
20
|
-
// İkiliyi PATH üzerinde çöz. Windows'ta Node spawn(shell:false) PATHEXT aramaz →
|
|
21
|
-
// 'dalfox' verilse 'dalfox.exe'yi bulamaz; bu yüzden elle çözüyoruz.
|
|
22
|
-
// Döner: {path, viaCmd} — viaCmd ise .cmd/.bat olduğu için cmd.exe ile sarılır.
|
|
23
|
-
export function resolveBin(bin) {
|
|
24
|
-
const isWin = process.platform === 'win32';
|
|
25
|
-
const exts = isWin ? (process.env.PATHEXT || '.EXE;.CMD;.BAT;.COM').split(';').filter(Boolean) : [''];
|
|
26
|
-
const isFile = (p) => { try {
|
|
27
|
-
return statSync(p).isFile();
|
|
28
|
-
}
|
|
29
|
-
catch {
|
|
30
|
-
return false;
|
|
31
|
-
} };
|
|
32
|
-
const classify = (p) => ({ path: p, viaCmd: /\.(cmd|bat)$/i.test(p) });
|
|
33
|
-
// Mutlak/parçalı yol verildiyse doğrudan dene.
|
|
34
|
-
if (bin.includes('/') || bin.includes('\\')) {
|
|
35
|
-
if (isFile(bin))
|
|
36
|
-
return classify(bin);
|
|
37
|
-
for (const e of exts)
|
|
38
|
-
if (e && isFile(bin + e))
|
|
39
|
-
return classify(bin + e);
|
|
40
|
-
return null;
|
|
41
|
-
}
|
|
42
|
-
// Önce CLI'nin yönettiği dizin, sonra sistem PATH.
|
|
43
|
-
const dirs = [managedBinDir(), ...(process.env.PATH || '').split(path.delimiter)].filter(Boolean);
|
|
44
|
-
for (const d of dirs) {
|
|
45
|
-
const base = path.join(d, bin);
|
|
46
|
-
if (!isWin && isFile(base))
|
|
47
|
-
return classify(base);
|
|
48
|
-
for (const e of exts) {
|
|
49
|
-
const cand = e ? base + e : base;
|
|
50
|
-
if (isFile(cand))
|
|
51
|
-
return classify(cand);
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
return null;
|
|
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
|
-
}
|
|
1
|
+
const MAX_BODY = 262_144; // 256 KB cevap gövdesi üst sınırı
|
|
220
2
|
async function postJson(config, p, body, timeoutMs) {
|
|
221
3
|
try {
|
|
222
4
|
const r = await fetch(`${config.baseUrl}${p}`, {
|
|
@@ -234,78 +16,69 @@ async function postJson(config, p, body, timeoutMs) {
|
|
|
234
16
|
return { ok: false, reason: e?.name === 'TimeoutError' ? 'timeout' : 'upstream_error' };
|
|
235
17
|
}
|
|
236
18
|
}
|
|
237
|
-
function
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
19
|
+
function hostOf(u) {
|
|
20
|
+
try {
|
|
21
|
+
return new URL(u).hostname.toLowerCase();
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return '';
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
// Tek bir istek-spec'ini yerelde çalıştır. allowed dışı host → atlanır (SSRF koruması).
|
|
28
|
+
async function execReq(req, allowed) {
|
|
29
|
+
const t0 = Date.now();
|
|
30
|
+
const h = hostOf(req.url);
|
|
31
|
+
if (!allowed.has(h)) {
|
|
32
|
+
return { id: req.id, ms: 0, error: `izin-dışı host: ${h}` };
|
|
33
|
+
}
|
|
34
|
+
try {
|
|
35
|
+
const r = await fetch(req.url, {
|
|
36
|
+
method: req.method || 'GET',
|
|
37
|
+
headers: req.headers,
|
|
38
|
+
body: req.body,
|
|
39
|
+
redirect: 'follow',
|
|
40
|
+
signal: AbortSignal.timeout(Math.max(3, req.timeout || 20) * 1000),
|
|
41
|
+
});
|
|
42
|
+
let body = '';
|
|
252
43
|
try {
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
: spawn(found.path, step.args, { shell: false, windowsHide: true });
|
|
256
|
-
}
|
|
257
|
-
catch {
|
|
258
|
-
resolve({ id: step.id, ran: false, exit: -1, stdout: '', stderr: `başlatılamadı (kurulu değil?): ${step.bin}` });
|
|
259
|
-
return;
|
|
44
|
+
const buf = await r.arrayBuffer();
|
|
45
|
+
body = Buffer.from(buf).toString('utf8').slice(0, MAX_BODY);
|
|
260
46
|
}
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
child.on('error', () => {
|
|
270
|
-
if (done)
|
|
271
|
-
return;
|
|
272
|
-
done = true;
|
|
273
|
-
clearTimeout(to);
|
|
274
|
-
resolve({ id: step.id, ran: false, exit: -1, stdout: out, stderr: `çalıştırılamadı (kurulu değil?): ${step.bin}` });
|
|
275
|
-
});
|
|
276
|
-
child.on('close', (code) => {
|
|
277
|
-
if (done)
|
|
278
|
-
return;
|
|
279
|
-
done = true;
|
|
280
|
-
clearTimeout(to);
|
|
281
|
-
resolve({ id: step.id, ran: true, exit: code ?? -1, stdout: out, stderr: err });
|
|
282
|
-
});
|
|
283
|
-
});
|
|
47
|
+
catch { /* gövde okunamadı */ }
|
|
48
|
+
const headers = {};
|
|
49
|
+
r.headers.forEach((v, k) => { headers[k] = v; });
|
|
50
|
+
return { id: req.id, status: r.status, headers, body, ms: Date.now() - t0, url: r.url };
|
|
51
|
+
}
|
|
52
|
+
catch (e) {
|
|
53
|
+
return { id: req.id, ms: Date.now() - t0, error: e?.name === 'TimeoutError' ? 'timeout' : (e?.message || 'fetch error').slice(0, 80) };
|
|
54
|
+
}
|
|
284
55
|
}
|
|
285
|
-
// Tam akış: plan al → yerelde
|
|
56
|
+
// Tam akış: plan al → her round'da istekleri yerelde at → /exec ile analiz ettir → done'a dek döngü.
|
|
286
57
|
export async function runPentest(config, tool, target, scopeAck, log) {
|
|
287
58
|
const plan = await postJson(config, '/pentest/plan', { tool, target, scope_ack: scopeAck }, 15000);
|
|
288
59
|
if (!plan.ok)
|
|
289
60
|
return { ok: false, reason: plan.reason, plan };
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
log(`
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
if (!
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
61
|
+
const allowed = new Set((plan.allowed_hosts || [plan.host || '']).map((x) => x.toLowerCase()));
|
|
62
|
+
let requests = plan.requests || [];
|
|
63
|
+
let round = plan.round || 1;
|
|
64
|
+
let report = null;
|
|
65
|
+
for (let guard = 0; guard < 8 && requests.length; guard++) {
|
|
66
|
+
log(`Tur ${round}: ${requests.length} istek yerelde atılıyor — trafik senin IP'nden çıkıyor…`);
|
|
67
|
+
// İstekleri sınırlı eşzamanlılıkla çalıştır (hedefi yormamak için 6'şarlı)
|
|
68
|
+
const responses = [];
|
|
69
|
+
const CONC = 6;
|
|
70
|
+
for (let i = 0; i < requests.length; i += CONC) {
|
|
71
|
+
const batch = requests.slice(i, i + CONC);
|
|
72
|
+
const rs = await Promise.all(batch.map((req) => execReq(req, allowed)));
|
|
73
|
+
responses.push(...rs);
|
|
74
|
+
}
|
|
75
|
+
report = await postJson(config, '/pentest/exec', { job_id: plan.job_id, responses }, 30000);
|
|
76
|
+
if (!report.ok)
|
|
77
|
+
return { ok: false, reason: report.reason, plan, report };
|
|
78
|
+
if (report.done)
|
|
79
|
+
break;
|
|
80
|
+
requests = report.requests || [];
|
|
81
|
+
round = report.round || round + 1;
|
|
82
|
+
}
|
|
83
|
+
return { ok: true, plan, report };
|
|
311
84
|
}
|