Autarch/web/templates/vuln_scanner.html
DigiJ cdde8717d0 v2.3.0 — RCS exploit v2.0, Starlink hack, SMS forge, Archon RCS module
Major RCS/SMS exploitation rewrite (v2.0):
- bugle_db direct extraction (plaintext messages, no decryption needed)
- CVE-2024-0044 run-as privilege escalation (Android 12-13)
- AOSP RCS provider queries (content://rcs/)
- Archon app relay for Shizuku-elevated bugle_db access
- 7-tab web UI: Extract, Database, Forge, Modify, Exploit, Backup, Monitor
- SQL query interface for extracted databases
- Full backup/restore/clone with SMS Backup & Restore XML support
- Known CVE database (CVE-2023-24033, CVE-2024-49415, CVE-2025-48593)
- IMS/RCS diagnostics, Phenotype verbose logging, Pixel tools

New modules: Starlink hack, SMS forge, SDR drone detection
Archon Android app: RCS messaging module with Shizuku integration
Updated manuals to v2.3, 60 web blueprints confirmed

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-03 13:50:59 -08:00

628 lines
29 KiB
HTML

{% extends "base.html" %}
{% block title %}AUTARCH — Vulnerability Scanner{% endblock %}
{% block content %}
<div class="page-header" style="display:flex;align-items:center;gap:1rem;flex-wrap:wrap">
<div>
<h1>Vulnerability Scanner</h1>
<p style="margin:0;font-size:0.85rem;color:var(--text-secondary)">
Port scanning, CVE matching, default credential checking, header &amp; SSL analysis, Nuclei integration.
</p>
</div>
<a href="{{ url_for('offense.index') }}" class="btn btn-sm" style="margin-left:auto">&larr; Offense</a>
</div>
<!-- Tab Bar -->
<div class="tab-bar">
<button class="tab active" data-tab-group="vs" data-tab="scan" onclick="showTab('vs','scan')">Scan</button>
<button class="tab" data-tab-group="vs" data-tab="templates" onclick="showTab('vs','templates');vsLoadTemplates()">Templates</button>
<button class="tab" data-tab-group="vs" data-tab="results" onclick="showTab('vs','results');vsLoadScans()">Results</button>
</div>
<!-- ==================== SCAN TAB ==================== -->
<div class="tab-content active" data-tab-group="vs" data-tab="scan">
<div class="section">
<h2>New Scan</h2>
<div style="display:grid;grid-template-columns:1fr auto;gap:1rem;align-items:end;max-width:900px">
<div>
<div class="form-group" style="margin-bottom:0.75rem">
<label>Target (IP / hostname / URL)</label>
<input type="text" id="vs-target" class="form-control" placeholder="192.168.1.1 or example.com" onkeypress="if(event.key==='Enter')vsStartScan()">
</div>
<div style="display:flex;gap:0.75rem;align-items:end;flex-wrap:wrap">
<div class="form-group" style="margin:0;min-width:140px">
<label>Profile</label>
<select id="vs-profile" class="form-control" onchange="vsProfileChanged()">
<option value="quick">Quick</option>
<option value="standard" selected>Standard</option>
<option value="full">Full</option>
<option value="custom">Custom</option>
</select>
</div>
<div class="form-group" style="margin:0;flex:1;min-width:180px">
<label>Ports <span style="color:var(--text-muted);font-size:0.75rem">(optional, overrides profile)</span></label>
<input type="text" id="vs-ports" class="form-control" placeholder="e.g. 1-1024,8080,8443">
</div>
<button id="vs-start-btn" class="btn btn-primary" onclick="vsStartScan()">Start Scan</button>
</div>
</div>
<div id="vs-profile-desc" style="font-size:0.8rem;color:var(--text-muted);max-width:220px;padding-bottom:4px">
Port scan + service detection + CVE matching + headers + SSL
</div>
</div>
</div>
<!-- Active Scan Progress -->
<div id="vs-active-scan" class="section" style="display:none">
<h2>Active Scan</h2>
<div style="display:flex;align-items:center;gap:1rem;margin-bottom:0.75rem">
<div style="flex:1;background:var(--bg-input);border-radius:6px;height:24px;overflow:hidden">
<div id="vs-progress-bar" style="height:100%;background:var(--accent);transition:width 0.3s;width:0%"></div>
</div>
<span id="vs-progress-pct" style="font-size:0.85rem;font-weight:600;min-width:40px;text-align:right">0%</span>
</div>
<div id="vs-progress-msg" style="font-size:0.8rem;color:var(--text-muted);margin-bottom:0.75rem"></div>
<!-- Severity Summary Badges -->
<div id="vs-severity-badges" style="display:flex;gap:0.5rem;flex-wrap:wrap;margin-bottom:1rem">
<span class="vs-sev-badge" style="background:rgba(239,68,68,0.2);color:var(--danger)">Critical: <strong id="vs-cnt-critical">0</strong></span>
<span class="vs-sev-badge" style="background:rgba(230,126,34,0.2);color:#e67e22">High: <strong id="vs-cnt-high">0</strong></span>
<span class="vs-sev-badge" style="background:rgba(245,158,11,0.2);color:var(--warning)">Medium: <strong id="vs-cnt-medium">0</strong></span>
<span class="vs-sev-badge" style="background:rgba(59,130,246,0.2);color:#3b82f6">Low: <strong id="vs-cnt-low">0</strong></span>
<span class="vs-sev-badge" style="background:rgba(139,143,168,0.15);color:var(--text-muted)">Info: <strong id="vs-cnt-info">0</strong></span>
</div>
<!-- Findings Table -->
<div style="overflow-x:auto">
<table class="data-table" style="font-size:0.82rem">
<thead>
<tr>
<th style="width:90px">Severity</th>
<th>Finding</th>
<th style="width:140px">Service</th>
<th style="width:60px">Port</th>
<th>Details</th>
</tr>
</thead>
<tbody id="vs-findings-body"></tbody>
</table>
</div>
<div id="vs-no-findings" style="display:none;color:var(--text-muted);font-size:0.85rem;padding:1rem 0">
No findings yet...
</div>
</div>
<!-- Standalone Tools -->
<div class="section" style="margin-top:1.5rem">
<h2>Standalone Checks</h2>
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:1rem;max-width:900px">
<!-- Headers Check -->
<div class="card">
<h4>Security Headers</h4>
<div class="form-group" style="margin-bottom:0.5rem">
<input type="text" id="vs-hdr-url" class="form-control" placeholder="https://example.com" onkeypress="if(event.key==='Enter')vsCheckHeaders()">
</div>
<button class="btn btn-primary btn-small" onclick="vsCheckHeaders()">Check Headers</button>
<div id="vs-hdr-results" style="margin-top:0.75rem;font-size:0.82rem"></div>
</div>
<!-- SSL Check -->
<div class="card">
<h4>SSL/TLS Analysis</h4>
<div style="display:flex;gap:0.5rem">
<div class="form-group" style="flex:1;margin-bottom:0.5rem">
<input type="text" id="vs-ssl-host" class="form-control" placeholder="example.com" onkeypress="if(event.key==='Enter')vsCheckSSL()">
</div>
<div class="form-group" style="width:70px;margin-bottom:0.5rem">
<input type="number" id="vs-ssl-port" class="form-control" value="443" min="1" max="65535">
</div>
</div>
<button class="btn btn-primary btn-small" onclick="vsCheckSSL()">Check SSL</button>
<div id="vs-ssl-results" style="margin-top:0.75rem;font-size:0.82rem"></div>
</div>
<!-- Creds Check -->
<div class="card">
<h4>Default Credentials</h4>
<div class="form-group" style="margin-bottom:0.5rem">
<input type="text" id="vs-cred-target" class="form-control" placeholder="192.168.1.1" onkeypress="if(event.key==='Enter')vsCheckCreds()">
</div>
<button class="btn btn-primary btn-small" onclick="vsCheckCreds()">Check Creds</button>
<div id="vs-cred-results" style="margin-top:0.75rem;font-size:0.82rem"></div>
</div>
</div>
</div>
</div>
<!-- ==================== TEMPLATES TAB ==================== -->
<div class="tab-content" data-tab-group="vs" data-tab="templates">
<div class="section">
<h2>Nuclei Templates</h2>
<div id="vs-nuclei-status" style="margin-bottom:1rem;font-size:0.85rem"></div>
<div class="form-group" style="max-width:400px;margin-bottom:1rem">
<input type="text" id="vs-tmpl-search" class="form-control" placeholder="Search templates..." oninput="vsFilterTemplates()">
</div>
<div id="vs-templates-list" style="max-height:500px;overflow-y:auto;font-size:0.82rem"></div>
<div id="vs-templates-loading" style="color:var(--text-muted);font-size:0.85rem">
<div class="spinner-inline"></div> Loading templates...
</div>
</div>
</div>
<!-- ==================== RESULTS TAB ==================== -->
<div class="tab-content" data-tab-group="vs" data-tab="results">
<div class="section">
<h2>Scan History</h2>
<div class="tool-actions" style="margin-bottom:1rem">
<button class="btn btn-primary btn-small" onclick="vsLoadScans()">Refresh</button>
</div>
<div style="overflow-x:auto">
<table class="data-table" style="font-size:0.82rem">
<thead>
<tr>
<th>Target</th>
<th>Date</th>
<th>Profile</th>
<th>Status</th>
<th>Findings</th>
<th>Severity Breakdown</th>
<th style="width:140px">Actions</th>
</tr>
</thead>
<tbody id="vs-history-body"></tbody>
</table>
</div>
<div id="vs-no-history" style="display:none;color:var(--text-muted);font-size:0.85rem;padding:1rem 0">
No scans recorded yet.
</div>
</div>
<!-- Detailed Results Panel (shown when clicking a scan row) -->
<div id="vs-detail-panel" class="section" style="display:none">
<div style="display:flex;align-items:center;gap:1rem;margin-bottom:1rem">
<h2 id="vs-detail-title" style="margin:0">Scan Details</h2>
<button class="btn btn-sm" onclick="document.getElementById('vs-detail-panel').style.display='none'">&times; Close</button>
</div>
<div id="vs-detail-summary" style="margin-bottom:1rem;font-size:0.85rem"></div>
<div style="overflow-x:auto">
<table class="data-table" style="font-size:0.82rem">
<thead>
<tr>
<th style="width:90px">Severity</th>
<th>Finding</th>
<th style="width:100px">Type</th>
<th style="width:140px">Service</th>
<th style="width:60px">Port</th>
<th>Details</th>
</tr>
</thead>
<tbody id="vs-detail-findings"></tbody>
</table>
</div>
<div id="vs-detail-services" style="margin-top:1rem"></div>
</div>
</div>
<style>
.vs-sev-badge {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 4px 12px;
border-radius: 20px;
font-size: 0.78rem;
font-weight: 500;
}
.vs-sev-critical { background: var(--danger); color: #fff; }
.vs-sev-high { background: #e67e22; color: #fff; }
.vs-sev-medium { background: var(--warning); color: #000; }
.vs-sev-low { background: #3b82f6; color: #fff; }
.vs-sev-info { background: var(--bg-input); color: var(--text-muted); }
.hdr-good { color: #22c55e; }
.hdr-weak { color: #f59e0b; }
.hdr-missing { color: var(--danger); }
.spinner-inline {
display: inline-block; width: 14px; height: 14px;
border: 2px solid var(--border); border-top-color: var(--accent);
border-radius: 50%; animation: spin 0.8s linear infinite;
vertical-align: middle; margin-right: 6px;
}
@keyframes spin { to { transform: rotate(360deg) } }
.vs-tmpl-item {
padding: 4px 8px;
border-bottom: 1px solid var(--border);
font-family: monospace;
}
.vs-tmpl-item:hover {
background: var(--bg-input);
}
.vs-detail-row:hover {
background: var(--bg-input);
cursor: pointer;
}
</style>
<script>
/* ── State ── */
let vsActivePoll = null;
let vsAllTemplates = [];
/* ── Severity Badge Helper ── */
function vsSevBadge(sev) {
sev = (sev || 'info').toLowerCase();
const colors = {
critical: 'background:var(--danger);color:#fff',
high: 'background:#e67e22;color:#fff',
medium: 'background:var(--warning);color:#000',
low: 'background:#3b82f6;color:#fff',
info: 'background:var(--bg-input);color:var(--text-muted)'
};
return '<span style="display:inline-block;padding:2px 10px;border-radius:12px;font-size:0.75rem;font-weight:600;' +
(colors[sev] || colors.info) + '">' + sev.toUpperCase() + '</span>';
}
/* ── Profile description ── */
const profileDescs = {
quick: 'Fast port scan + top service CVEs',
standard: 'Port scan + service detection + CVE matching + headers + SSL',
full: 'All ports + full CVE + default creds + headers + SSL + nuclei',
custom: 'User-defined parameters'
};
function vsProfileChanged() {
const p = document.getElementById('vs-profile').value;
document.getElementById('vs-profile-desc').textContent = profileDescs[p] || '';
}
/* ── Start Scan ── */
function vsStartScan() {
const target = document.getElementById('vs-target').value.trim();
if (!target) return;
const profile = document.getElementById('vs-profile').value;
const ports = document.getElementById('vs-ports').value.trim();
const btn = document.getElementById('vs-start-btn');
setLoading(btn, true);
postJSON('/vuln-scanner/scan', { target, profile, ports })
.then(d => {
setLoading(btn, false);
if (!d.ok) { alert('Error: ' + (d.error || 'Unknown')); return; }
document.getElementById('vs-active-scan').style.display = '';
document.getElementById('vs-findings-body').innerHTML = '';
document.getElementById('vs-no-findings').style.display = '';
vsResetCounts();
vsCheckScan(d.job_id);
})
.catch(e => { setLoading(btn, false); alert('Error: ' + e.message); });
}
function vsResetCounts() {
['critical','high','medium','low','info'].forEach(s => {
document.getElementById('vs-cnt-' + s).textContent = '0';
});
}
/* ── Poll Scan ── */
function vsCheckScan(jobId) {
if (vsActivePoll) clearInterval(vsActivePoll);
vsActivePoll = setInterval(() => {
fetchJSON('/vuln-scanner/scan/' + jobId)
.then(d => {
if (!d.ok) { clearInterval(vsActivePoll); vsActivePoll = null; return; }
// Progress
const pct = d.progress || 0;
document.getElementById('vs-progress-bar').style.width = pct + '%';
document.getElementById('vs-progress-pct').textContent = pct + '%';
document.getElementById('vs-progress-msg').textContent = d.progress_message || '';
// Summary counts
const sum = d.summary || {};
['critical','high','medium','low','info'].forEach(s => {
document.getElementById('vs-cnt-' + s).textContent = sum[s] || 0;
});
// Findings
const findings = d.findings || [];
if (findings.length > 0) {
document.getElementById('vs-no-findings').style.display = 'none';
let html = '';
for (const f of findings) {
html += '<tr>' +
'<td>' + vsSevBadge(f.severity) + '</td>' +
'<td>' + esc(f.title || '') + '</td>' +
'<td>' + esc(f.service || '') + '</td>' +
'<td>' + (f.port || '') + '</td>' +
'<td style="font-size:0.78rem;color:var(--text-secondary);max-width:300px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">' +
esc((f.description || '').substring(0, 150)) + '</td>' +
'</tr>';
}
document.getElementById('vs-findings-body').innerHTML = html;
}
// Done?
if (d.status === 'complete' || d.status === 'error') {
clearInterval(vsActivePoll);
vsActivePoll = null;
if (d.status === 'error') {
document.getElementById('vs-progress-msg').innerHTML =
'<span style="color:var(--danger)">Error: ' + esc(d.error || 'Unknown') + '</span>';
} else {
document.getElementById('vs-progress-msg').innerHTML =
'<span style="color:#22c55e">Scan complete</span>';
}
}
})
.catch(() => { clearInterval(vsActivePoll); vsActivePoll = null; });
}, 2000);
}
/* ── Load Scans ── */
function vsLoadScans() {
fetchJSON('/vuln-scanner/scans')
.then(d => {
const scans = d.scans || [];
const noHist = document.getElementById('vs-no-history');
const body = document.getElementById('vs-history-body');
if (!scans.length) {
noHist.style.display = '';
body.innerHTML = '';
return;
}
noHist.style.display = 'none';
let html = '';
for (const s of scans) {
const sum = s.summary || {};
const dateStr = s.started ? new Date(s.started).toLocaleString() : '';
const statusColor = s.status === 'complete' ? '#22c55e' : (s.status === 'error' ? 'var(--danger)' : 'var(--warning)');
const sevBreak =
(sum.critical ? '<span style="color:var(--danger)">' + sum.critical + 'C</span> ' : '') +
(sum.high ? '<span style="color:#e67e22">' + sum.high + 'H</span> ' : '') +
(sum.medium ? '<span style="color:var(--warning)">' + sum.medium + 'M</span> ' : '') +
(sum.low ? '<span style="color:#3b82f6">' + sum.low + 'L</span> ' : '') +
(sum.info ? '<span style="color:var(--text-muted)">' + sum.info + 'I</span>' : '');
html += '<tr class="vs-detail-row" onclick="vsViewScan(\'' + s.job_id + '\')">' +
'<td>' + esc(s.target) + '</td>' +
'<td style="font-size:0.78rem">' + esc(dateStr) + '</td>' +
'<td>' + esc(s.profile) + '</td>' +
'<td style="color:' + statusColor + '">' + esc(s.status) + '</td>' +
'<td><strong>' + (s.findings_count || 0) + '</strong></td>' +
'<td>' + (sevBreak || '<span style="color:var(--text-muted)">none</span>') + '</td>' +
'<td style="white-space:nowrap">' +
'<button class="btn btn-sm" onclick="event.stopPropagation();vsViewScan(\'' + s.job_id + '\')">View</button> ' +
'<button class="btn btn-sm" onclick="event.stopPropagation();vsExportScan(\'' + s.job_id + '\')">Export</button> ' +
'<button class="btn btn-sm btn-danger" onclick="event.stopPropagation();vsDeleteScan(\'' + s.job_id + '\')">Del</button>' +
'</td></tr>';
}
body.innerHTML = html;
});
}
/* ── View Scan Details ── */
function vsViewScan(jobId) {
fetchJSON('/vuln-scanner/scan/' + jobId)
.then(d => {
if (!d.ok) return;
const panel = document.getElementById('vs-detail-panel');
panel.style.display = '';
document.getElementById('vs-detail-title').textContent = 'Scan: ' + (d.target || jobId);
const sum = d.summary || {};
let sumHtml = '<div style="display:flex;gap:0.5rem;flex-wrap:wrap;margin-bottom:0.5rem">' +
'<span class="vs-sev-badge" style="background:rgba(239,68,68,0.2);color:var(--danger)">Critical: <strong>' + (sum.critical || 0) + '</strong></span>' +
'<span class="vs-sev-badge" style="background:rgba(230,126,34,0.2);color:#e67e22">High: <strong>' + (sum.high || 0) + '</strong></span>' +
'<span class="vs-sev-badge" style="background:rgba(245,158,11,0.2);color:var(--warning)">Medium: <strong>' + (sum.medium || 0) + '</strong></span>' +
'<span class="vs-sev-badge" style="background:rgba(59,130,246,0.2);color:#3b82f6">Low: <strong>' + (sum.low || 0) + '</strong></span>' +
'<span class="vs-sev-badge" style="background:rgba(139,143,168,0.15);color:var(--text-muted)">Info: <strong>' + (sum.info || 0) + '</strong></span>' +
'</div>';
sumHtml += '<div>Status: <strong>' + esc(d.status || '') + '</strong> | Profile: <strong>' + esc(d.profile || '') + '</strong>';
if (d.completed) sumHtml += ' | Completed: ' + new Date(d.completed).toLocaleString();
sumHtml += '</div>';
document.getElementById('vs-detail-summary').innerHTML = sumHtml;
const findings = d.findings || [];
if (findings.length) {
let fhtml = '';
for (const f of findings) {
fhtml += '<tr>' +
'<td>' + vsSevBadge(f.severity) + '</td>' +
'<td>' + esc(f.title || '') + '</td>' +
'<td>' + esc(f.type || '') + '</td>' +
'<td>' + esc(f.service || '') + '</td>' +
'<td>' + (f.port || '') + '</td>' +
'<td style="font-size:0.78rem;color:var(--text-secondary)">' + esc((f.description || '').substring(0, 200));
if (f.reference) fhtml += ' <a href="' + esc(f.reference) + '" target="_blank" style="font-size:0.75rem">[ref]</a>';
if (f.cvss) fhtml += ' <span style="color:var(--accent);font-size:0.75rem">CVSS:' + esc(f.cvss) + '</span>';
fhtml += '</td></tr>';
}
document.getElementById('vs-detail-findings').innerHTML = fhtml;
} else {
document.getElementById('vs-detail-findings').innerHTML =
'<tr><td colspan="6" style="color:var(--text-muted);text-align:center">No findings</td></tr>';
}
// Services discovered
const services = d.services || [];
if (services.length) {
let shtml = '<h3 style="margin-top:0.5rem">Discovered Services</h3>' +
'<table class="data-table" style="font-size:0.82rem"><thead><tr><th>Port</th><th>Protocol</th><th>Service</th><th>Version</th></tr></thead><tbody>';
for (const s of services) {
shtml += '<tr><td>' + s.port + '</td><td>' + esc(s.protocol || 'tcp') + '</td>' +
'<td>' + esc(s.service || '') + '</td><td>' + esc(s.version || '') + '</td></tr>';
}
shtml += '</tbody></table>';
document.getElementById('vs-detail-services').innerHTML = shtml;
} else {
document.getElementById('vs-detail-services').innerHTML = '';
}
panel.scrollIntoView({ behavior: 'smooth' });
});
}
/* ── Delete Scan ── */
function vsDeleteScan(jobId) {
if (!confirm('Delete this scan?')) return;
fetch('/vuln-scanner/scan/' + jobId, { method: 'DELETE' })
.then(r => r.json())
.then(() => {
vsLoadScans();
document.getElementById('vs-detail-panel').style.display = 'none';
});
}
/* ── Export Scan ── */
function vsExportScan(jobId) {
window.open('/vuln-scanner/scan/' + jobId + '/export?format=json', '_blank');
}
/* ── Check Headers ── */
function vsCheckHeaders() {
const url = document.getElementById('vs-hdr-url').value.trim();
if (!url) return;
const div = document.getElementById('vs-hdr-results');
div.innerHTML = '<div class="spinner-inline"></div> Checking...';
postJSON('/vuln-scanner/headers', { url })
.then(d => {
if (!d.ok) { div.innerHTML = '<span style="color:var(--danger)">Error: ' + esc(d.error || 'Unknown') + '</span>'; return; }
let html = '<div style="margin-bottom:0.5rem">Score: <strong>' + (d.score || 0) + '%</strong>';
if (d.server) html += ' | Server: <strong>' + esc(d.server) + '</strong>';
html += '</div>';
const headers = d.headers || {};
for (const [hdr, info] of Object.entries(headers)) {
const cls = 'hdr-' + info.rating;
const icon = info.present ? '&#10003;' : '&#10007;';
html += '<div style="padding:2px 0"><span class="' + cls + '">' + icon + '</span> ' + esc(hdr);
if (info.value) html += ' <span style="color:var(--text-muted);font-size:0.75rem">' + esc(info.value).substring(0, 60) + '</span>';
html += '</div>';
}
div.innerHTML = html;
})
.catch(e => { div.innerHTML = '<span style="color:var(--danger)">' + e.message + '</span>'; });
}
/* ── Check SSL ── */
function vsCheckSSL() {
const host = document.getElementById('vs-ssl-host').value.trim();
if (!host) return;
const port = parseInt(document.getElementById('vs-ssl-port').value) || 443;
const div = document.getElementById('vs-ssl-results');
div.innerHTML = '<div class="spinner-inline"></div> Checking...';
postJSON('/vuln-scanner/ssl', { host, port })
.then(d => {
if (!d.ok || d.error) {
div.innerHTML = '<span style="color:var(--danger)">Error: ' + esc(d.error || 'Unknown') + '</span>';
return;
}
let html = '<div>Valid: <strong class="' + (d.valid ? 'hdr-good' : 'hdr-missing') + '">' + (d.valid ? 'Yes' : 'No') + '</strong></div>';
html += '<div>Protocol: ' + esc(d.protocol || '?') + '</div>';
html += '<div>Cipher: ' + esc(d.cipher || '?') + '</div>';
if (d.key_size) html += '<div>Key size: ' + d.key_size + ' bits</div>';
if (d.expires) html += '<div>Expires: ' + esc(d.expires) + '</div>';
if (d.issuer && typeof d.issuer === 'object') {
html += '<div>Issuer: ' + esc(d.issuer.organizationName || d.issuer.commonName || JSON.stringify(d.issuer)) + '</div>';
}
const issues = d.issues || [];
for (const issue of issues) {
html += '<div class="hdr-missing">[!] ' + esc(issue) + '</div>';
}
const weak = d.weak_ciphers || [];
for (const wc of weak) {
html += '<div class="hdr-missing">[!] Weak cipher: ' + esc(wc) + '</div>';
}
if (!issues.length && !weak.length && d.valid) {
html += '<div class="hdr-good" style="margin-top:4px">No issues detected</div>';
}
div.innerHTML = html;
})
.catch(e => { div.innerHTML = '<span style="color:var(--danger)">' + e.message + '</span>'; });
}
/* ── Check Creds ── */
function vsCheckCreds() {
const target = document.getElementById('vs-cred-target').value.trim();
if (!target) return;
const div = document.getElementById('vs-cred-results');
div.innerHTML = '<div class="spinner-inline"></div> Scanning ports & testing credentials...';
postJSON('/vuln-scanner/creds', { target })
.then(d => {
if (!d.ok) { div.innerHTML = '<span style="color:var(--danger)">Error: ' + esc(d.error || 'Unknown') + '</span>'; return; }
const found = d.found || [];
let html = '<div style="margin-bottom:0.5rem">' + (d.services_checked || 0) + ' services checked</div>';
if (found.length) {
for (const c of found) {
html += '<div style="color:var(--danger);padding:2px 0">[!] ' + esc(c.service) + ': <strong>' +
esc(c.username) + ':' + esc(c.password) + '</strong></div>';
}
} else {
html += '<div class="hdr-good">No default credentials found</div>';
}
div.innerHTML = html;
})
.catch(e => { div.innerHTML = '<span style="color:var(--danger)">' + e.message + '</span>'; });
}
/* ── Load Templates ── */
function vsLoadTemplates() {
const list = document.getElementById('vs-templates-list');
const loading = document.getElementById('vs-templates-loading');
const status = document.getElementById('vs-nuclei-status');
loading.style.display = '';
list.innerHTML = '';
fetchJSON('/vuln-scanner/templates')
.then(d => {
loading.style.display = 'none';
if (d.installed) {
status.innerHTML = '<span class="hdr-good">&#10003; Nuclei installed</span>' +
' <span style="color:var(--text-muted)">(' + esc(d.nuclei_path) + ')</span>';
} else {
status.innerHTML = '<span class="hdr-missing">&#10007; Nuclei not installed</span>' +
' <span style="color:var(--text-muted)">Install: go install -v github.com/projectdiscovery/nuclei/v3/cmd/nuclei@latest</span>';
}
vsAllTemplates = d.templates || [];
const cats = d.categories || [];
if (cats.length) {
let catHtml = '<div style="margin-bottom:0.75rem;display:flex;gap:0.5rem;flex-wrap:wrap">';
for (const cat of cats) {
const count = vsAllTemplates.filter(t => t.startsWith(cat + '/')).length;
catHtml += '<span class="vs-sev-badge" style="background:var(--bg-input);color:var(--accent);cursor:pointer" ' +
'onclick="document.getElementById(\'vs-tmpl-search\').value=\'' + cat + '/\';vsFilterTemplates()">' +
esc(cat) + ' (' + count + ')</span>';
}
catHtml += '</div>';
list.innerHTML = catHtml;
}
vsRenderTemplates(vsAllTemplates.slice(0, 200));
})
.catch(() => { loading.style.display = 'none'; });
}
function vsFilterTemplates() {
const q = document.getElementById('vs-tmpl-search').value.trim().toLowerCase();
const filtered = q ? vsAllTemplates.filter(t => t.toLowerCase().includes(q)) : vsAllTemplates;
vsRenderTemplates(filtered.slice(0, 200));
}
function vsRenderTemplates(templates) {
const container = document.getElementById('vs-templates-list');
// Preserve category badges if they exist
const badges = container.querySelector('div');
let html = badges ? badges.outerHTML : '';
if (templates.length) {
html += '<div style="margin-bottom:0.5rem;color:var(--text-muted)">Showing ' + templates.length +
(templates.length >= 200 ? '+ (use search to filter)' : '') + ' templates</div>';
for (const t of templates) {
html += '<div class="vs-tmpl-item">' + esc(t) + '</div>';
}
} else {
html += '<div style="color:var(--text-muted)">No templates found</div>';
}
container.innerHTML = html;
}
/* ── Helpers (use global esc, postJSON, fetchJSON, setLoading, showTab) ── */
</script>
{% endblock %}