wormclaude 1.0.182 → 1.0.184
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 +100 -0
- package/dist/theme.js +1 -1
- package/dist/tools.js +60 -13
- package/package.json +1 -1
package/dist/pentest.js
CHANGED
|
@@ -41,6 +41,106 @@ 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
|
+
}
|
|
44
144
|
// İstek başlıklarına kullanıcı oturumunu (auth) birleştir. Sunucunun spec'i temel; auth ÜSTE biner
|
|
45
145
|
// (Cookie + Authorization), böylece her istek login-arkası koşar. Sunucu zaten Cookie/Authorization
|
|
46
146
|
// koyduysa kullanıcınınki kazanır (gerçek oturum). allowed-host SSRF guard'ı değişmez.
|
package/dist/theme.js
CHANGED
package/dist/tools.js
CHANGED
|
@@ -234,6 +234,9 @@ export const toolSchemas = [
|
|
|
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
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
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).' },
|
|
237
240
|
},
|
|
238
241
|
required: ['tool', 'target'],
|
|
239
242
|
},
|
|
@@ -742,8 +745,26 @@ async function execOne(call, hooks) {
|
|
|
742
745
|
// AUTHENTICATED / login-arkası: kullanıcı session cookie / auth header verdiyse her isteğe enjekte
|
|
743
746
|
// edilir (sır SUNUCUYA gitmez, sadece hedefe kullanıcı IP'sinden). Cache key'e auth-parmakizi ekle ki
|
|
744
747
|
// anonim tarama sonucu authenticated tarama için (veya tersi) yeniden kullanılmasın.
|
|
745
|
-
|
|
748
|
+
let _cookie = String(args?.cookie || '').trim();
|
|
746
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
|
+
}
|
|
747
768
|
const _auth = (_cookie || _authHeader) ? { cookie: _cookie || undefined, authHeader: _authHeader || undefined } : undefined;
|
|
748
769
|
const _authTag = _auth ? '|auth' : '';
|
|
749
770
|
// FULL: tam kapsamlı tarama TEK ÇAĞRIDA — model orkestrasyona girmesin (döngü olur). Kod
|
|
@@ -755,10 +776,22 @@ async function execOne(call, hooks) {
|
|
|
755
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.` };
|
|
756
777
|
}
|
|
757
778
|
try {
|
|
758
|
-
const { runPentest } = await import('./pentest.js');
|
|
779
|
+
const { runPentest, fetchPtTools } = await import('./pentest.js');
|
|
759
780
|
const all = [];
|
|
760
|
-
|
|
761
|
-
|
|
781
|
+
// PROFESYONEL KAPSAM: çekirdek (recon+dirscan+xss+sqli) + sunucudaki TÜM şablon tarayıcıları
|
|
782
|
+
// (SSRF/XXE/SSTI/LFI/LDAP/open-redirect/CRLF/graphql/exposed-files/waf-detect …). /fullscan ile aynı.
|
|
783
|
+
const _steps = ['recon', 'dirscan', 'xss', 'sqli'];
|
|
784
|
+
try {
|
|
785
|
+
const _pt = await fetchPtTools(cfg());
|
|
786
|
+
for (const tm of ((_pt && _pt.templates) || [])) {
|
|
787
|
+
if (tm && tm.id && !_steps.includes(String(tm.id)))
|
|
788
|
+
_steps.push(String(tm.id));
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
catch { }
|
|
792
|
+
let rpt = `Tam kapsamlı tarama — ${target}${_auth ? ' (authenticated / login-arkası — oturum enjekte edildi)' : ''}${_loginNote}\n${_steps.length} tarayıcı koşuluyor (recon+dirscan+xss+sqli + şablonlar).\n`;
|
|
793
|
+
let _wafSeen = false;
|
|
794
|
+
for (const _t of _steps) {
|
|
762
795
|
if (hooks?.signal?.aborted) {
|
|
763
796
|
rpt += `\n[STOPPED] kullanıcı durdurdu (Esc) — kalan taramalar atlandı.`;
|
|
764
797
|
break;
|
|
@@ -768,29 +801,43 @@ async function execOne(call, hooks) {
|
|
|
768
801
|
if (o?.ok) {
|
|
769
802
|
const ff = o.report?.findings || [];
|
|
770
803
|
all.push(...ff.map((x) => ({ ...x, scan: _t })));
|
|
771
|
-
|
|
804
|
+
if (ff.some((x) => /waf|firewall|cloudflare|akamai|imperva/i.test(JSON.stringify(x))))
|
|
805
|
+
_wafSeen = true;
|
|
806
|
+
rpt += `\n[${_t}] ${ff.length ? ff.length + ' bulgu' : 'temiz'}`;
|
|
772
807
|
}
|
|
773
808
|
else {
|
|
774
809
|
const rsn = o?.reason || o?.plan?.reason || 'error';
|
|
775
|
-
rpt += `\n[${_t}] çalışmadı: ${rsn}`;
|
|
776
810
|
if (rsn === 'trust_required') {
|
|
777
|
-
rpt +=
|
|
811
|
+
rpt += `\n[${_t}] çalışmadı: ${rsn} — Doğrulanmış Araştırmacı (seviye 3+) gerekir.`;
|
|
778
812
|
break;
|
|
779
813
|
}
|
|
814
|
+
rpt += `\n[${_t}] ${rsn === 'invalid_target' ? 'atlandı (param yok)' : 'çalışmadı: ' + rsn}`;
|
|
780
815
|
}
|
|
781
816
|
}
|
|
782
817
|
catch (e) {
|
|
783
818
|
rpt += `\n[${_t}] hata: ${e?.message || e}`;
|
|
784
819
|
}
|
|
785
820
|
}
|
|
786
|
-
|
|
821
|
+
// no_params spam'ini tekille (her inject tarayıcı bir tane ekliyor)
|
|
822
|
+
const _np = all.filter((f) => f.note === 'no_params');
|
|
823
|
+
for (let i = all.length - 1; i >= 0; i--)
|
|
824
|
+
if (all[i].note === 'no_params')
|
|
825
|
+
all.splice(i, 1);
|
|
826
|
+
if (_np.length)
|
|
827
|
+
all.push({ type: 'info', severity: 'info', url: target, note: 'no_params',
|
|
828
|
+
evidence: `${_np.length} enjeksiyon tarayıcısı test edilebilir GET parametresi bulamadı — girdiler POST form/login-arkası/JS-üretimli olabilir.` });
|
|
829
|
+
const _sev = { critical: 0, high: 1, medium: 2, low: 3, info: 4 };
|
|
830
|
+
all.sort((a, b) => (_sev[a.severity] ?? 9) - (_sev[b.severity] ?? 9));
|
|
831
|
+
if (_wafSeen || /cloudflare|akamai|imperva/i.test(rpt)) {
|
|
832
|
+
rpt += `\n\n⚠️ WAF/CDN tespit edildi — payload'lar filtrelenmiş olabilir; "temiz" sonuçlar KESİN değil. Kesin sonuç için origin sunucuya (CDN arkası) doğrudan tara.`;
|
|
833
|
+
}
|
|
834
|
+
rpt += `\n\nToplam ${all.length} bulgu (${_steps.length} tarayıcı).`;
|
|
787
835
|
if (all.length)
|
|
788
|
-
rpt += '\n' + all.slice(0,
|
|
789
|
-
rpt += `\n[full-done]
|
|
836
|
+
rpt += '\n' + all.slice(0, 80).map((x) => ' ' + JSON.stringify(x)).join('\n');
|
|
837
|
+
rpt += `\n[full-done] comprehensive scan (${_steps.length} scanners) ran. Do NOT call SecurityScan again — report the findings to the user in text (severity-ordered summary + prioritized fixes), in their language.`;
|
|
790
838
|
const _now = Date.now();
|
|
791
839
|
_scanCache.set(_fk, { out: rpt, t: _now });
|
|
792
|
-
|
|
793
|
-
for (const _t of ['recon', 'xss', 'sqli'])
|
|
840
|
+
for (const _t of ['recon', 'xss', 'sqli', 'dirscan'])
|
|
794
841
|
_scanCache.set(_t + '|' + target.toLowerCase() + _authTag, { out: rpt, t: _now });
|
|
795
842
|
return { ok: true, output: rpt, args };
|
|
796
843
|
}
|
|
@@ -816,7 +863,7 @@ async function execOne(call, hooks) {
|
|
|
816
863
|
}
|
|
817
864
|
const rep = out.report || {};
|
|
818
865
|
const f = rep.findings || [];
|
|
819
|
-
let txt = `Tarama tamam (${tool}) — ${rep.target || target}\n`;
|
|
866
|
+
let txt = `Tarama tamam (${tool}) — ${rep.target || target}${_loginNote}\n`;
|
|
820
867
|
if (!f.length)
|
|
821
868
|
txt += 'Zafiyet bulunamadı (hedef temiz ya da yanıt vermedi).';
|
|
822
869
|
else
|