wormclaude 1.0.181 → 1.0.183
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 +126 -5
- package/dist/theme.js +1 -1
- package/dist/tools.js +38 -8
- package/package.json +1 -1
package/dist/pentest.js
CHANGED
|
@@ -41,8 +41,127 @@ function hostOf(u) {
|
|
|
41
41
|
return '';
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
|
+
// ── FAZ 4c: LOGIN OTOMASYONU (cred → session cookie; tamamen istemci-tarafı, sır sunucuya gitmez) ──
|
|
45
|
+
function _setCookies(res) {
|
|
46
|
+
// Node 18.14+ getSetCookie() tüm Set-Cookie satırlarını verir; yoksa tek başlığa düş.
|
|
47
|
+
try {
|
|
48
|
+
const f = res.headers.getSetCookie;
|
|
49
|
+
if (typeof f === 'function')
|
|
50
|
+
return f.call(res.headers) || [];
|
|
51
|
+
}
|
|
52
|
+
catch { }
|
|
53
|
+
const one = res.headers.get('set-cookie');
|
|
54
|
+
return one ? [one] : [];
|
|
55
|
+
}
|
|
56
|
+
function _mergeJar(jar, setCookies) {
|
|
57
|
+
for (const sc of setCookies) {
|
|
58
|
+
const kv = sc.split(';', 1)[0];
|
|
59
|
+
const eq = kv.indexOf('=');
|
|
60
|
+
if (eq > 0)
|
|
61
|
+
jar[kv.slice(0, eq).trim()] = kv.slice(eq + 1).trim();
|
|
62
|
+
}
|
|
63
|
+
return jar;
|
|
64
|
+
}
|
|
65
|
+
function _cookieHeader(jar) {
|
|
66
|
+
return Object.entries(jar).map(([k, v]) => `${k}=${v}`).join('; ');
|
|
67
|
+
}
|
|
68
|
+
function _attrOf(s, key) {
|
|
69
|
+
const m = new RegExp(key + '\\s*=\\s*["\']([^"\']*)', 'i').exec(s);
|
|
70
|
+
return m ? m[1] : '';
|
|
71
|
+
}
|
|
72
|
+
// Login formunu bul: type=password içeren <form>. userField = ilk görünür text/email input; passField =
|
|
73
|
+
// password input; hidden/csrf alanları gerçek değeriyle korunur.
|
|
74
|
+
function _findLoginForm(html, baseUrl) {
|
|
75
|
+
for (const m of html.matchAll(/<form\b([^>]*)>([\s\S]*?)<\/form>/gi)) {
|
|
76
|
+
const attrs = m[1], inner = m[2];
|
|
77
|
+
if (!/<input\b[^>]*type\s*=\s*["']?password/i.test(inner))
|
|
78
|
+
continue;
|
|
79
|
+
let passField = '', userField = '', hidden = {};
|
|
80
|
+
for (const t of inner.matchAll(/<(?:input|select|textarea)\b([^>]*)>/gi)) {
|
|
81
|
+
const ia = t[1];
|
|
82
|
+
const name = _attrOf(ia, 'name');
|
|
83
|
+
if (!name)
|
|
84
|
+
continue;
|
|
85
|
+
const typ = (_attrOf(ia, 'type') || 'text').toLowerCase();
|
|
86
|
+
if (typ === 'password')
|
|
87
|
+
passField = name;
|
|
88
|
+
else if (typ === 'hidden')
|
|
89
|
+
hidden[name] = _attrOf(ia, 'value');
|
|
90
|
+
else if (!userField && ['text', 'email', 'tel', 'username'].includes(typ))
|
|
91
|
+
userField = name;
|
|
92
|
+
}
|
|
93
|
+
if (!passField)
|
|
94
|
+
continue;
|
|
95
|
+
if (!userField)
|
|
96
|
+
userField = 'username';
|
|
97
|
+
let action = baseUrl;
|
|
98
|
+
try {
|
|
99
|
+
action = new URL(_attrOf(attrs, 'action') || baseUrl, baseUrl).toString();
|
|
100
|
+
}
|
|
101
|
+
catch { }
|
|
102
|
+
const method = (_attrOf(attrs, 'method') || 'post').toLowerCase();
|
|
103
|
+
return { action, method, userField, passField, hidden };
|
|
104
|
+
}
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
/** Otomatik login: login sayfasını çek → formu bul (CSRF korunur) → cred ile POST → Set-Cookie yakala
|
|
108
|
+
* → session cookie string döndür. Trafik kullanıcı IP'sinden; cookie/cred SUNUCUYA GİTMEZ. */
|
|
109
|
+
export async function autoLogin(loginUrl, username, password, extra) {
|
|
110
|
+
let jar = {};
|
|
111
|
+
try {
|
|
112
|
+
const r0 = await fetch(loginUrl, { headers: { 'User-Agent': 'Mozilla/5.0' }, redirect: 'follow', signal: AbortSignal.timeout(20000) });
|
|
113
|
+
_mergeJar(jar, _setCookies(r0));
|
|
114
|
+
const html = await r0.text();
|
|
115
|
+
const form = _findLoginForm(html, r0.url || loginUrl);
|
|
116
|
+
if (!form)
|
|
117
|
+
return { ok: false, cookie: '', note: 'Login formu bulunamadı (password alanı yok). login_url doğru mu?' };
|
|
118
|
+
const data = { ...form.hidden, [form.userField]: username, [form.passField]: password, ...(extra || {}) };
|
|
119
|
+
const r1 = await fetch(form.action, {
|
|
120
|
+
method: 'POST',
|
|
121
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'User-Agent': 'Mozilla/5.0', ...(Object.keys(jar).length ? { Cookie: _cookieHeader(jar) } : {}) },
|
|
122
|
+
body: new URLSearchParams(data).toString(),
|
|
123
|
+
redirect: 'manual', signal: AbortSignal.timeout(20000),
|
|
124
|
+
});
|
|
125
|
+
_mergeJar(jar, _setCookies(r1));
|
|
126
|
+
// login sonrası 30x yönlendirme genelde asıl session cookie'sini set eder → bir adım takip et
|
|
127
|
+
const loc = r1.headers.get('location');
|
|
128
|
+
if (loc && [301, 302, 303, 307, 308].includes(r1.status)) {
|
|
129
|
+
try {
|
|
130
|
+
const nu = new URL(loc, form.action).toString();
|
|
131
|
+
const r2 = await fetch(nu, { headers: { 'User-Agent': 'Mozilla/5.0', ...(Object.keys(jar).length ? { Cookie: _cookieHeader(jar) } : {}) }, redirect: 'manual', signal: AbortSignal.timeout(15000) });
|
|
132
|
+
_mergeJar(jar, _setCookies(r2));
|
|
133
|
+
}
|
|
134
|
+
catch { }
|
|
135
|
+
}
|
|
136
|
+
if (!Object.keys(jar).length)
|
|
137
|
+
return { ok: false, cookie: '', note: 'Login denendi ama Set-Cookie gelmedi (kimlik hatalı ya da JS/token tabanlı login olabilir).' };
|
|
138
|
+
return { ok: true, cookie: _cookieHeader(jar), note: `Login OK — ${Object.keys(jar).length} cookie alındı (oturumlu tarama).` };
|
|
139
|
+
}
|
|
140
|
+
catch (e) {
|
|
141
|
+
return { ok: false, cookie: '', note: 'Login hatası: ' + (e?.name === 'TimeoutError' ? 'zaman aşımı' : (e?.message || e)) };
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
// İstek başlıklarına kullanıcı oturumunu (auth) birleştir. Sunucunun spec'i temel; auth ÜSTE biner
|
|
145
|
+
// (Cookie + Authorization), böylece her istek login-arkası koşar. Sunucu zaten Cookie/Authorization
|
|
146
|
+
// koyduysa kullanıcınınki kazanır (gerçek oturum). allowed-host SSRF guard'ı değişmez.
|
|
147
|
+
export function _mergeAuth(reqHeaders, auth) {
|
|
148
|
+
if (!auth || (!auth.cookie && !auth.authHeader && !auth.headers))
|
|
149
|
+
return reqHeaders;
|
|
150
|
+
const h = { ...(reqHeaders || {}) };
|
|
151
|
+
if (auth.cookie) {
|
|
152
|
+
const existing = h['Cookie'] || h['cookie'];
|
|
153
|
+
h['Cookie'] = existing ? `${existing}; ${auth.cookie}` : auth.cookie;
|
|
154
|
+
delete h['cookie'];
|
|
155
|
+
}
|
|
156
|
+
if (auth.authHeader)
|
|
157
|
+
h['Authorization'] = auth.authHeader;
|
|
158
|
+
if (auth.headers)
|
|
159
|
+
for (const [k, v] of Object.entries(auth.headers))
|
|
160
|
+
h[k] = v;
|
|
161
|
+
return h;
|
|
162
|
+
}
|
|
44
163
|
// Tek bir istek-spec'ini yerelde çalıştır. allowed dışı host → atlanır (SSRF koruması).
|
|
45
|
-
async function execReq(req, allowed) {
|
|
164
|
+
async function execReq(req, allowed, auth) {
|
|
46
165
|
const t0 = Date.now();
|
|
47
166
|
const h = hostOf(req.url);
|
|
48
167
|
if (!allowed.has(h)) {
|
|
@@ -51,7 +170,7 @@ async function execReq(req, allowed) {
|
|
|
51
170
|
try {
|
|
52
171
|
const r = await fetch(req.url, {
|
|
53
172
|
method: req.method || 'GET',
|
|
54
|
-
headers: req.headers,
|
|
173
|
+
headers: _mergeAuth(req.headers, auth),
|
|
55
174
|
body: req.body,
|
|
56
175
|
redirect: req.follow === false ? 'manual' : 'follow', // open-redirect vb. için Location'ı görmek
|
|
57
176
|
signal: AbortSignal.timeout(Math.max(3, req.timeout || 20) * 1000),
|
|
@@ -71,8 +190,10 @@ async function execReq(req, allowed) {
|
|
|
71
190
|
}
|
|
72
191
|
}
|
|
73
192
|
// Tam akış: plan al → her round'da istekleri yerelde at → /exec ile analiz ettir → done'a dek döngü.
|
|
74
|
-
export async function runPentest(config, tool, target, scopeAck, log) {
|
|
75
|
-
const
|
|
193
|
+
export async function runPentest(config, tool, target, scopeAck, log, auth) {
|
|
194
|
+
const _authed = !!(auth && (auth.cookie || auth.authHeader || auth.headers));
|
|
195
|
+
// authenticated: SUNUCUYA SADECE BAYRAK gider (login-arkası crawl/POST planlasın diye) — sır/cookie DEĞİL.
|
|
196
|
+
const plan = await postJson(config, '/pentest/plan', { tool, target, scope_ack: scopeAck, authenticated: _authed }, 15000);
|
|
76
197
|
if (!plan.ok)
|
|
77
198
|
return { ok: false, reason: plan.reason, plan };
|
|
78
199
|
const allowed = new Set((plan.allowed_hosts || [plan.host || '']).map((x) => x.toLowerCase()));
|
|
@@ -86,7 +207,7 @@ export async function runPentest(config, tool, target, scopeAck, log) {
|
|
|
86
207
|
const CONC = 6;
|
|
87
208
|
for (let i = 0; i < requests.length; i += CONC) {
|
|
88
209
|
const batch = requests.slice(i, i + CONC);
|
|
89
|
-
const rs = await Promise.all(batch.map((req) => execReq(req, allowed)));
|
|
210
|
+
const rs = await Promise.all(batch.map((req) => execReq(req, allowed, auth)));
|
|
90
211
|
responses.push(...rs);
|
|
91
212
|
}
|
|
92
213
|
report = await postJson(config, '/pentest/exec', { job_id: plan.job_id, responses }, 30000);
|
package/dist/theme.js
CHANGED
package/dist/tools.js
CHANGED
|
@@ -226,12 +226,17 @@ export const toolSchemas = [
|
|
|
226
226
|
type: 'function',
|
|
227
227
|
function: {
|
|
228
228
|
name: 'SecurityScan',
|
|
229
|
-
description: "Run an AUTHORIZED security scan with WormClaude's own server-side engine (no external tools, no manual payloads). Use THIS — never shell/curl/Invoke-WebRequest — whenever the user asks to scan/test a target (\"tara\", \"scan\", \"xss tara\", \"sqli\", \"recon\", \"açık bul\", \"zafiyet tara\", \"tam kapsamlı tara\"). For a FULL/comprehensive scan use tool='full' ONCE — it deterministically runs recon+XSS+SQLi in code and returns ONE consolidated report (do NOT chain recon/xss/sqli yourself, that loops). For a single check use recon/xss/sqli. For xss/sqli give a PARAMETERIZED url; for recon/full give a domain. Requires authorized-researcher level (3+); traffic exits the user's IP — only authorized targets.",
|
|
229
|
+
description: "Run an AUTHORIZED security scan with WormClaude's own server-side engine (no external tools, no manual payloads). Use THIS — never shell/curl/Invoke-WebRequest — whenever the user asks to scan/test a target (\"tara\", \"scan\", \"xss tara\", \"sqli\", \"recon\", \"açık bul\", \"zafiyet tara\", \"tam kapsamlı tara\"). For a FULL/comprehensive scan use tool='full' ONCE — it deterministically runs recon+XSS+SQLi in code and returns ONE consolidated report (do NOT chain recon/xss/sqli yourself, that loops). For a single check use recon/xss/sqli. For xss/sqli give a PARAMETERIZED url; for recon/full give a domain. For AUTHENTICATED / behind-login scanning, pass the user's session via `cookie` (and/or `auth_header`) — every request is then sent logged-in, so pages and parameters behind the login are reached. Requires authorized-researcher level (3+); traffic exits the user's IP — only authorized targets.",
|
|
230
230
|
parameters: {
|
|
231
231
|
type: 'object',
|
|
232
232
|
properties: {
|
|
233
233
|
tool: { type: 'string', enum: ['full', 'recon', 'scan', 'xss', 'sqli', 'dirscan'], description: 'full → tam kapsamlı (recon+xss+sqli tek seferde, kapsamlı tarama için BUNU kullan); recon/scan/dirscan → domain; xss/sqli → parameterized URL' },
|
|
234
234
|
target: { type: 'string', description: 'Domain for recon/scan/dirscan (example.com) OR parameterized URL for xss/sqli (https://example.com/p?id=1)' },
|
|
235
|
+
cookie: { type: 'string', description: 'Optional. Session cookie for AUTHENTICATED / behind-login scanning, e.g. "sessionid=abc123; csrftoken=xyz". The user copies it from their logged-in browser (DevTools → Application → Cookies, or the Cookie request header). Sent only to the target from the user IP — never stored on the server. Provide it whenever the user wants to scan pages behind a login.' },
|
|
236
|
+
auth_header: { type: 'string', description: 'Optional. Authorization header value for token-based authenticated scanning, e.g. "Bearer eyJhbGci..." or "Basic dXNlcjpwYXNz". Use instead of / together with cookie when the site authenticates via a token header.' },
|
|
237
|
+
login_url: { type: 'string', description: 'Optional. URL of the login PAGE for AUTO-LOGIN. When the user gives credentials instead of a cookie, set this + username + password; the scanner fetches the login form, submits the credentials (preserving CSRF), captures the session cookie, and scans logged-in. Login happens from the user IP; credentials/cookie never reach the server.' },
|
|
238
|
+
username: { type: 'string', description: 'Optional. Username/email for auto-login (use with login_url + password).' },
|
|
239
|
+
password: { type: 'string', description: 'Optional. Password for auto-login (use with login_url + username).' },
|
|
235
240
|
},
|
|
236
241
|
required: ['tool', 'target'],
|
|
237
242
|
},
|
|
@@ -737,10 +742,35 @@ async function execOne(call, hooks) {
|
|
|
737
742
|
}
|
|
738
743
|
if (tool === 'scan')
|
|
739
744
|
tool = 'recon'; // "scan" genel tarama → motorun bildiği "recon" (slash ile aynı)
|
|
745
|
+
// AUTHENTICATED / login-arkası: kullanıcı session cookie / auth header verdiyse her isteğe enjekte
|
|
746
|
+
// edilir (sır SUNUCUYA gitmez, sadece hedefe kullanıcı IP'sinden). Cache key'e auth-parmakizi ekle ki
|
|
747
|
+
// anonim tarama sonucu authenticated tarama için (veya tersi) yeniden kullanılmasın.
|
|
748
|
+
let _cookie = String(args?.cookie || '').trim();
|
|
749
|
+
const _authHeader = String(args?.auth_header || '').trim();
|
|
750
|
+
// FAZ 4c: login_url + username + password verildiyse OTOMATİK login ol → session cookie'yi al
|
|
751
|
+
// (manuel cookie kopyalamaya gerek yok). İstemci-tarafı; cred/cookie sunucuya gitmez.
|
|
752
|
+
const _loginUrl = String(args?.login_url || '').trim();
|
|
753
|
+
const _username = String(args?.username || '').trim();
|
|
754
|
+
const _password = String(args?.password || '');
|
|
755
|
+
let _loginNote = '';
|
|
756
|
+
if (_loginUrl && _username && _password && !_cookie) {
|
|
757
|
+
try {
|
|
758
|
+
const { autoLogin } = await import('./pentest.js');
|
|
759
|
+
const lr = await autoLogin(_loginUrl, _username, _password);
|
|
760
|
+
_loginNote = `\n[login] ${lr.note}`;
|
|
761
|
+
if (lr.ok && lr.cookie)
|
|
762
|
+
_cookie = lr.cookie;
|
|
763
|
+
}
|
|
764
|
+
catch (e) {
|
|
765
|
+
_loginNote = `\n[login] hata: ${e?.message || e}`;
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
const _auth = (_cookie || _authHeader) ? { cookie: _cookie || undefined, authHeader: _authHeader || undefined } : undefined;
|
|
769
|
+
const _authTag = _auth ? '|auth' : '';
|
|
740
770
|
// FULL: tam kapsamlı tarama TEK ÇAĞRIDA — model orkestrasyona girmesin (döngü olur). Kod
|
|
741
771
|
// deterministik olarak recon → xss → sqli koşar, TEK konsolide rapor döner.
|
|
742
772
|
if (tool === 'full' || tool === 'fullscan') {
|
|
743
|
-
const _fk = 'full|' + target.toLowerCase();
|
|
773
|
+
const _fk = 'full|' + target.toLowerCase() + _authTag;
|
|
744
774
|
const _fc = _scanCache.get(_fk);
|
|
745
775
|
if (_fc && Date.now() - _fc.t < 120000) {
|
|
746
776
|
return { ok: false, args, output: `[ALREADY-SCANNED] full scan "${target}" already ran — findings are above. Do NOT call SecurityScan again; summarize the results in text.` };
|
|
@@ -748,14 +778,14 @@ async function execOne(call, hooks) {
|
|
|
748
778
|
try {
|
|
749
779
|
const { runPentest } = await import('./pentest.js');
|
|
750
780
|
const all = [];
|
|
751
|
-
let rpt = `Tam kapsamlı tarama — ${target}\n`;
|
|
781
|
+
let rpt = `Tam kapsamlı tarama — ${target}${_auth ? ' (authenticated / login-arkası — oturum enjekte edildi)' : ''}${_loginNote}\n`;
|
|
752
782
|
for (const _t of ['recon', 'xss', 'sqli']) {
|
|
753
783
|
if (hooks?.signal?.aborted) {
|
|
754
784
|
rpt += `\n[STOPPED] kullanıcı durdurdu (Esc) — kalan taramalar atlandı.`;
|
|
755
785
|
break;
|
|
756
786
|
}
|
|
757
787
|
try {
|
|
758
|
-
const o = await runPentest(cfg(), _t, target, true, () => { });
|
|
788
|
+
const o = await runPentest(cfg(), _t, target, true, () => { }, _auth);
|
|
759
789
|
if (o?.ok) {
|
|
760
790
|
const ff = o.report?.findings || [];
|
|
761
791
|
all.push(...ff.map((x) => ({ ...x, scan: _t })));
|
|
@@ -782,7 +812,7 @@ async function execOne(call, hooks) {
|
|
|
782
812
|
_scanCache.set(_fk, { out: rpt, t: _now });
|
|
783
813
|
// Alt tarama tiplerini de cache'le → full sonrası model tek tek recon/xss/sqli çağıramaz (gereksiz tekrar).
|
|
784
814
|
for (const _t of ['recon', 'xss', 'sqli'])
|
|
785
|
-
_scanCache.set(_t + '|' + target.toLowerCase(), { out: rpt, t: _now });
|
|
815
|
+
_scanCache.set(_t + '|' + target.toLowerCase() + _authTag, { out: rpt, t: _now });
|
|
786
816
|
return { ok: true, output: rpt, args };
|
|
787
817
|
}
|
|
788
818
|
catch (e) {
|
|
@@ -790,7 +820,7 @@ async function execOne(call, hooks) {
|
|
|
790
820
|
}
|
|
791
821
|
}
|
|
792
822
|
// DÖNGÜ-KIRICI: aynı tarama 2 dk içinde tekrar → yeniden koşma, önceki sonuç + "yeniden tarama" notu.
|
|
793
|
-
const _ck = tool + '|' + target.toLowerCase();
|
|
823
|
+
const _ck = tool + '|' + target.toLowerCase() + _authTag;
|
|
794
824
|
const _cached = _scanCache.get(_ck);
|
|
795
825
|
if (_cached && Date.now() - _cached.t < 120000) {
|
|
796
826
|
// SADECE kısa DUR notu — bulguları TEKRAR verme (yoksa model onları yeniden yazıp tekrar çağırır).
|
|
@@ -798,7 +828,7 @@ async function execOne(call, hooks) {
|
|
|
798
828
|
}
|
|
799
829
|
try {
|
|
800
830
|
const { runPentest } = await import('./pentest.js');
|
|
801
|
-
const out = await runPentest(cfg(), tool, target, true, () => { });
|
|
831
|
+
const out = await runPentest(cfg(), tool, target, true, () => { }, _auth);
|
|
802
832
|
if (!out?.ok) {
|
|
803
833
|
const reason = out?.reason || out?.plan?.reason || 'error';
|
|
804
834
|
const tip = reason === 'trust_required' ? ' (Doğrulanmış Araştırmacı / seviye 3+ gerekir: /dogrula)'
|
|
@@ -807,7 +837,7 @@ async function execOne(call, hooks) {
|
|
|
807
837
|
}
|
|
808
838
|
const rep = out.report || {};
|
|
809
839
|
const f = rep.findings || [];
|
|
810
|
-
let txt = `Tarama tamam (${tool}) — ${rep.target || target}\n`;
|
|
840
|
+
let txt = `Tarama tamam (${tool}) — ${rep.target || target}${_loginNote}\n`;
|
|
811
841
|
if (!f.length)
|
|
812
842
|
txt += 'Zafiyet bulunamadı (hedef temiz ya da yanıt vermedi).';
|
|
813
843
|
else
|