From 4fca4a85c29ffa2d8d179fb026cba9d00e190836 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=85=AC=E6=98=8E?= <83812544+Ed1s0nZ@users.noreply.github.com> Date: Mon, 4 May 2026 03:42:24 +0800 Subject: [PATCH] Add files via upload --- web/static/css/c2.css | 1195 ++++++++++++++++++++ web/static/css/style.css | 49 + web/static/i18n/en-US.json | 322 +++++- web/static/i18n/zh-CN.json | 322 +++++- web/static/js/c2.js | 1875 ++++++++++++++++++++++++++++++++ web/static/js/dashboard.js | 61 +- web/static/js/monitor.js | 23 +- web/static/js/notifications.js | 19 + web/static/js/router.js | 27 +- web/templates/index.html | 251 +++++ 10 files changed, 4133 insertions(+), 11 deletions(-) create mode 100644 web/static/css/c2.css create mode 100644 web/static/js/c2.js diff --git a/web/static/css/c2.css b/web/static/css/c2.css new file mode 100644 index 00000000..96edb0dc --- /dev/null +++ b/web/static/css/c2.css @@ -0,0 +1,1195 @@ +/* CyberStrikeAI C2 Module — Redesigned UI */ + +/* ============================================================================ + CSS Variables + ============================================================================ */ + +:root { + --c2-accent: #3b82f6; + --c2-accent-hover: #2563eb; + --c2-accent-dim: rgba(59, 130, 246, 0.08); + --c2-accent-glow: rgba(59, 130, 246, 0.25); + --c2-green: #10b981; + --c2-green-dim: rgba(16, 185, 129, 0.08); + --c2-red: #ef4444; + --c2-red-dim: rgba(239, 68, 68, 0.08); + --c2-amber: #f59e0b; + --c2-amber-dim: rgba(245, 158, 11, 0.08); + --c2-purple: #8b5cf6; + --c2-purple-dim: rgba(139, 92, 246, 0.08); + --c2-surface: #ffffff; + --c2-surface-alt: #f8fafc; + --c2-border: #e2e8f0; + --c2-border-hover: #cbd5e1; + --c2-text: #0f172a; + --c2-text-dim: #64748b; + --c2-text-muted: #94a3b8; + --c2-radius: 12px; + --c2-radius-sm: 8px; + --c2-radius-xs: 6px; + --c2-shadow-sm: 0 1px 2px rgba(0,0,0,0.04); + --c2-shadow-md: 0 4px 12px rgba(0,0,0,0.06); + --c2-shadow-lg: 0 12px 40px rgba(0,0,0,0.1); + --c2-mono: 'SF Mono', 'Fira Code', 'JetBrains Mono', 'Cascadia Code', monospace; +} + +/* ============================================================================ + Form Controls (scoped to C2 pages) + ============================================================================ */ + +#page-c2 .form-control, +#page-c2-listeners .form-control, +#page-c2-sessions .form-control, +#page-c2-tasks .form-control, +#page-c2-payloads .form-control, +#page-c2-events .form-control, +#page-c2-profiles .form-control, +.c2-modal .form-control { + display: block; + width: 100%; + padding: 10px 14px; + font-size: 14px; + line-height: 1.5; + color: var(--c2-text); + background: var(--c2-surface); + border: 1.5px solid var(--c2-border); + border-radius: var(--c2-radius-sm); + transition: border-color 0.2s, box-shadow 0.2s; + outline: none; + font-family: inherit; + -webkit-appearance: none; + appearance: none; +} + +#page-c2 .form-control:focus, +#page-c2-listeners .form-control:focus, +#page-c2-sessions .form-control:focus, +#page-c2-tasks .form-control:focus, +#page-c2-payloads .form-control:focus, +#page-c2-events .form-control:focus, +#page-c2-profiles .form-control:focus, +.c2-modal .form-control:focus { + border-color: var(--c2-accent); + box-shadow: 0 0 0 3px var(--c2-accent-dim); +} + +#page-c2 select.form-control, +#page-c2-payloads select.form-control, +.c2-modal select.form-control { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%2364748b' d='M2.5 4.5L6 8l3.5-3.5'/%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: right 12px center; + background-size: 12px; + padding-right: 36px; + cursor: pointer; +} + +#page-c2 textarea.form-control, +#page-c2-payloads textarea.form-control, +.c2-modal textarea.form-control { + resize: vertical; + min-height: 80px; + font-family: var(--c2-mono); + font-size: 13px; + line-height: 1.6; +} + +#page-c2 .form-control::placeholder, +#page-c2-payloads .form-control::placeholder, +.c2-modal .form-control::placeholder { + color: var(--c2-text-muted); +} + +/* ============================================================================ + C2 Button Overrides (within C2 scope) + ============================================================================ */ + +.c2-listener-actions .btn-primary, +.c2-payload-card .btn-primary, +.c2-modal-footer .btn-primary { + background: var(--c2-accent); + color: #fff; + border: none; + padding: 10px 20px; + border-radius: var(--c2-radius-sm); + font-weight: 600; + font-size: 14px; + cursor: pointer; + transition: all 0.2s; +} + +.c2-listener-actions .btn-primary:hover, +.c2-payload-card .btn-primary:hover, +.c2-modal-footer .btn-primary:hover { + background: var(--c2-accent-hover); + box-shadow: 0 4px 12px var(--c2-accent-glow); + transform: translateY(-1px); +} + +/* ============================================================================ + Layout + ============================================================================ */ + +.c2-layout { display: flex; flex-direction: column; height: 100%; } +.c2-main { flex: 1; overflow-y: auto; } + +.c2-empty { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + text-align: center; + width: 100%; + flex: 1; + min-height: calc(100vh - 200px); + padding: 24px; + color: var(--c2-text-dim); +} + +.c2-loading { + text-align: center; + padding: 48px; + color: var(--c2-text-dim); +} + +.c2-error { + color: var(--c2-red); + padding: 20px; + text-align: center; + background: var(--c2-red-dim); + border-radius: var(--c2-radius-sm); + margin: 12px; +} + +/* ============================================================================ + Dashboard / Welcome + ============================================================================ */ + +.c2-welcome { + text-align: center; + padding: 100px 24px 80px; + max-width: 860px; + margin: 0 auto; +} + +.c2-welcome-icon { + margin-bottom: 16px; + animation: c2-float 4s ease-in-out infinite; +} + +@keyframes c2-float { + 0%, 100% { transform: translateY(0); } + 50% { transform: translateY(-8px); } +} + +.c2-welcome h3 { + font-size: 28px; + margin-bottom: 12px; + color: var(--c2-text); + font-weight: 800; + letter-spacing: -0.5px; +} + +.c2-welcome p { + color: var(--c2-text-dim); + font-size: 15px; + line-height: 1.7; + margin-bottom: 48px; + max-width: 520px; + margin-left: auto; + margin-right: auto; +} + +.c2-stats { + display: flex; + justify-content: center; + gap: 16px; + margin-bottom: 48px; + flex-wrap: wrap; +} + +.c2-stat-item { + display: flex; + flex-direction: column; + align-items: center; + padding: 28px 40px; + background: var(--c2-surface); + border-radius: var(--c2-radius); + border: 1.5px solid var(--c2-border); + min-width: 160px; + transition: all 0.3s ease; +} + +.c2-stat-item:hover { + transform: translateY(-4px); + box-shadow: var(--c2-shadow-md); + border-color: var(--c2-accent); +} + +.c2-stat-item:nth-child(1) .c2-stat-value { color: var(--c2-accent); } +.c2-stat-item:nth-child(2) .c2-stat-value { color: var(--c2-green); } +.c2-stat-item:nth-child(3) .c2-stat-value { color: var(--c2-amber); } + +.c2-stat-value { + font-size: 36px; + font-weight: 800; + line-height: 1; + letter-spacing: -1px; +} + +.c2-stat-label { + font-size: 12px; + color: var(--c2-text-dim); + margin-top: 12px; + font-weight: 600; + letter-spacing: 0.3px; +} + +.c2-actions { + display: flex; + gap: 12px; + justify-content: center; + flex-wrap: wrap; +} + +/* ============================================================================ + Listener Cards + ============================================================================ */ + +.c2-listener-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(340px, 1fr)); + gap: 16px; + padding: 24px; + align-items: start; +} +.c2-listener-grid:has(.c2-empty) { + display: flex; +} + +.c2-listener-card { + background: var(--c2-surface); + border: 1.5px solid var(--c2-border); + border-radius: var(--c2-radius); + padding: 24px; + transition: all 0.25s ease; + position: relative; +} + +.c2-listener-card.running { border-left: 4px solid var(--c2-green); } +.c2-listener-card.stopped { border-left: 4px solid var(--c2-text-muted); } +.c2-listener-card.error { border-left: 4px solid var(--c2-amber); } + +.c2-listener-card:hover { + box-shadow: var(--c2-shadow-md); + border-color: var(--c2-border-hover); +} + +.c2-listener-header { + display: flex; + justify-content: space-between; + align-items: flex-start; + margin-bottom: 16px; +} + +.c2-listener-name { + font-weight: 700; + font-size: 16px; + color: var(--c2-text); +} + +.c2-listener-id { + font-size: 11px; + color: var(--c2-text-muted); + font-family: var(--c2-mono); + margin-top: 4px; +} + +.c2-listener-type { + font-size: 11px; + padding: 4px 10px; + border-radius: var(--c2-radius-xs); + background: var(--c2-surface-alt); + color: var(--c2-text-dim); + font-weight: 600; + border: 1px solid var(--c2-border); + text-transform: capitalize; +} + +.c2-listener-info { + font-size: 13px; + color: var(--c2-text-dim); + margin-bottom: 20px; + line-height: 1.8; +} + +.c2-listener-address { + font-family: var(--c2-mono); + font-size: 13px; + margin-bottom: 6px; + display: flex; + align-items: center; + gap: 8px; + color: var(--c2-text); +} + +.c2-status-dot { + width: 8px; + height: 8px; + border-radius: 50%; + display: inline-block; + flex-shrink: 0; +} + +.c2-status-dot.running { background: var(--c2-green); box-shadow: 0 0 0 3px var(--c2-green-dim); } +.c2-status-dot.stopped { background: var(--c2-text-muted); } +.c2-status-dot.active { background: var(--c2-green); box-shadow: 0 0 0 3px var(--c2-green-dim); } +.c2-status-dot.sleeping { background: var(--c2-amber); box-shadow: 0 0 0 3px var(--c2-amber-dim); } +.c2-status-dot.dead { background: var(--c2-text-muted); } + +.c2-listener-actions { + display: flex; + gap: 8px; + flex-wrap: wrap; + padding-top: 16px; + border-top: 1px solid var(--c2-border); +} + +.c2-listener-actions button { flex: 1; min-width: 70px; } + +/* ============================================================================ + Session Management + ============================================================================ */ + +.c2-session-layout { + display: flex; + height: calc(100vh - 140px); + overflow: hidden; +} + +.c2-session-sidebar { + width: 320px; + border-right: 1px solid var(--c2-border); + overflow-y: auto; + background: var(--c2-surface-alt); + padding: 16px; +} + +.c2-session-list { display: flex; flex-direction: column; gap: 6px; } + +.c2-session-item { + padding: 14px 16px; + border-radius: var(--c2-radius-sm); + cursor: pointer; + transition: all 0.15s ease; + border: 1.5px solid transparent; + background: var(--c2-surface); +} + +.c2-session-item:hover { + border-color: var(--c2-border-hover); + background: var(--c2-surface); + box-shadow: var(--c2-shadow-sm); +} + +.c2-session-item.active { + background: var(--c2-accent-dim); + border-color: var(--c2-accent); + box-shadow: 0 0 0 1px var(--c2-accent); +} + +.c2-session-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 6px; +} + +.c2-session-host { + font-weight: 700; + font-size: 14px; + color: var(--c2-text); +} + +.c2-session-status { + font-size: 10px; + padding: 2px 8px; + border-radius: var(--c2-radius-xs); + text-transform: uppercase; + letter-spacing: 0.5px; + font-weight: 700; +} + +.c2-session-status.active { background: var(--c2-green-dim); color: var(--c2-green); } +.c2-session-status.sleeping { background: var(--c2-amber-dim); color: var(--c2-amber); } +.c2-session-status.dead { background: var(--c2-red-dim); color: var(--c2-red); } + +.c2-session-meta { + font-size: 12px; + color: var(--c2-text-dim); + line-height: 1.5; +} + +.c2-session-item-footer { + display: flex; + justify-content: space-between; + align-items: center; + gap: 8px; + margin-top: 6px; + min-height: 22px; +} + +.c2-session-item-time { + font-size: 10px; + opacity: 0.5; + flex: 1; + min-width: 0; +} + +.c2-session-card-delete { + flex-shrink: 0; + font-size: 11px; + font-weight: 600; + padding: 3px 8px; + border-radius: var(--c2-radius-xs); + border: 1px solid var(--c2-border); + background: var(--c2-surface-alt); + color: var(--c2-text-dim); + cursor: pointer; + transition: color 0.15s ease, border-color 0.15s ease, background 0.15s ease; +} + +.c2-session-card-delete:hover { + border-color: var(--c2-red); + color: var(--c2-red); + background: var(--c2-red-dim); +} + +.c2-session-item.active .c2-session-card-delete { + background: var(--c2-surface); +} + +.c2-session-main { + flex: 1; + overflow-y: auto; + padding: 24px; + background: var(--c2-surface); +} + +.c2-session-detail { max-width: 1400px; margin: 0 auto; } + +.c2-session-header-bar { + display: flex; + justify-content: space-between; + align-items: flex-start; + margin-bottom: 24px; + padding-bottom: 20px; + border-bottom: 1px solid var(--c2-border); +} + +.c2-session-title h3 { + font-size: 22px; + margin-bottom: 6px; + display: flex; + align-items: center; + gap: 10px; + font-weight: 800; +} + +.c2-session-badge { + font-size: 10px; + padding: 3px 10px; + border-radius: var(--c2-radius-xs); + font-weight: 700; +} + +.c2-session-subtitle { + font-size: 13px; + color: var(--c2-text-dim); + font-family: var(--c2-mono); +} + +.c2-session-actions { display: flex; gap: 8px; } + +/* Session Tabs */ +.c2-session-tabs { + display: flex; + gap: 2px; + margin-bottom: 24px; + border-bottom: 1.5px solid var(--c2-border); + padding-bottom: 0; +} + +.c2-session-tab { + padding: 10px 20px; + cursor: pointer; + transition: all 0.15s; + font-weight: 600; + font-size: 13px; + color: var(--c2-text-dim); + border-bottom: 2px solid transparent; + margin-bottom: -1.5px; +} + +.c2-session-tab:hover { + color: var(--c2-text); +} + +.c2-session-tab.active { + color: var(--c2-accent); + border-bottom-color: var(--c2-accent); +} + +.c2-tab-panel { display: none; } +.c2-tab-panel.active { display: block; } + +/* ============================================================================ + Terminal + ============================================================================ */ + +.c2-terminal-container { + background: #0f172a; + border-radius: var(--c2-radius) var(--c2-radius) 0 0; + height: 480px; + overflow: hidden; + position: relative; + border: 1.5px solid #1e293b; + border-bottom: none; +} + +.c2-terminal-toolbar { + display: flex; + gap: 8px; + padding: 10px 16px; + background: #1e293b; + border-radius: 0 0 var(--c2-radius) var(--c2-radius); + align-items: center; + border: 1.5px solid #1e293b; + border-top: none; +} + +.c2-terminal-status { + margin-left: auto; + font-size: 11px; + color: #64748b; + font-family: var(--c2-mono); +} + +/* ============================================================================ + File Manager + ============================================================================ */ + +.c2-file-toolbar { + display: flex; + gap: 8px; + padding: 12px 16px; + background: var(--c2-surface-alt); + border-radius: var(--c2-radius-sm); + margin-bottom: 16px; + align-items: center; + border: 1px solid var(--c2-border); +} + +.c2-path-breadcrumb { + font-family: var(--c2-mono); + font-size: 12px; + color: var(--c2-text-dim); + margin-left: auto; + background: var(--c2-surface); + padding: 5px 12px; + border-radius: var(--c2-radius-xs); + border: 1px solid var(--c2-border); +} + +.c2-file-list { + background: var(--c2-surface); + border-radius: var(--c2-radius); + border: 1px solid var(--c2-border); + overflow: hidden; +} + +.c2-file-table { width: 100%; border-collapse: collapse; } + +.c2-file-table th { + text-align: left; + padding: 12px 18px; + font-size: 11px; + text-transform: uppercase; + letter-spacing: 0.5px; + color: var(--c2-text-dim); + background: var(--c2-surface-alt); + font-weight: 700; + border-bottom: 1px solid var(--c2-border); +} + +.c2-file-table td { + padding: 10px 18px; + border-top: 1px solid var(--c2-border); + font-size: 13px; +} + +.c2-file-table tbody tr:hover td { background: var(--c2-surface-alt); } + +.c2-file-name { display: flex; align-items: center; gap: 10px; } +.c2-file-icon { font-size: 16px; } + +/* ============================================================================ + Task List + ============================================================================ */ + +.c2-tasks-page-wrap { + display: flex; + flex-direction: column; + gap: 0; +} + +.c2-tasks-toolbar { + display: flex; + align-items: center; + gap: 12px; + padding: 8px 4px 10px; + border-bottom: 1px solid var(--c2-border); +} + +.c2-tasks-select-all-label { + display: inline-flex; + align-items: center; + gap: 8px; + font-size: 13px; + color: var(--c2-text-muted); + cursor: pointer; + user-select: none; +} + +.c2-tasks-select-all-label input { + cursor: pointer; +} + +.c2-task-table-col-check { + width: 44px; + text-align: center; + vertical-align: middle; +} + +.c2-task-check-label { + display: flex; + justify-content: center; + align-items: center; + cursor: pointer; +} + +.c2-task-check-label input { + cursor: pointer; +} + +.c2-task-list-container { + background: var(--c2-surface); + border-radius: var(--c2-radius); + border: 1px solid var(--c2-border); + overflow: hidden; +} + +.c2-task-table { width: 100%; border-collapse: collapse; } + +.c2-task-table th { + text-align: left; + padding: 12px 18px; + font-size: 11px; + text-transform: uppercase; + letter-spacing: 0.5px; + color: var(--c2-text-dim); + background: var(--c2-surface-alt); + font-weight: 700; + border-bottom: 1px solid var(--c2-border); +} + +.c2-task-table td { + padding: 12px 18px; + border-top: 1px solid var(--c2-border); + font-size: 13px; +} + +.c2-task-table tbody tr:hover td { background: var(--c2-surface-alt); } + +.c2-status-badge { + font-size: 11px; + padding: 3px 10px; + border-radius: var(--c2-radius-xs); + font-weight: 600; + display: inline-block; +} + +.c2-status-badge.queued { background: #f1f5f9; color: #64748b; } +.c2-status-badge.sent { background: var(--c2-accent-dim); color: var(--c2-accent); } +.c2-status-badge.running { background: var(--c2-amber-dim); color: #b45309; } +.c2-status-badge.success { background: var(--c2-green-dim); color: #047857; } +.c2-status-badge.failed { background: var(--c2-red-dim); color: var(--c2-red); } +.c2-status-badge.cancelled { background: #f1f5f9; color: #94a3b8; } + +.c2-task-item-compact { + display: flex; + align-items: center; + padding: 10px 14px; + border-bottom: 1px solid var(--c2-border); + gap: 12px; + transition: background 0.1s; +} + +.c2-task-item-compact:hover { background: var(--c2-surface-alt); } + +.c2-task-status-dot { + width: 8px; + height: 8px; + border-radius: 50%; + flex-shrink: 0; +} + +.c2-task-status-dot.queued { background: #94a3b8; } +.c2-task-status-dot.sent { background: var(--c2-accent); } +.c2-task-status-dot.running { background: var(--c2-amber); } +.c2-task-status-dot.success { background: var(--c2-green); } +.c2-task-status-dot.failed { background: var(--c2-red); } +.c2-task-status-dot.cancelled { background: #94a3b8; } + +/* ============================================================================ + Payload Page + ============================================================================ */ + +.c2-payload-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(440px, 1fr)); + gap: 24px; + padding: 24px; +} + +.c2-payload-card { + background: var(--c2-surface); + border: 1.5px solid var(--c2-border); + border-radius: var(--c2-radius); + padding: 32px; + transition: all 0.25s; +} + +.c2-payload-card:hover { + box-shadow: var(--c2-shadow-md); + border-color: var(--c2-border-hover); +} + +.c2-payload-card h3 { + font-size: 18px; + margin-bottom: 6px; + color: var(--c2-text); + font-weight: 700; + display: flex; + align-items: center; + gap: 8px; +} + +.c2-payload-card h3 .c2-payload-icon { + display: inline-flex; + align-items: center; + justify-content: center; + width: 32px; + height: 32px; + border-radius: var(--c2-radius-sm); + font-size: 16px; + flex-shrink: 0; +} + +.c2-payload-desc { + font-size: 13px; + color: var(--c2-text-dim); + margin-bottom: 28px; + line-height: 1.6; +} + +.c2-oneliner-output { + background: #0f172a; + color: #a5f3fc; + padding: 16px 18px; + border-radius: var(--c2-radius-sm); + font-family: var(--c2-mono); + font-size: 12px; + word-break: break-all; + margin-top: 16px; + white-space: pre-wrap; + max-height: 200px; + overflow-y: auto; + border: 1px solid #1e293b; + line-height: 1.6; + cursor: pointer; + transition: border-color 0.2s; +} + +.c2-oneliner-output:hover { border-color: var(--c2-accent); } + +.c2-build-success { + margin-top: 16px; + padding: 16px 18px; + background: var(--c2-green-dim); + border: 1px solid rgba(16, 185, 129, 0.2); + border-radius: var(--c2-radius-sm); + color: #047857; + font-size: 13px; + line-height: 1.8; +} + +/* ============================================================================ + Events + ============================================================================ */ + +.c2-events-page-wrap { + display: flex; + flex-direction: column; + gap: 0; +} + +.c2-events-toolbar { + display: flex; + align-items: center; + gap: 12px; + padding: 8px 4px 10px; + border-bottom: 1px solid var(--c2-border); + margin-bottom: 0; +} + +.c2-events-select-all-label { + display: inline-flex; + align-items: center; + gap: 8px; + font-size: 13px; + color: var(--c2-text-muted); + cursor: pointer; + user-select: none; +} + +.c2-events-select-all-label input { + cursor: pointer; +} + +.c2-event-check-label { + flex-shrink: 0; + display: flex; + align-items: flex-start; + padding-top: 4px; + cursor: pointer; +} + +.c2-event-check-label input { + cursor: pointer; +} + +.c2-event-row-delete { + flex-shrink: 0; + align-self: flex-start; + margin-top: 2px; + padding: 4px 10px; + font-size: 14px; + line-height: 1.2; +} + +.c2-event-list { + background: var(--c2-surface); + border-radius: var(--c2-radius); + border: 1px solid var(--c2-border); + max-height: calc(100vh - 200px); + overflow-y: auto; +} + +.c2-event-item { + display: flex; + align-items: flex-start; + gap: 14px; + padding: 14px 20px; + border-bottom: 1px solid var(--c2-border); + transition: background 0.1s; +} + +.c2-event-item:hover { background: var(--c2-surface-alt); } +.c2-event-item:last-child { border-bottom: none; } + +.c2-event-level { + width: 8px; + height: 8px; + border-radius: 50%; + margin-top: 6px; + flex-shrink: 0; +} + +.c2-event-level.info { background: var(--c2-accent); } +.c2-event-level.warn { background: var(--c2-amber); } +.c2-event-level.critical { background: var(--c2-red); } + +.c2-event-content { flex: 1; } + +.c2-event-message { + font-size: 13px; + color: var(--c2-text); + margin-bottom: 3px; + font-weight: 500; +} + +.c2-event-meta { + font-size: 11px; + color: var(--c2-text-muted); + font-family: var(--c2-mono); +} + +/* ============================================================================ + Profile + ============================================================================ */ + +.c2-profile-list { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); + gap: 16px; + padding: 24px; +} + +.c2-profile-list:has(.c2-empty) { + display: flex; +} + +.c2-profile-card { + background: var(--c2-surface); + border: 1.5px solid var(--c2-border); + border-radius: var(--c2-radius); + padding: 24px; + transition: all 0.2s; +} + +.c2-profile-card:hover { + box-shadow: var(--c2-shadow-md); + border-color: var(--c2-border-hover); +} + +.c2-profile-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 16px; +} + +.c2-profile-header h4 { + font-size: 15px; + color: var(--c2-text); + font-weight: 700; +} + +.c2-profile-info { + font-size: 12px; + color: var(--c2-text-dim); + line-height: 2; +} + +.c2-profile-info strong { + color: var(--c2-text); + font-weight: 600; + margin-right: 4px; +} + +/* ============================================================================ + Info Grid + ============================================================================ */ + +.c2-info-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); + gap: 0; + background: var(--c2-surface); + border-radius: var(--c2-radius); + border: 1px solid var(--c2-border); + padding: 4px 24px; +} + +.c2-info-grid > div { + padding: 12px 0; + border-bottom: 1px solid var(--c2-border); + font-size: 13px; + color: var(--c2-text-dim); +} + +.c2-info-grid > div:last-child { border-bottom: none; } +.c2-info-grid > div strong { + color: var(--c2-text); + font-weight: 600; + margin-right: 8px; + display: inline-block; + min-width: 100px; +} + +/* ============================================================================ + Task Detail Modal + ============================================================================ */ + +.c2-task-detail { line-height: 2; } +.c2-task-detail > div { margin-bottom: 6px; font-size: 13px; } + +.c2-task-error { + color: var(--c2-red); + padding: 14px; + background: var(--c2-red-dim); + border: 1px solid rgba(239, 68, 68, 0.15); + border-radius: var(--c2-radius-sm); + margin-top: 12px; + font-size: 13px; +} + +.c2-task-result pre { + background: #0f172a; + color: #e2e8f0; + padding: 16px; + border-radius: var(--c2-radius-sm); + overflow-x: auto; + font-family: var(--c2-mono); + font-size: 12px; + margin-top: 8px; + max-height: 400px; + overflow-y: auto; + border: 1px solid #1e293b; + line-height: 1.6; +} + +/* ============================================================================ + Forms + ============================================================================ */ + +.c2-form-row { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 16px; + margin-bottom: 4px; +} + +.c2-form-group { margin-bottom: 20px; } + +.c2-form-group label { + display: block; + font-size: 13px; + font-weight: 600; + margin-bottom: 6px; + color: var(--c2-text); +} + +.c2-form-group .form-hint { + font-size: 12px; + color: var(--c2-text-muted); + margin-top: 6px; +} + +/* ============================================================================ + Modal + ============================================================================ */ + +.c2-modal-overlay { + position: fixed; + top: 0; left: 0; right: 0; bottom: 0; + background: rgba(15, 23, 42, 0.5); + backdrop-filter: blur(8px); + -webkit-backdrop-filter: blur(8px); + display: flex; + align-items: center; + justify-content: center; + z-index: 1000; + padding: 24px; + animation: c2-fade-in 0.15s ease-out; +} + +@keyframes c2-fade-in { + from { opacity: 0; } + to { opacity: 1; } +} + +.c2-modal { + background: var(--c2-surface); + border-radius: 16px; + width: 100%; + max-width: 540px; + max-height: 85vh; + overflow-y: auto; + box-shadow: var(--c2-shadow-lg); + border: 1px solid var(--c2-border); + animation: c2-slide-up 0.2s ease-out; +} + +@keyframes c2-slide-up { + from { opacity: 0; transform: translateY(12px) scale(0.98); } + to { opacity: 1; transform: translateY(0) scale(1); } +} + +.c2-modal-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 24px 28px 20px; + border-bottom: 1px solid var(--c2-border); +} + +.c2-modal-header h3 { + font-size: 18px; + font-weight: 700; + color: var(--c2-text); +} + +.c2-modal-close { + font-size: 18px; + cursor: pointer; + color: var(--c2-text-muted); + background: none; + border: none; + padding: 0; + width: 32px; + height: 32px; + display: flex; + align-items: center; + justify-content: center; + border-radius: var(--c2-radius-xs); + transition: all 0.15s; +} + +.c2-modal-close:hover { + background: var(--c2-surface-alt); + color: var(--c2-text); +} + +.c2-modal-body { padding: 24px 28px; } + +.c2-modal-footer { + display: flex; + justify-content: flex-end; + gap: 10px; + padding: 16px 28px; + border-top: 1px solid var(--c2-border); + background: var(--c2-surface-alt); + border-radius: 0 0 16px 16px; +} + +/* ============================================================================ + Toolbar + ============================================================================ */ + +.c2-toolbar { + display: flex; + gap: 12px; + padding: 16px 24px; + border-bottom: 1px solid var(--c2-border); + align-items: center; + flex-wrap: wrap; +} + +/* ============================================================================ + Responsive + ============================================================================ */ + +@media (max-width: 768px) { + .c2-session-layout { flex-direction: column; } + .c2-session-sidebar { + width: 100%; + max-height: 220px; + border-right: none; + border-bottom: 1px solid var(--c2-border); + } + .c2-stats { flex-direction: column; gap: 12px; } + .c2-payload-grid { grid-template-columns: 1fr; } + .c2-listener-grid { grid-template-columns: 1fr; padding: 16px; } +} diff --git a/web/static/css/style.css b/web/static/css/style.css index 5edc266d..937bb1c5 100644 --- a/web/static/css/style.css +++ b/web/static/css/style.css @@ -12998,6 +12998,55 @@ header { .dashboard-kpi-row { grid-template-columns: 1fr; } } +/* C2 概览:主列内卡片,外层样式复用 .dashboard-grid .dashboard-section */ + +.dashboard-c2-strip { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 14px; +} + +@media (max-width: 720px) { + .dashboard-c2-strip { grid-template-columns: 1fr; } +} + +.dashboard-c2-stat { + background: linear-gradient(145deg, #f8fafc 0%, #eef2ff 100%); + border-radius: 12px; + padding: 14px 16px; + cursor: pointer; + border: 1px solid rgba(99, 102, 241, 0.14); + transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease; +} + +.dashboard-c2-stat:hover { + transform: translateY(-2px); + box-shadow: 0 8px 20px rgba(99, 102, 241, 0.12); + border-color: rgba(99, 102, 241, 0.28); +} + +.dashboard-c2-stat:focus-visible { + outline: 2px solid rgba(99, 102, 241, 0.45); + outline-offset: 2px; +} + +.dashboard-c2-stat-value { + font-size: 1.625rem; + font-weight: 800; + color: var(--text-primary); + line-height: 1.15; + letter-spacing: -0.02em; + font-variant-numeric: tabular-nums; +} + +.dashboard-c2-stat-label { + display: block; + margin-top: 6px; + font-size: 0.8125rem; + color: var(--text-secondary); + font-weight: 500; +} + .dashboard-kpi-card { background: #fff; border-radius: 14px; diff --git a/web/static/i18n/en-US.json b/web/static/i18n/en-US.json index 39606d84..e86c926a 100644 --- a/web/static/i18n/en-US.json +++ b/web/static/i18n/en-US.json @@ -75,7 +75,14 @@ "roles": "Roles", "rolesManagement": "Roles Management", "settings": "System settings", - "hitl": "Human-in-the-loop" + "hitl": "Human-in-the-loop", + "c2": "C2", + "c2Listeners": "Listeners", + "c2Sessions": "Sessions", + "c2Tasks": "Tasks", + "c2Payloads": "Payload", + "c2Events": "Events", + "c2Profiles": "Traffic profiles" }, "dashboard": { "title": "Dashboard", @@ -88,6 +95,14 @@ "clickToViewTasks": "Click to view tasks", "clickToViewVuln": "Click to view vulnerabilities", "clickToViewMCP": "Click to view MCP monitor", + "c2OverviewTitle": "C2 overview", + "c2GoManage": "Open C2 →", + "c2ListenersRunning": "Listeners running", + "c2SessionsOnline": "Sessions online", + "c2TasksPending": "Pending / queued tasks", + "c2ClickListeners": "View listeners", + "c2ClickSessions": "View sessions", + "c2ClickTasks": "View tasks", "severityDistribution": "Vulnerability severity distribution", "severityCritical": "Critical", "severityHigh": "High", @@ -263,6 +278,7 @@ "einoSubAgentStep": "Sub-agent {{agent}} · step {{n}}", "aiThinking": "AI thinking", "planning": "Planning", + "assistantStreamPhase": "Assistant output", "toolCallsDetected": "Detected {{count}} tool call(s)", "callTool": "Call tool: {{name}} ({{index}}/{{total}})", "toolExecComplete": "Tool {{name}} completed", @@ -2032,5 +2048,309 @@ "roleFilterOnBanner": "These tools are checked and linked to this role (independent of MCP-wide enable).", "roleFilterOffBanner": "These tools are unchecked and not linked to this role.", "checkboxLinkTitle": "Check to link this tool to this role" + }, + "c2": { + "title": "C2 Management", + "welcomeTitle": "AI-Native C2 Framework", + "welcomeDesc": "MCP-native design: let LLM call C2 like calling nmap to complete the full chain: initial access → control → tasks → lateral movement → cleanup", + "statListeners": "Running Listeners", + "statSessions": "Online Sessions", + "statPending": "Pending Tasks", + "goListeners": "Manage Listeners", + "goSessions": "View Sessions", + "clipboardCopied": "Copied to clipboard", + "fmt": { + "durationMs": "{{n}}ms", + "durationSec": "{{n}}s", + "durationMin": "{{n}}m" + }, + "files": { + "parent": "Parent", + "refresh": "Refresh", + "loading": "Loading…", + "timeout": "Timed out loading files", + "emptyDir": "Empty directory", + "colName": "Name", + "colSize": "Size", + "colMode": "Mode", + "colActions": "Actions", + "open": "Open", + "download": "Download", + "failed": "Failed" + }, + "listeners": { + "title": "Listener Management", + "create": "Create Listener", + "name": "Name", + "type": "Type", + "bindHost": "Bind Host", + "bindPort": "Bind Port", + "status": "Status", + "remark": "Remark", + "actions": "Actions", + "start": "Start", + "stop": "Stop", + "delete": "Delete", + "edit": "Edit", + "running": "Running", + "stopped": "Stopped", + "placeholderName": "Enter listener name", + "placeholderHost": "Default 127.0.0.1", + "placeholderPort": "Enter port number", + "placeholderRemark": "Optional remark", + "emptyTitle": "No listeners yet", + "emptyHint": "Create your first C2 listener using the button below", + "headerCreateBtn": "+ Create Listener", + "modalCreateTitle": "Create Listener", + "placeholderNameExample": "e.g. http-beacon-01", + "bindHintExternal": "Use 0.0.0.0 to allow external access", + "callbackHost": "Callback host (optional)", + "callbackHostHint": "Public IP or hostname stored for payloads/beacons; separate from bind address. If empty, payload generation falls back to bind address / auto-detect.", + "placeholderRemarkLong": "Optional remark", + "editTitle": "Edit Listener", + "startedAt": "Started {{time}}", + "confirmDelete": "Delete this listener? All related sessions and tasks will be removed.", + "toastFillRequired": "Please fill in all required fields", + "toastCreated": "Listener created", + "toastStarted": "Listener started", + "toastStopped": "Listener stopped", + "toastDeleted": "Listener deleted", + "toastUpdated": "Listener updated", + "submitCreate": "Create", + "typeLabels": { + "http_beacon": "HTTP Beacon", + "https_beacon": "HTTPS Beacon", + "tcp_reverse": "TCP Reverse", + "websocket": "WebSocket" + } + }, + "sessions": { + "title": "Session Management", + "hostname": "Hostname", + "username": "Username", + "os": "OS", + "arch": "Arch", + "ip": "IP Address", + "status": "Status", + "active": "Active", + "sleeping": "Sleeping", + "dead": "Dead", + "isAdmin": "Admin", + "pid": "PID", + "sleep": "Sleep Interval", + "jitter": "Jitter", + "firstSeen": "First Seen", + "lastCheckIn": "Last Check-in", + "selectSession": "Select Session", + "terminal": "Terminal", + "files": "Files", + "tasks": "Tasks", + "info": "Info", + "execute": "Execute Command", + "kill": "Kill Session", + "cardDeleteSession": "Delete", + "btnSleep": "Sleep", + "emptyTitle": "No active sessions", + "emptyHint": "Start a listener and run a payload on the target", + "unknownHost": "unknown", + "rootBadge": "ROOT", + "curlBeaconTitle": "Lightweight Curl beacon", + "curlBeaconBody": "This session was created with a Curl oneliner: heartbeat and tasks only.\nInteractive terminal and file management require a compiled Beacon binary or a TCP reverse listener.", + "infoSessionId": "Session ID", + "infoImplantUuid": "Implant UUID", + "infoHostname": "Hostname", + "infoUsername": "Username", + "infoOs": "OS", + "infoArch": "Arch", + "infoPid": "PID", + "infoProcess": "Process", + "infoAdmin": "Admin", + "infoInternalIp": "Internal IP", + "infoSleep": "Sleep", + "infoSleepLine": "{{sec}}s (jitter {{jitter}}%)", + "infoFirstSeen": "First seen", + "infoLastCheckin": "Last check-in", + "infoNote": "Note", + "adminYes": "Yes", + "adminNo": "No", + "promptSleepSeconds": "Sleep interval (seconds)", + "promptJitterPercent": "Jitter percent (0–100)", + "toastSleepUpdated": "Sleep settings updated", + "confirmExitSession": "Send exit command to this session?", + "confirmDeleteSession": "Remove this session and related tasks/files from the server? (Does not send exit to the implant; use Kill Session to exit the agent.)", + "toastExitSent": "Exit command sent", + "toastSessionDeleted": "Session record deleted", + "terminalWelcome": "CyberStrikeAI C2 Terminal — AI-Native Command & Control", + "termStatusReady": "Ready", + "termStatusExec": "Executing…", + "termStatusErr": "Error", + "termStatusTimeout": "Timeout", + "termNoSession": "Error: no session selected", + "termWaitTimeout": "[Timed out waiting for result]", + "termCleared": "Terminal cleared", + "termNoSelection": "No text selected", + "clearTerminal": "Clear" + }, + "tasks": { + "title": "Task Management", + "taskId": "Task ID", + "type": "Type", + "status": "Status", + "result": "Result", + "error": "Error", + "duration": "Duration", + "createdAt": "Created At", + "queued": "Queued", + "sent": "Sent", + "running": "Running", + "success": "Success", + "failed": "Failed", + "cancelled": "Cancelled", + "viewResult": "View Result", + "cancel": "Cancel Task", + "refresh": "Refresh Tasks", + "pending": "Pending", + "emptyAll": "No tasks yet", + "emptySession": "No tasks for this session", + "colTask": "Task", + "colSession": "Session", + "colType": "Type", + "colStatus": "Status", + "colDuration": "Duration", + "colCreated": "Created", + "colActions": "Actions", + "view": "View", + "cancelBtn": "Cancel", + "modalTitle": "Task details", + "labelId": "ID", + "labelSession": "Session", + "labelType": "Type", + "labelStatus": "Status", + "labelCreated": "Created", + "labelSent": "Sent", + "labelCompleted": "Completed", + "labelDuration": "Duration", + "labelError": "Error", + "labelResult": "Output", + "toastCancelled": "Task cancelled", + "batchDelete": "Delete selected", + "selectAll": "Select all on this page", + "deleteOne": "Delete task", + "deleteBtn": "Delete", + "confirmDeleteOne": "Delete this task record from the server? This cannot be undone.", + "confirmBatchDelete": "Delete {{n}} selected task record(s)? This cannot be undone.", + "toastSelectFirst": "Select tasks to delete first", + "toastDeleted": "Deleted {{n}} task(s)", + "paginationShow": "Showing {{start}}-{{end}} / {{total}} records", + "paginationPerPage": "Per page", + "paginationFirst": "First", + "paginationPrev": "Previous", + "paginationPage": "Page {{current}} / {{total}}", + "paginationNext": "Next", + "paginationLast": "Last" + }, + "payloads": { + "title": "Payload Generator", + "oneliner": "Oneliner Payload", + "build": "Build Beacon", + "listener": "Listener", + "kind": "Kind", + "host": "Callback Host", + "generate": "Generate", + "copy": "Copy to Clipboard", + "copied": "Copied", + "os": "Target OS", + "arch": "Target Arch", + "buildBtn": "Build", + "building": "Building...", + "download": "Download", + "linux": "Linux", + "windows": "Windows", + "darwin": "macOS", + "amd64": "AMD64", + "arm64": "ARM64", + "386": "386", + "arm": "ARM", + "onelinerDesc": "Generate a one-line reverse shell for the target (Bash / Python / PowerShell / Curl).", + "buildDesc": "Cross-compile a full Beacon binary for Linux / Windows / macOS.", + "hostOptional": "Callback host (optional)", + "placeholderListenerHost": "Leave empty: listener callback host, else bind address", + "generateOnelinerBtn": "Generate Oneliner", + "buildBeaconBtn": "Build Beacon", + "loopbackBeaconWarning": "127.0.0.1 works only when the beacon and C2 share the same host and network namespace (simple local test). If C2 runs in Docker and the beacon runs on the host, 127.0.0.1 hits the host loopback, not the container — use the published port plus the host LAN IP or host.docker.internal. For remote targets, bind 0.0.0.0 and rebuild.", + "noBeaconListenersTcpOnly": "tcp_reverse supports compiled Beacon (TCP + CSB1 framing); classic shell still uses Oneliner above", + "noListenersOption": "No listeners available", + "noKindOption": "No kinds available", + "toastLoadListenersFail": "Failed to load listeners: {{msg}}", + "toastPickListener": "Select a listener first", + "toastOnelinerFail": "Failed to generate oneliner: {{msg}}", + "toastBuildFail": "Failed to build beacon: {{msg}}", + "toastBuildSuccess": "Build succeeded: {{bytes}} bytes", + "buildSuccessTitle": "Build succeeded", + "buildMetaOsArch": "OS: {{os}} | Arch: {{arch}}", + "buildSize": "Size: {{bytes}} bytes", + "clickToCopyTitle": "Click to copy", + "toastDownloadQueued": "Download task queued" + }, + "events": { + "title": "Event Audit", + "level": "Level", + "category": "Category", + "message": "Message", + "time": "Time", + "info": "Info", + "warn": "Warning", + "critical": "Critical", + "listener": "Listener", + "session": "Session", + "task": "Task", + "payload": "Payload", + "opsec": "OPSEC", + "refresh": "Refresh", + "empty": "No events yet", + "batchDelete": "Delete selected", + "selectAll": "Select all on this page", + "deleteOne": "Delete", + "confirmDeleteOne": "Delete this event? This cannot be undone.", + "confirmBatchDelete": "Delete {{n}} selected event(s)? This cannot be undone.", + "toastSelectFirst": "Select events to delete first", + "toastDeleted": "Deleted {{n}} event(s)", + "paginationShow": "Showing {{start}}-{{end}} / {{total}} records", + "paginationPerPage": "Per page", + "paginationFirst": "First", + "paginationPrev": "Previous", + "paginationPage": "Page {{current}} / {{total}}", + "paginationNext": "Next", + "paginationLast": "Last" + }, + "profiles": { + "title": "Malleable Profile", + "name": "Name", + "userAgent": "User-Agent", + "uris": "URI Paths", + "headers": "Headers", + "jitter": "Jitter Range", + "create": "Create Profile", + "createBtn": "+ Create Profile", + "delete": "Delete", + "empty": "No profiles yet", + "defaultValue": "Default", + "modalCreateTitle": "Create Malleable Profile", + "profileNameLabel": "Profile name", + "placeholderProfileName": "e.g. cdn-fronting", + "hintUa": "Custom User-Agent for Beacon HTTP requests", + "labelBeaconUris": "Beacon URIs (one per line)", + "hintUris": "URI paths used when the beacon checks in", + "labelJitterMin": "Jitter min (ms)", + "labelJitterMax": "Jitter max (ms)", + "labelRespHeaders": "Custom response headers (JSON)", + "hintHeaders": "HTTP response headers to mimic a legitimate server", + "toastNameRequired": "Profile name is required", + "toastInvalidHeadersJson": "Invalid JSON in response headers", + "toastCreated": "Profile created", + "toastDeleted": "Profile deleted", + "confirmDelete": "Delete this profile?", + "submitCreate": "Create" + } } } diff --git a/web/static/i18n/zh-CN.json b/web/static/i18n/zh-CN.json index a5f579b5..b099f3fa 100644 --- a/web/static/i18n/zh-CN.json +++ b/web/static/i18n/zh-CN.json @@ -75,7 +75,14 @@ "roles": "角色", "rolesManagement": "角色管理", "settings": "系统设置", - "hitl": "人机协同" + "hitl": "人机协同", + "c2": "C2", + "c2Listeners": "监听器", + "c2Sessions": "会话", + "c2Tasks": "任务", + "c2Payloads": "载荷", + "c2Events": "事件", + "c2Profiles": "流量伪装" }, "dashboard": { "title": "仪表盘", @@ -88,6 +95,14 @@ "clickToViewTasks": "点击查看任务管理", "clickToViewVuln": "点击查看漏洞管理", "clickToViewMCP": "点击查看 MCP 监控", + "c2OverviewTitle": "C2 概览", + "c2GoManage": "进入 C2 →", + "c2ListenersRunning": "运行中监听器", + "c2SessionsOnline": "在线会话", + "c2TasksPending": "待审 / 排队任务", + "c2ClickListeners": "查看监听器", + "c2ClickSessions": "查看会话", + "c2ClickTasks": "查看任务", "severityDistribution": "漏洞严重程度分布", "severityCritical": "严重", "severityHigh": "高危", @@ -252,6 +267,7 @@ "einoSubAgentStep": "子代理 {{agent}} · 第 {{n}} 步", "aiThinking": "AI思考", "planning": "规划中", + "assistantStreamPhase": "助手输出", "toolCallsDetected": "检测到 {{count}} 个工具调用", "callTool": "调用工具: {{name}} ({{index}}/{{total}})", "toolExecComplete": "工具 {{name}} 执行完成", @@ -2021,5 +2037,309 @@ "roleFilterOnBanner": "以下为「已勾选、关联到本角色」的工具(与 MCP 管理里全局开/关无关)。", "roleFilterOffBanner": "以下为「未勾选、未关联到本角色」的工具。", "checkboxLinkTitle": "勾选表示本角色关联使用该工具" + }, + "c2": { + "title": "C2 管理", + "welcomeTitle": "AI-Native C2 框架", + "welcomeDesc": "以 MCP 工具为一等公民,让 LLM 可以像调用 nmap 一样调用 C2 完成「上线 → 控制 → 任务 → 横向 → 清场」全流程", + "statListeners": "运行中监听器", + "statSessions": "在线会话", + "statPending": "待审任务", + "goListeners": "管理监听器", + "goSessions": "查看会话", + "clipboardCopied": "已复制到剪贴板", + "fmt": { + "durationMs": "{{n}}ms", + "durationSec": "{{n}}秒", + "durationMin": "{{n}}分钟" + }, + "files": { + "parent": "上级目录", + "refresh": "刷新", + "loading": "加载中…", + "timeout": "加载文件超时", + "emptyDir": "空目录", + "colName": "名称", + "colSize": "大小", + "colMode": "权限", + "colActions": "操作", + "open": "打开", + "download": "下载", + "failed": "失败" + }, + "listeners": { + "title": "监听器管理", + "create": "创建监听器", + "name": "名称", + "type": "类型", + "bindHost": "绑定地址", + "bindPort": "绑定端口", + "status": "状态", + "remark": "备注", + "actions": "操作", + "start": "启动", + "stop": "停止", + "delete": "删除", + "edit": "编辑", + "running": "运行中", + "stopped": "已停止", + "placeholderName": "输入监听器名称", + "placeholderHost": "默认 127.0.0.1", + "placeholderPort": "输入端口号", + "placeholderRemark": "可选备注", + "emptyTitle": "暂无监听器", + "emptyHint": "点击下方按钮创建第一个 C2 监听器", + "headerCreateBtn": "+ 创建监听器", + "modalCreateTitle": "创建监听器", + "placeholderNameExample": "例如 http-beacon-01", + "bindHintExternal": "使用 0.0.0.0 允许外部访问", + "callbackHost": "回连地址(可选)", + "callbackHostHint": "公网 IP 或域名,写入配置供 Payload/Beacon 使用;与「绑定地址」分离。不填则生成 Payload 时按绑定地址或自动探测。", + "placeholderRemarkLong": "可选的备注说明", + "editTitle": "编辑监听器", + "startedAt": "启动于 {{time}}", + "confirmDelete": "确定删除此监听器?相关会话与任务将被清除。", + "toastFillRequired": "请填写必填项", + "toastCreated": "监听器已创建", + "toastStarted": "监听器已启动", + "toastStopped": "监听器已停止", + "toastDeleted": "监听器已删除", + "toastUpdated": "监听器已更新", + "submitCreate": "创建", + "typeLabels": { + "http_beacon": "HTTP Beacon", + "https_beacon": "HTTPS Beacon", + "tcp_reverse": "TCP 反向", + "websocket": "WebSocket" + } + }, + "sessions": { + "title": "会话管理", + "hostname": "主机名", + "username": "用户名", + "os": "操作系统", + "arch": "架构", + "ip": "IP 地址", + "status": "状态", + "active": "活跃", + "sleeping": "休眠", + "dead": "离线", + "isAdmin": "管理员", + "pid": "进程ID", + "sleep": "Sleep 间隔", + "jitter": "Jitter", + "firstSeen": "首次上线", + "lastCheckIn": "上次心跳", + "selectSession": "选择会话", + "terminal": "终端", + "files": "文件", + "tasks": "任务", + "info": "信息", + "execute": "执行命令", + "kill": "终止会话", + "cardDeleteSession": "删除会话", + "btnSleep": "Sleep", + "emptyTitle": "暂无在线会话", + "emptyHint": "启动监听器并在目标上执行 Payload", + "unknownHost": "未知", + "rootBadge": "ROOT", + "curlBeaconTitle": "轻量级 Curl 信标", + "curlBeaconBody": "此会话由 Curl Oneliner 建立,仅支持心跳保活和任务下发。\n交互式终端与文件管理需使用编译的 Beacon 可执行文件或 TCP 反向监听器。", + "infoSessionId": "会话 ID", + "infoImplantUuid": "植入体 UUID", + "infoHostname": "主机名", + "infoUsername": "用户名", + "infoOs": "操作系统", + "infoArch": "架构", + "infoPid": "PID", + "infoProcess": "进程", + "infoAdmin": "管理员", + "infoInternalIp": "内网 IP", + "infoSleep": "Sleep", + "infoSleepLine": "{{sec}} 秒(抖动 {{jitter}}%)", + "infoFirstSeen": "首次上线", + "infoLastCheckin": "上次心跳", + "infoNote": "备注", + "adminYes": "是", + "adminNo": "否", + "promptSleepSeconds": "Sleep 间隔(秒)", + "promptJitterPercent": "抖动百分比(0–100)", + "toastSleepUpdated": "Sleep 设置已更新", + "confirmExitSession": "向该会话发送退出指令?", + "confirmDeleteSession": "从服务器删除此会话及其关联任务与文件记录?(不会向植入体发送退出;若需退出目标进程请使用「终止会话」。)", + "toastExitSent": "退出指令已发送", + "toastSessionDeleted": "会话记录已删除", + "terminalWelcome": "CyberStrikeAI C2 终端 — AI-Native 命令与控制", + "termStatusReady": "就绪", + "termStatusExec": "执行中…", + "termStatusErr": "错误", + "termStatusTimeout": "超时", + "termNoSession": "错误:未选择会话", + "termWaitTimeout": "[等待结果超时]", + "termCleared": "终端已清屏", + "termNoSelection": "未选中文本", + "clearTerminal": "清屏" + }, + "tasks": { + "title": "任务管理", + "taskId": "任务ID", + "type": "类型", + "status": "状态", + "result": "结果", + "error": "错误", + "duration": "耗时", + "createdAt": "创建时间", + "queued": "队列中", + "sent": "已发送", + "running": "执行中", + "success": "成功", + "failed": "失败", + "cancelled": "已取消", + "viewResult": "查看结果", + "cancel": "取消任务", + "refresh": "刷新任务", + "pending": "待处理", + "emptyAll": "暂无任务", + "emptySession": "该会话暂无任务", + "colTask": "任务", + "colSession": "会话", + "colType": "类型", + "colStatus": "状态", + "colDuration": "耗时", + "colCreated": "创建时间", + "colActions": "操作", + "view": "查看", + "cancelBtn": "取消", + "modalTitle": "任务详情", + "labelId": "ID", + "labelSession": "会话", + "labelType": "类型", + "labelStatus": "状态", + "labelCreated": "创建时间", + "labelSent": "发送时间", + "labelCompleted": "完成时间", + "labelDuration": "耗时", + "labelError": "错误", + "labelResult": "输出", + "toastCancelled": "任务已取消", + "batchDelete": "批量删除", + "selectAll": "全选本页", + "deleteOne": "删除任务", + "deleteBtn": "删除", + "confirmDeleteOne": "确定从服务器删除该任务记录吗?此操作不可恢复。", + "confirmBatchDelete": "确定删除选中的 {{n}} 条任务记录吗?此操作不可恢复。", + "toastSelectFirst": "请先勾选要删除的任务", + "toastDeleted": "已删除 {{n}} 条", + "paginationShow": "显示 {{start}}-{{end}} / 共 {{total}} 条记录", + "paginationPerPage": "每页显示", + "paginationFirst": "首页", + "paginationPrev": "上一页", + "paginationPage": "第 {{current}} / {{total}} 页", + "paginationNext": "下一页", + "paginationLast": "末页" + }, + "payloads": { + "title": "Payload 生成", + "oneliner": "单行 Payload", + "build": "编译 Beacon", + "listener": "监听器", + "kind": "类型", + "host": "回连地址", + "generate": "生成", + "copy": "复制到剪贴板", + "copied": "已复制", + "os": "目标系统", + "arch": "目标架构", + "buildBtn": "构建", + "building": "构建中...", + "download": "下载", + "linux": "Linux", + "windows": "Windows", + "darwin": "macOS", + "amd64": "AMD64", + "arm64": "ARM64", + "386": "386", + "arm": "ARM", + "onelinerDesc": "快速生成可在目标机直接执行的反弹命令,支持 Bash / Python / PowerShell / Curl", + "buildDesc": "交叉编译多平台完整 Beacon 可执行文件,支持 Linux / Windows / macOS", + "hostOptional": "回连地址(可选)", + "placeholderListenerHost": "留空则优先监听器「回连地址」,否则按绑定地址", + "generateOnelinerBtn": "生成 Oneliner", + "buildBeaconBtn": "构建 Beacon", + "loopbackBeaconWarning": "127.0.0.1 仅适合「Beacon 与 C2 在同一台机、同一网络环境」本机自测。若 C2 跑在 Docker 里、Beacon 在宿主机直跑,127.0.0.1 会连到宿主而非容器,往往不上线,请改用映射端口 + 主机局域网 IP 或 host.docker.internal。远程目标请绑定 0.0.0.0 并重新编译。", + "noBeaconListenersTcpOnly": "tcp_reverse 监听器现已支持编译 Beacon(反向 TCP + 魔数 CSB1 成帧);经典 shell 仍可用上方单行 Payload", + "noListenersOption": "暂无可用监听器", + "noKindOption": "无可用类型", + "toastLoadListenersFail": "加载监听器列表失败:{{msg}}", + "toastPickListener": "请先选择一个监听器", + "toastOnelinerFail": "生成 Oneliner 失败:{{msg}}", + "toastBuildFail": "构建 Beacon 失败:{{msg}}", + "toastBuildSuccess": "构建成功:{{bytes}} bytes", + "buildSuccessTitle": "构建成功", + "buildMetaOsArch": "系统:{{os}} | 架构:{{arch}}", + "buildSize": "大小:{{bytes}} bytes", + "clickToCopyTitle": "点击复制", + "toastDownloadQueued": "下载任务已排队" + }, + "events": { + "title": "事件审计", + "level": "级别", + "category": "类别", + "message": "消息", + "time": "时间", + "info": "信息", + "warn": "警告", + "critical": "严重", + "listener": "监听器", + "session": "会话", + "task": "任务", + "payload": "Payload", + "opsec": "OPSEC", + "refresh": "刷新", + "empty": "暂无事件", + "batchDelete": "批量删除", + "selectAll": "全选本页", + "deleteOne": "删除", + "confirmDeleteOne": "确定删除该条事件吗?此操作不可恢复。", + "confirmBatchDelete": "确定删除选中的 {{n}} 条事件吗?此操作不可恢复。", + "toastSelectFirst": "请先勾选要删除的事件", + "toastDeleted": "已删除 {{n}} 条", + "paginationShow": "显示 {{start}}-{{end}} / 共 {{total}} 条记录", + "paginationPerPage": "每页显示", + "paginationFirst": "首页", + "paginationPrev": "上一页", + "paginationPage": "第 {{current}} / {{total}} 页", + "paginationNext": "下一页", + "paginationLast": "末页" + }, + "profiles": { + "title": "流量伪装", + "name": "名称", + "userAgent": "User-Agent", + "uris": "URI 路径", + "headers": "请求头", + "jitter": "Jitter 范围", + "create": "创建 Profile", + "createBtn": "+ 创建 Profile", + "delete": "删除", + "empty": "暂无 Profile", + "defaultValue": "默认", + "modalCreateTitle": "创建 Malleable Profile", + "profileNameLabel": "Profile 名称", + "placeholderProfileName": "例如 cdn-fronting", + "hintUa": "自定义 Beacon HTTP 请求中的 User-Agent 头", + "labelBeaconUris": "Beacon URI(每行一个)", + "hintUris": "Beacon 回连使用的 URI 路径", + "labelJitterMin": "Jitter 最小值 (ms)", + "labelJitterMax": "Jitter 最大值 (ms)", + "labelRespHeaders": "自定义响应头 (JSON)", + "hintHeaders": "用于伪装为合法服务的 HTTP 响应头", + "toastNameRequired": "请填写 Profile 名称", + "toastInvalidHeadersJson": "响应头 JSON 无效", + "toastCreated": "Profile 已创建", + "toastDeleted": "Profile 已删除", + "confirmDelete": "确定删除此 Profile?", + "submitCreate": "创建" + } } } diff --git a/web/static/js/c2.js b/web/static/js/c2.js new file mode 100644 index 00000000..6b8e82c4 --- /dev/null +++ b/web/static/js/c2.js @@ -0,0 +1,1875 @@ +// C2 模块前端逻辑 - 完整实现 +// 支持: xterm 终端、文件管理、监听器/会话/任务/事件/Payload/Profile 管理 + +(function() { + 'use strict'; + + // C2 模块命名空间 + const C2 = { + currentPage: '', + listeners: [], + sessions: [], + tasks: [], + tasksPage: 1, + tasksPageSize: 10, + tasksTotal: 0, + tasksPendingQueuedCount: null, + events: [], + eventsPage: 1, + eventsPageSize: 10, + eventsTotal: 0, + profiles: [], + selectedSessionId: null, + selectedListenerId: null, + eventSource: null, + // xterm 相关 + terminalInstance: null, + terminalFitAddon: null, + terminalResizeObserver: null, + terminalContainer: null, + terminalSessionId: 'main', + // 文件管理 + currentPath: '/', + fileList: [], + // 任务轮询 + taskPollInterval: null, + }; + + // API 基础路径 + const API_BASE = '/api/c2'; + + window.__c2DownloadPayload = function(filename) { + const url = `${API_BASE}/payloads/${filename}/download`; + const fetchFn = (typeof apiFetch === 'function') ? apiFetch : fetch; + fetchFn(url).then(resp => { + if (!resp.ok) throw new Error('download failed: ' + resp.status); + return resp.blob(); + }).then(blob => { + const a = document.createElement('a'); + a.href = URL.createObjectURL(blob); + a.download = filename; + document.body.appendChild(a); + a.click(); + a.remove(); + URL.revokeObjectURL(a.href); + }).catch(err => { + if (window.showToast) window.showToast(err.message, 'error'); + }); + }; + + function c2t(key, opts) { + try { + if (typeof window.t === 'function') return window.t(key, opts || {}); + } catch (e) {} + return key; + } + + function listenerTypeLabel(type) { + if (!type) return ''; + const k = 'c2.listeners.typeLabels.' + String(type).toLowerCase(); + const tr = c2t(k); + if (tr !== k) return tr; + return String(type).replace(/_/g, ' '); + } + + function sessionStatusLabel(status) { + const s = String(status || '').toLowerCase(); + if (!s) return ''; + const k = 'c2.sessions.' + s; + const tr = c2t(k); + if (tr !== k) return tr; + return status; + } + + function taskStatusLabel(status) { + const s = String(status || '').toLowerCase(); + if (!s) return ''; + const k = 'c2.tasks.' + s; + const tr = c2t(k); + if (tr !== k) return tr; + return status; + } + + // ============================================================================ + // 工具函数 + // ============================================================================ + + function apiRequest(method, url, data) { + const options = { + method: method, + headers: { 'Content-Type': 'application/json' } + }; + if (data && (method === 'POST' || method === 'PUT' || method === 'PATCH' || method === 'DELETE')) { + options.body = JSON.stringify(data); + } + if (typeof apiFetch === 'function') { + return apiFetch(url, options).then(r => r.json()); + } + return fetch(url, options).then(r => r.json()); + } + + function showToast(message, type = 'info') { + if (window.showToast) { + window.showToast(message, type); + return; + } + const container = document.getElementById('c2-toast-container') || (() => { + const div = document.createElement('div'); + div.id = 'c2-toast-container'; + div.style.cssText = 'position:fixed;top:20px;right:20px;z-index:10000;display:flex;flex-direction:column;gap:8px;'; + document.body.appendChild(div); + return div; + })(); + const toast = document.createElement('div'); + const colors = { error: '#e53e3e', success: '#38a169', info: '#3182ce', warn: '#d69e2e' }; + toast.style.cssText = `background:${colors[type] || colors.info};color:#fff;padding:10px 18px;border-radius:6px;font-size:0.875rem;box-shadow:0 4px 12px rgba(0,0,0,0.2);opacity:0;transition:opacity .3s;max-width:400px;word-break:break-word;`; + toast.textContent = message; + container.appendChild(toast); + requestAnimationFrame(() => { toast.style.opacity = '1'; }); + setTimeout(() => { + toast.style.opacity = '0'; + setTimeout(() => toast.remove(), 300); + }, 3500); + } + + function formatTime(dateStr) { + if (!dateStr) return '-'; + return new Date(dateStr).toLocaleString(); + } + + function formatDuration(ms) { + if (!ms || ms <= 0) return '-'; + if (ms < 1000) return c2t('c2.fmt.durationMs', { n: ms }); + if (ms < 60000) return c2t('c2.fmt.durationSec', { n: (ms / 1000).toFixed(1) }); + return c2t('c2.fmt.durationMin', { n: (ms / 60000).toFixed(1) }); + } + + function escapeHtml(text) { + if (!text) return ''; + const div = document.createElement('div'); + div.textContent = text; + return div.innerHTML; + } + + function copyToClipboard(text) { + if (navigator.clipboard) { + navigator.clipboard.writeText(text).then(() => showToast(c2t('c2.clipboardCopied'), 'success')); + } else { + const ta = document.createElement('textarea'); + ta.value = text; + document.body.appendChild(ta); + ta.select(); + document.execCommand('copy'); + document.body.removeChild(ta); + showToast(c2t('c2.clipboardCopied'), 'success'); + } + } + + // ============================================================================ + // 页面初始化 + // ============================================================================ + + C2.init = function() { + const pageId = window.currentPageId || ''; + + if (pageId.startsWith('c2')) { + C2.connectEventStream(); + } + + switch(pageId) { + case 'c2': + case 'c2-listeners': + C2.loadListeners(); + break; + case 'c2-sessions': + C2.loadSessions(); + break; + case 'c2-tasks': + C2.loadTasks(); + break; + case 'c2-payloads': + C2.loadListenersForPayload(); + break; + case 'c2-events': + C2.loadEvents(); + break; + case 'c2-profiles': + C2.loadProfiles(); + break; + } + }; + + // ============================================================================ + // 监听器管理 + // ============================================================================ + + C2.loadListeners = function() { + apiRequest('GET', `${API_BASE}/listeners`).then(data => { + C2.listeners = data.listeners || []; + C2.renderListeners(); + C2.updateDashboardStats(); + }); + }; + + C2.renderListeners = function() { + const container = document.getElementById('c2-listener-grid'); + if (!container) return; + + if (C2.listeners.length === 0) { + container.innerHTML = ` +
+ + + + + + +

${escapeHtml(c2t('c2.listeners.emptyTitle'))}

+

${escapeHtml(c2t('c2.listeners.emptyHint'))}

+ +
`; + return; + } + + container.innerHTML = C2.listeners.map(l => ` +
+
+
+
${escapeHtml(l.name)}
+
${l.id.substring(0, 12)}...
+
+ ${escapeHtml(listenerTypeLabel(l.type))} +
+
+
+ + ${l.bindHost}:${l.bindPort} +
+ ${l.startedAt ? `
${escapeHtml(c2t('c2.listeners.startedAt', { time: formatTime(l.startedAt) }))}
` : ''} + ${l.remark ? `
${escapeHtml(l.remark)}
` : ''} +
+
+ ${l.status === 'stopped' + ? `` + : `` + } + + +
+
+ `).join(''); + }; + + C2.getListenerCallbackHost = function(l) { + if (!l) return ''; + try { + var raw = l.configJson != null ? l.configJson : '{}'; + var j = typeof raw === 'string' ? JSON.parse(raw || '{}') : (raw || {}); + return String(j.callback_host || '').trim(); + } catch (e) { + return ''; + } + }; + + C2.showCreateListenerModal = function() { + const modal = document.getElementById('c2-modal'); + const content = document.getElementById('c2-modal-content'); + if (!content) return; + + content.innerHTML = ` +
+

${escapeHtml(c2t('c2.listeners.modalCreateTitle'))}

+ +
+
+
+
+ + +
+
+ + +
+
+
+
+ + +
${escapeHtml(c2t('c2.listeners.bindHintExternal'))}
+
+
+ + +
+
+
+ + +
${escapeHtml(c2t('c2.listeners.callbackHostHint'))}
+
+
+ + +
+
+ + `; + modal.style.display = 'flex'; + }; + + C2.createListener = function() { + const name = document.getElementById('c2-listener-name')?.value.trim(); + const type = document.getElementById('c2-listener-type')?.value; + const bindHost = document.getElementById('c2-listener-host')?.value || '127.0.0.1'; + const bindPort = parseInt(document.getElementById('c2-listener-port')?.value); + const callbackHost = document.getElementById('c2-listener-callback-host')?.value?.trim() || ''; + const remark = document.getElementById('c2-listener-remark')?.value; + + if (!name || !type || !bindPort) { + showToast(c2t('c2.listeners.toastFillRequired'), 'error'); + return; + } + + apiRequest('POST', `${API_BASE}/listeners`, { + name, type, bind_host: bindHost, bind_port: bindPort, remark, + callback_host: callbackHost + }).then(data => { + if (data.error) { + showToast(data.error, 'error'); + } else { + showToast(c2t('c2.listeners.toastCreated'), 'success'); + C2.closeModal(); + C2.loadListeners(); + } + }); + }; + + C2.startListener = function(id) { + apiRequest('POST', `${API_BASE}/listeners/${id}/start`, {}).then(data => { + if (data.error) showToast(data.error, 'error'); + else { + showToast(c2t('c2.listeners.toastStarted'), 'success'); + C2.loadListeners(); + } + }); + }; + + C2.stopListener = function(id) { + apiRequest('POST', `${API_BASE}/listeners/${id}/stop`, {}).then(data => { + if (data.error) showToast(data.error, 'error'); + else { + showToast(c2t('c2.listeners.toastStopped'), 'success'); + C2.loadListeners(); + } + }); + }; + + C2.deleteListener = function(id) { + if (!confirm(c2t('c2.listeners.confirmDelete'))) return; + apiRequest('DELETE', `${API_BASE}/listeners/${id}`, {}).then(data => { + showToast(c2t('c2.listeners.toastDeleted'), 'success'); + C2.loadListeners(); + }); + }; + + C2.editListener = function(id) { + const l = C2.listeners.find(x => x.id === id); + if (!l) return; + + const cbHost = C2.getListenerCallbackHost(l); + + const modal = document.getElementById('c2-modal'); + const content = document.getElementById('c2-modal-content'); + if (!content) return; + + content.innerHTML = ` +
+

${escapeHtml(c2t('c2.listeners.editTitle'))}

+ +
+
+
+ + +
+
+
+ + +
+
+ + +
+
+
+ + +
${escapeHtml(c2t('c2.listeners.callbackHostHint'))}
+
+
+ + +
+
+ + `; + modal.style.display = 'flex'; + }; + + C2.saveListener = function(id) { + const name = document.getElementById('c2-listener-name')?.value.trim(); + const bindHost = document.getElementById('c2-listener-host')?.value; + const bindPort = parseInt(document.getElementById('c2-listener-port')?.value); + const callbackHost = document.getElementById('c2-listener-callback-host')?.value?.trim() ?? ''; + const remark = document.getElementById('c2-listener-remark')?.value; + + apiRequest('PUT', `${API_BASE}/listeners/${id}`, { + name, bind_host: bindHost, bind_port: bindPort, remark, + callback_host: callbackHost + }).then(data => { + if (data.error) showToast(data.error, 'error'); + else { + showToast(c2t('c2.listeners.toastUpdated'), 'success'); + C2.closeModal(); + C2.loadListeners(); + } + }); + }; + + // ============================================================================ + // 会话管理 + // ============================================================================ + + C2.loadSessions = function() { + return apiRequest('GET', `${API_BASE}/sessions`).then(data => { + C2.sessions = data.sessions || []; + C2.renderSessions(); + C2.updateDashboardStats(); + }); + }; + + C2.renderSessions = function() { + const list = document.getElementById('c2-session-list'); + const main = document.getElementById('c2-session-main'); + if (!list) return; + + if (C2.sessions.length === 0) { + list.innerHTML = ` +
+ + + + + +

${escapeHtml(c2t('c2.sessions.emptyTitle'))}

+

${escapeHtml(c2t('c2.sessions.emptyHint'))}

+
`; + if (main) main.innerHTML = ''; + return; + } + + list.innerHTML = C2.sessions.map(s => ` +
+
+ ${escapeHtml(s.hostname || c2t('c2.sessions.unknownHost'))} + ${escapeHtml(sessionStatusLabel(s.status))} +
+
+ ${escapeHtml(s.username)} · ${s.os}/${s.arch} + ${s.isAdmin ? '' + escapeHtml(c2t('c2.sessions.rootBadge')) + '' : ''} +
+
+ ${s.internalIp || '-'} · PID ${s.pid} +
+ +
+ `).join(''); + + if (C2.selectedSessionId && !C2.sessions.find(s => s.id === C2.selectedSessionId)) { + C2.selectedSessionId = null; + } + if (!C2.selectedSessionId && C2.sessions.length > 0) { + C2.selectSession(C2.sessions[0].id); + } + }; + + C2.selectSession = function(id) { + C2.selectedSessionId = id; + C2.renderSessions(); + C2.renderSessionDetail(id); + C2.initTerminal(); + }; + + C2.renderSessionDetail = function(id) { + const container = document.getElementById('c2-session-main'); + if (!container) return; + + const s = C2.sessions.find(x => x.id === id); + if (!s) return; + + const adminVal = s.isAdmin ? c2t('c2.sessions.adminYes') : c2t('c2.sessions.adminNo'); + const sleepLine = c2t('c2.sessions.infoSleepLine', { sec: s.sleepSeconds, jitter: s.jitterPercent }); + container.innerHTML = ` +
+
+
+

${escapeHtml(s.hostname)} ${escapeHtml(sessionStatusLabel(s.status))}

+
${s.id} | ${escapeHtml(s.username)}@${s.os}/${s.arch}
+
+
+ + +
+
+ +
+
${escapeHtml(c2t('c2.sessions.terminal'))}
+
${escapeHtml(c2t('c2.sessions.files'))}
+
${escapeHtml(c2t('c2.sessions.tasks'))}
+
${escapeHtml(c2t('c2.sessions.info'))}
+
+ +
+
+
+
+ + + ${escapeHtml(c2t('c2.sessions.termStatusReady'))} +
+
+ + + +
+
+ `; + + var isCurlBeacon = s.implantUuid && s.implantUuid.startsWith('curl_'); + if (isCurlBeacon) { + var termContainer = container.querySelector('#c2-terminal-container'); + if (termContainer) { + termContainer.innerHTML = + '
' + + '
📡
' + + '
' + escapeHtml(c2t('c2.sessions.curlBeaconTitle')) + '
' + + '
' + c2t('c2.sessions.curlBeaconBody').split('\n').map(function (ln) { return escapeHtml(ln); }).join('
') + '
' + + '
'; + } + } + setTimeout(() => { + if (!isCurlBeacon) C2.initTerminal(); + C2.loadFileList(s.id, '.'); + C2.loadSessionTasks(s.id); + }, 50); + }; + + C2.switchTab = function(tab) { + document.querySelectorAll('.c2-session-tab').forEach(el => el.classList.remove('active')); + document.querySelectorAll('.c2-tab-panel').forEach(el => el.style.display = 'none'); + + const tabEl = document.querySelector(`.c2-session-tab[data-tab="${tab}"]`); + if (tabEl) tabEl.classList.add('active'); + + const panel = document.getElementById(`c2-tab-${tab}`); + if (panel) panel.style.display = 'block'; + + if (tab === 'terminal') { + setTimeout(() => C2.fitTerminal(), 50); + } + }; + + C2.setSessionSleep = function(id) { + const sleep = prompt(c2t('c2.sessions.promptSleepSeconds'), '5'); + if (!sleep) return; + const jitter = prompt(c2t('c2.sessions.promptJitterPercent'), '0') || '0'; + + apiRequest('PUT', `${API_BASE}/sessions/${id}/sleep`, { + sleep_seconds: parseInt(sleep), + jitter_percent: parseInt(jitter) + }).then(data => { + if (data.error) showToast(data.error, 'error'); + else showToast(c2t('c2.sessions.toastSleepUpdated'), 'success'); + }); + }; + + C2.killSession = function(id) { + if (!confirm(c2t('c2.sessions.confirmExitSession'))) return; + apiRequest('POST', `${API_BASE}/tasks`, { + session_id: id, + task_type: 'exit', + payload: {} + }).then(data => { + showToast(c2t('c2.sessions.toastExitSent'), 'success'); + }); + }; + + C2.deleteSessionRecord = function(id) { + if (!confirm(c2t('c2.sessions.confirmDeleteSession'))) return; + apiRequest('DELETE', `${API_BASE}/sessions/${id}`, {}).then(data => { + if (data.error) { + showToast(data.error, 'error'); + return; + } + showToast(c2t('c2.sessions.toastSessionDeleted'), 'success'); + if (C2.selectedSessionId === id) C2.selectedSessionId = null; + C2.loadSessions(); + }); + }; + + // ============================================================================ + // xterm 终端 + // ============================================================================ + + C2.initTerminal = function() { + const container = document.getElementById('c2-terminal-container'); + if (!container || typeof Terminal === 'undefined') return; + + if (C2.terminalInstance) { + C2.terminalInstance.dispose(); + } + + const term = new Terminal({ + cursorBlink: true, + cursorStyle: 'block', + fontSize: 14, + fontFamily: 'Menlo, Monaco, "Courier New", monospace', + lineHeight: 1.3, + scrollback: 5000, + theme: { + background: '#0d1117', + foreground: '#e6edf3', + cursor: '#58a6ff', + selection: 'rgba(88, 166, 255, 0.3)' + } + }); + + if (typeof FitAddon !== 'undefined') { + const FitCtor = FitAddon.FitAddon || FitAddon; + C2.terminalFitAddon = new FitCtor(); + term.loadAddon(C2.terminalFitAddon); + } + + term.open(container); + + try { + if (C2.terminalFitAddon) C2.terminalFitAddon.fit(); + } catch (e) {} + + let lineBuffer = ''; + const prompt = '$ '; + + term.writeln('\x1b[36m' + c2t('c2.sessions.terminalWelcome') + '\x1b[0m'); + term.writeln(''); + term.write(prompt); + + term.onData(e => { + const code = e.charCodeAt(0); + if (code === 13) { // Enter + term.writeln(''); + const cmd = lineBuffer.trim(); + lineBuffer = ''; + if (cmd) { + C2.executeInTerminal(cmd, term); + } else { + term.write(prompt); + } + } else if (code === 127) { // Backspace + if (lineBuffer.length > 0) { + lineBuffer = lineBuffer.slice(0, -1); + term.write('\b \b'); + } + } else if (code >= 32) { // Printable + lineBuffer += e; + term.write(e); + } + }); + + C2.terminalInstance = term; + + // Resize observer + if (C2.terminalResizeObserver) { + C2.terminalResizeObserver.disconnect(); + } + C2.terminalResizeObserver = new ResizeObserver(() => { + C2.fitTerminal(); + }); + C2.terminalResizeObserver.observe(container); + }; + + C2.fitTerminal = function() { + if (C2.terminalFitAddon && C2.terminalInstance) { + try { + C2.terminalFitAddon.fit(); + } catch (e) {} + } + }; + + C2.executeInTerminal = function(cmd, term) { + if (!C2.selectedSessionId) { + term.writeln('\x1b[31m' + c2t('c2.sessions.termNoSession') + '\x1b[0m'); + term.write('$ '); + return; + } + + const statusEl = document.getElementById('c2-terminal-status'); + if (statusEl) statusEl.textContent = c2t('c2.sessions.termStatusExec'); + + apiRequest('POST', `${API_BASE}/tasks`, { + session_id: C2.selectedSessionId, + task_type: 'shell', + payload: { command: cmd, timeout_seconds: 60 } + }).then(data => { + if (data.error) { + term.writeln(`\x1b[31mError: ${data.error}\x1b[0m`); + term.write('$ '); + if (statusEl) statusEl.textContent = c2t('c2.sessions.termStatusErr'); + } else { + C2.waitForTaskResult(data.task?.id || data.task_id, term); + } + }); + }; + + C2.waitForTaskResult = function(taskId, term) { + let attempts = 0; + const maxAttempts = 60; + let delay = 500; + const maxDelay = 5000; + const check = () => { + if (++attempts > maxAttempts) { + term.writeln('\x1b[33m' + c2t('c2.sessions.termWaitTimeout') + '\x1b[0m'); + term.write('$ '); + const statusEl = document.getElementById('c2-terminal-status'); + if (statusEl) statusEl.textContent = c2t('c2.sessions.termStatusTimeout'); + return; + } + apiRequest('GET', `${API_BASE}/tasks/${taskId}`).then(data => { + const task = data.task; + if (task && (task.status === 'success' || task.status === 'failed')) { + if (task.resultText) { + const lines = task.resultText.replace(/\r\n/g, '\n').replace(/\r/g, '\n').split('\n'); + lines.forEach(line => term.writeln(line)); + } + if (task.error) { + term.writeln(`\x1b[31m${task.error}\x1b[0m`); + } + term.write('$ '); + const statusEl = document.getElementById('c2-terminal-status'); + if (statusEl) statusEl.textContent = c2t('c2.sessions.termStatusReady'); + } else { + delay = Math.min(delay * 1.5, maxDelay); + setTimeout(check, delay); + } + }); + }; + check(); + }; + + C2.clearTerminal = function() { + if (C2.terminalInstance) { + C2.terminalInstance.clear(); + C2.terminalInstance.writeln('\x1b[36m' + c2t('c2.sessions.termCleared') + '\x1b[0m'); + C2.terminalInstance.write('$ '); + } + }; + + C2.copyTerminal = function() { + if (!C2.terminalInstance) return; + const text = C2.terminalInstance.getSelection(); + if (text) copyToClipboard(text); + else showToast(c2t('c2.sessions.termNoSelection'), 'warning'); + }; + + // ============================================================================ + // 文件管理 + // ============================================================================ + + C2.loadFileList = function(sessionId, path) { + if (!sessionId) sessionId = C2.selectedSessionId; + if (!sessionId) return; + if (!path) path = C2.currentPath || '.'; + + const container = document.getElementById('c2-file-list'); + const breadcrumb = document.getElementById('c2-current-path'); + + if (container) container.innerHTML = '
' + escapeHtml(c2t('c2.files.loading')) + '
'; + + apiRequest('POST', `${API_BASE}/tasks`, { + session_id: sessionId, + task_type: 'ls', + payload: { path: path } + }).then(data => { + if (data.error) { + if (container) container.innerHTML = `
${data.error}
`; + return; + } + C2.waitForFileList(data.task?.id || data.task_id, sessionId, path); + }); + }; + + C2.waitForFileList = function(taskId, sessionId, path) { + let attempts = 0; + const container = document.getElementById('c2-file-list'); + const check = () => { + if (++attempts > 60) { + if (container) container.innerHTML = '
' + escapeHtml(c2t('c2.files.timeout')) + '
'; + return; + } + apiRequest('GET', `${API_BASE}/tasks/${taskId}`).then(data => { + const task = data.task; + if (task && task.status === 'success') { + C2.currentPath = path; + const breadcrumb = document.getElementById('c2-current-path'); + if (breadcrumb) breadcrumb.textContent = path; + C2.renderFileList(task.resultText || ''); + } else if (task && task.status === 'failed') { + if (container) container.innerHTML = `
${escapeHtml(task.error || c2t('c2.files.failed'))}
`; + } else { + setTimeout(check, 500); + } + }); + }; + check(); + }; + + C2.renderFileList = function(output) { + const container = document.getElementById('c2-file-list'); + if (!container) return; + + const lines = output.split('\n').filter(l => l.trim()); + if (lines.length === 0) { + container.innerHTML = '
' + escapeHtml(c2t('c2.files.emptyDir')) + '
'; + return; + } + + container.innerHTML = ` + + + + + + + + + + + ${lines.map(line => { + const parts = line.split(/\s+/); + const name = parts[parts.length - 1] || line; + const isDir = line.startsWith('d') || parts[0]?.startsWith?.('d'); + return ` + + + + + + + `; + }).join('')} + +
${escapeHtml(c2t('c2.files.colName'))}${escapeHtml(c2t('c2.files.colSize'))}${escapeHtml(c2t('c2.files.colMode'))}${escapeHtml(c2t('c2.files.colActions'))}
+ ${isDir ? '📁' : '📄'} + ${escapeHtml(name)} + ${parts[parts.length - 5] || '-'}${parts[parts.length - 4] || '-'} + ${isDir + ? `` + : `` + } +
+ `; + }; + + C2.refreshFiles = function() { + C2.loadFileList(null, C2.currentPath); + }; + + C2.downloadFile = function(filename) { + if (!C2.selectedSessionId) return; + const remotePath = C2.currentPath === '/' ? '/' + filename : C2.currentPath + '/' + filename; + + apiRequest('POST', `${API_BASE}/tasks`, { + session_id: C2.selectedSessionId, + task_type: 'download', + payload: { remote_path: remotePath } + }).then(data => { + if (data.error) showToast(data.error, 'error'); + else showToast(c2t('c2.payloads.toastDownloadQueued'), 'success'); + }); + }; + + // ============================================================================ + // 任务管理 + // ============================================================================ + + C2.loadTasks = function(page) { + const p = page != null ? page : (C2.tasksPage || 1); + C2.tasksPage = p; + const ps = C2.tasksPageSize || 10; + apiRequest('GET', `${API_BASE}/tasks?page=${encodeURIComponent(String(p))}&page_size=${encodeURIComponent(String(ps))}`).then(data => { + if (data.error) { + showToast(String(data.error), 'error'); + return; + } + C2.tasks = data.tasks || []; + C2.tasksTotal = typeof data.total === 'number' ? data.total : (C2.tasks.length || 0); + if (typeof data.pending_queued_count === 'number') { + C2.tasksPendingQueuedCount = data.pending_queued_count; + } + const maxPage = Math.max(1, Math.ceil(C2.tasksTotal / ps)); + if (p > maxPage) { + C2.loadTasks(maxPage); + return; + } + C2.renderTasks(); + C2.renderTasksPagination(); + C2.syncTasksToolbar(); + C2.updateDashboardStats(); + }).catch(err => { + showToast(err.message || String(err), 'error'); + }); + }; + + C2.goTasksPage = function(targetPage) { + const totalPages = Math.max(1, Math.ceil((C2.tasksTotal || 0) / (C2.tasksPageSize || 10))); + if (targetPage < 1 || targetPage > totalPages) return; + C2.loadTasks(targetPage); + const list = document.getElementById('c2-task-list'); + if (list) list.scrollIntoView({ behavior: 'smooth', block: 'start' }); + }; + + C2.changeTasksPageSize = function() { + const sel = document.getElementById('c2-tasks-page-size-pagination'); + if (!sel) return; + const n = parseInt(sel.value, 10); + if (n > 0) { + C2.tasksPageSize = n; + C2.loadTasks(1); + } + }; + + C2.renderTasksPagination = function() { + const paginationContainer = document.getElementById('c2-tasks-pagination'); + if (!paginationContainer) return; + const total = C2.tasksTotal || 0; + const currentPage = C2.tasksPage || 1; + const pageSize = C2.tasksPageSize || 10; + const totalPages = Math.max(1, Math.ceil(total / pageSize)); + if (total === 0) { + paginationContainer.innerHTML = ''; + return; + } + const start = total === 0 ? 0 : (currentPage - 1) * pageSize + 1; + const end = Math.min(currentPage * pageSize, total); + let html = '
'; + html += ` +
+ ${escapeHtml(c2t('c2.tasks.paginationShow', { start, end, total }))} + +
+
+ + + ${escapeHtml(c2t('c2.tasks.paginationPage', { current: currentPage, total: totalPages }))} + + +
+ `; + html += '
'; + paginationContainer.innerHTML = html; + if (typeof applyTranslations === 'function') applyTranslations(paginationContainer); + }; + + C2.collectCheckedTaskIds = function() { + return Array.from(document.querySelectorAll('.c2-task-row-check:checked')).map(cb => cb.getAttribute('data-id')).filter(Boolean); + }; + + C2.syncTasksToolbar = function() { + const batchBtn = document.getElementById('c2-tasks-batch-delete'); + const ids = C2.collectCheckedTaskIds(); + if (batchBtn) batchBtn.disabled = ids.length === 0; + const all = document.querySelectorAll('.c2-task-row-check'); + const selAll = document.getElementById('c2-tasks-select-all'); + if (selAll && all.length) { + const nChecked = document.querySelectorAll('.c2-task-row-check:checked').length; + selAll.checked = nChecked === all.length; + selAll.indeterminate = nChecked > 0 && nChecked < all.length; + } else if (selAll) { + selAll.checked = false; + selAll.indeterminate = false; + } + }; + + C2.onTasksSelectAll = function(checked) { + document.querySelectorAll('.c2-task-row-check').forEach(cb => { cb.checked = checked; }); + C2.syncTasksToolbar(); + }; + + C2.deleteTaskById = function(id) { + if (!id) return; + if (!confirm(c2t('c2.tasks.confirmDeleteOne'))) return; + apiRequest('DELETE', `${API_BASE}/tasks`, { ids: [id] }).then(data => { + if (data.error) { + showToast(String(data.error), 'error'); + return; + } + showToast(c2t('c2.tasks.toastDeleted', { n: data.deleted != null ? data.deleted : 1 }), 'success'); + C2.loadTasks(C2.tasksPage || 1); + }).catch(err => showToast(err.message || String(err), 'error')); + }; + + C2.deleteSelectedTasks = function() { + const ids = C2.collectCheckedTaskIds(); + if (!ids.length) { + showToast(c2t('c2.tasks.toastSelectFirst'), 'warn'); + return; + } + if (!confirm(c2t('c2.tasks.confirmBatchDelete', { n: ids.length }))) return; + apiRequest('DELETE', `${API_BASE}/tasks`, { ids }).then(data => { + if (data.error) { + showToast(String(data.error), 'error'); + return; + } + const deleted = data.deleted != null ? data.deleted : ids.length; + showToast(c2t('c2.tasks.toastDeleted', { n: deleted }), 'success'); + C2.loadTasks(C2.tasksPage || 1); + }).catch(err => showToast(err.message || String(err), 'error')); + }; + + C2.loadSessionTasks = function(sessionId) { + apiRequest('GET', `${API_BASE}/tasks?session_id=${encodeURIComponent(sessionId)}&limit=50`).then(data => { + const container = document.getElementById('c2-session-tasks-list'); + const tasks = data.tasks || []; + if (typeof data.pending_queued_count === 'number') { + C2.tasksPendingQueuedCount = data.pending_queued_count; + C2.updateDashboardStats(); + } + + if (!container) return; + if (tasks.length === 0) { + container.innerHTML = '
' + escapeHtml(c2t('c2.tasks.emptySession')) + '
'; + return; + } + + container.innerHTML = tasks.map(t => ` +
+ + ${t.taskType} + ${escapeHtml(taskStatusLabel(t.status))} | ${formatDuration(t.durationMs)} + +
+ `).join(''); + }); + }; + + C2.renderTasks = function() { + const container = document.getElementById('c2-task-list'); + if (!container) return; + + const selAll = document.getElementById('c2-tasks-select-all'); + if (selAll) { + selAll.checked = false; + selAll.indeterminate = false; + } + + if (C2.tasks.length === 0) { + container.innerHTML = '
' + escapeHtml(c2t('c2.tasks.emptyAll')) + '
'; + if (selAll) selAll.disabled = true; + C2.syncTasksToolbar(); + return; + } + if (selAll) selAll.disabled = false; + + const delTitle = escapeHtml(c2t('c2.tasks.deleteOne')); + container.innerHTML = ` + + + + + + + + + + + + + + + ${C2.tasks.map(t => { + const rawId = t.id || ''; + const idJson = JSON.stringify(rawId); + const shortTaskId = rawId.length > 14 ? escapeHtml(rawId.substring(0, 12)) + '\u2026' : escapeHtml(rawId); + const sid = t.sessionId ? escapeHtml(String(t.sessionId).substring(0, 8)) + '\u2026' : '-'; + return ` + + + + + + + + + + + `; + }).join('')} + +
${escapeHtml(c2t('c2.tasks.colTask'))}${escapeHtml(c2t('c2.tasks.colSession'))}${escapeHtml(c2t('c2.tasks.colType'))}${escapeHtml(c2t('c2.tasks.colStatus'))}${escapeHtml(c2t('c2.tasks.colDuration'))}${escapeHtml(c2t('c2.tasks.colCreated'))}${escapeHtml(c2t('c2.tasks.colActions'))}
+ + ${shortTaskId}${sid}${escapeHtml(t.taskType || '')}${escapeHtml(taskStatusLabel(t.status))}${formatDuration(t.durationMs)}${formatTime(t.createdAt)} + + ${t.status === 'queued' || t.status === 'sent' + ? `` + : ''} + +
+ `; + C2.syncTasksToolbar(); + if (typeof applyTranslations === 'function') applyTranslations(container); + }; + + C2.viewTask = function(id) { + const modal = document.getElementById('c2-modal'); + const content = document.getElementById('c2-modal-content'); + if (!content) return; + + const renderTaskModal = function(t) { + if (!t || !modal) return; + content.innerHTML = ` +
+

${escapeHtml(c2t('c2.tasks.modalTitle'))}

+ +
+
+
+
${escapeHtml(c2t('c2.tasks.labelId'))}: ${t.id}
+
${escapeHtml(c2t('c2.tasks.labelSession'))}: ${t.sessionId}
+
${escapeHtml(c2t('c2.tasks.labelType'))}: ${t.taskType}
+
${escapeHtml(c2t('c2.tasks.labelStatus'))}: ${escapeHtml(taskStatusLabel(t.status))}
+
${escapeHtml(c2t('c2.tasks.labelCreated'))}: ${formatTime(t.createdAt)}
+
${escapeHtml(c2t('c2.tasks.labelSent'))}: ${formatTime(t.sentAt)}
+
${escapeHtml(c2t('c2.tasks.labelCompleted'))}: ${formatTime(t.completedAt)}
+
${escapeHtml(c2t('c2.tasks.labelDuration'))}: ${formatDuration(t.durationMs)}
+ ${t.error ? `
${escapeHtml(c2t('c2.tasks.labelError'))}: ${escapeHtml(t.error)}
` : ''} + ${t.resultText ? ` +
+ ${escapeHtml(c2t('c2.tasks.labelResult'))}: +
${escapeHtml(t.resultText)}
+
+ ` : ''} +
+
+ + `; + modal.style.display = 'flex'; + }; + + const local = C2.tasks.find(x => x.id === id); + if (local) { + renderTaskModal(local); + return; + } + apiRequest('GET', `${API_BASE}/tasks/${id}`).then(data => { + if (data.task) renderTaskModal(data.task); + }); + }; + + C2.cancelTask = function(id) { + apiRequest('POST', `${API_BASE}/tasks/${id}/cancel`, {}).then(data => { + if (data.error) showToast(data.error, 'error'); + else { + showToast(c2t('c2.tasks.toastCancelled'), 'success'); + C2.loadTasks(C2.tasksPage || 1); + } + }); + }; + + // ============================================================================ + // Payload 生成 + // ============================================================================ + + C2.loadListenersForPayload = function() { + apiRequest('GET', `${API_BASE}/listeners`).then(data => { + if (data.error) { + showToast(data.error, 'error'); + return; + } + C2.listeners = data.listeners || []; + C2.renderPayloadPage(); + }).catch(err => { + showToast(c2t('c2.payloads.toastLoadListenersFail', { msg: err.message || '' }), 'error'); + }); + }; + + var onelinerKindsByListenerType = { + 'tcp_reverse': [ + { value: 'bash', label: 'Bash (/dev/tcp)' }, + { value: 'nc', label: 'Netcat (-e)' }, + { value: 'nc_mkfifo', label: 'Netcat (mkfifo)' }, + { value: 'python', label: 'Python' }, + { value: 'perl', label: 'Perl' }, + { value: 'powershell', label: 'PowerShell' } + ], + 'http_beacon': [ + { value: 'curl_beacon', label: 'Curl Beacon (HTTP)' } + ], + 'https_beacon': [ + { value: 'curl_beacon', label: 'Curl Beacon (HTTP)' } + ], + 'websocket': [ + { value: 'curl_beacon', label: 'Curl Beacon (HTTP)' } + ] + }; + + C2.updateOnelinerKinds = function() { + var listenerSelect = document.getElementById('c2-payload-listener'); + var kindSelect = document.getElementById('c2-payload-kind'); + if (!listenerSelect || !kindSelect) return; + + var listenerId = listenerSelect.value; + var listener = (C2.listeners || []).find(function(l) { return l.id === listenerId; }); + var ltype = listener ? listener.type : ''; + var kinds = onelinerKindsByListenerType[ltype] || []; + + if (kinds.length === 0) { + kindSelect.innerHTML = ''; + } else { + kindSelect.innerHTML = kinds.map(function(k) { + return ''; + }).join(''); + } + }; + + C2.updateLoopbackBuildHint = function() { + const sel = document.getElementById('c2-build-listener'); + const hint = document.getElementById('c2-build-loopback-hint'); + if (!hint) return; + const override = document.getElementById('c2-build-host') && String(document.getElementById('c2-build-host').value || '').trim(); + if (override) { + hint.style.display = 'none'; + return; + } + const id = sel && sel.value; + if (!id) { + hint.style.display = 'none'; + return; + } + const l = (C2.listeners || []).find(function(x) { return x.id === id; }); + const h = (l && l.bindHost ? String(l.bindHost) : '').toLowerCase().trim(); + if (h === '127.0.0.1' || h === 'localhost' || h === '::1') { + hint.textContent = c2t('c2.payloads.loopbackBeaconWarning'); + hint.style.display = 'block'; + } else { + hint.style.display = 'none'; + } + }; + + C2.renderPayloadPage = function() { + const optionsHtml = C2.listeners.length > 0 + ? C2.listeners.map(l => + `` + ).join('') + : ''; + + const listenerSelect = document.getElementById('c2-payload-listener'); + if (listenerSelect) { + listenerSelect.innerHTML = optionsHtml; + listenerSelect.removeEventListener('change', C2.updateOnelinerKinds); + listenerSelect.addEventListener('change', C2.updateOnelinerKinds); + } + + const buildSelect = document.getElementById('c2-build-listener'); + if (buildSelect) { + const listeners = C2.listeners || []; + let buildOptionsHtml; + if (listeners.length > 0) { + buildOptionsHtml = listeners.map(l => + `` + ).join(''); + } else { + buildOptionsHtml = ''; + } + buildSelect.innerHTML = buildOptionsHtml; + buildSelect.removeEventListener('change', C2.updateLoopbackBuildHint); + buildSelect.addEventListener('change', C2.updateLoopbackBuildHint); + C2.updateLoopbackBuildHint(); + } + + const buildHostInput = document.getElementById('c2-build-host'); + if (buildHostInput) { + buildHostInput.removeEventListener('input', C2.updateLoopbackBuildHint); + buildHostInput.addEventListener('input', C2.updateLoopbackBuildHint); + } + + C2.updateOnelinerKinds(); + const buildBtn = document.getElementById('c2-build-btn'); + if (buildBtn && !buildBtn.disabled) buildBtn.textContent = c2t('c2.payloads.buildBeaconBtn'); + const genBtn = document.getElementById('c2-generate-oneliner-btn'); + if (genBtn) genBtn.textContent = c2t('c2.payloads.generateOnelinerBtn'); + }; + + C2.generateOneliner = function() { + const listenerId = document.getElementById('c2-payload-listener')?.value; + const kind = document.getElementById('c2-payload-kind')?.value || 'bash'; + const host = document.getElementById('c2-payload-host')?.value; + + if (!listenerId) { + showToast(c2t('c2.payloads.toastPickListener'), 'error'); + return; + } + + apiRequest('POST', `${API_BASE}/payloads/oneliner`, { + listener_id: listenerId, + kind: kind, + host: host + }).then(data => { + if (data.error) { + showToast(data.error, 'error'); + } else { + const output = document.getElementById('c2-oneliner-output'); + if (output) { + output.textContent = data.oneliner; + output.style.display = 'block'; + } + } + }).catch(err => { + showToast(c2t('c2.payloads.toastOnelinerFail', { msg: err.message || '' }), 'error'); + }); + }; + + C2.copyOneliner = function() { + const el = document.getElementById('c2-oneliner-output'); + if (el && el.textContent) copyToClipboard(el.textContent); + }; + + C2.buildBeacon = function() { + const listenerId = document.getElementById('c2-build-listener')?.value; + const os = document.getElementById('c2-build-os')?.value || 'linux'; + const arch = document.getElementById('c2-build-arch')?.value || 'amd64'; + const host = document.getElementById('c2-build-host')?.value; + + if (!listenerId) { + showToast(c2t('c2.payloads.toastPickListener'), 'error'); + return; + } + + const btn = document.getElementById('c2-build-btn'); + if (btn) { + btn.disabled = true; + btn.textContent = c2t('c2.payloads.building'); + } + + apiRequest('POST', `${API_BASE}/payloads/build`, { + listener_id: listenerId, + os: os, + arch: arch, + host: host + }).then(data => { + if (btn) { + btn.disabled = false; + btn.textContent = c2t('c2.payloads.buildBeaconBtn'); + } + if (data.error) { + showToast(data.error, 'error'); + } else { + showToast(c2t('c2.payloads.toastBuildSuccess', { bytes: data.payload?.size_bytes }), 'success'); + const result = document.getElementById('c2-build-result'); + if (result) { + result.innerHTML = ` +
+
✓ ${escapeHtml(c2t('c2.payloads.buildSuccessTitle'))}
+
${escapeHtml(c2t('c2.payloads.buildMetaOsArch', { os: data.payload?.os, arch: data.payload?.arch }))}
+
${escapeHtml(c2t('c2.payloads.buildSize', { bytes: data.payload?.size_bytes }))}
+ +
+ `; + } + } + }).catch(err => { + if (btn) { + btn.disabled = false; + btn.textContent = c2t('c2.payloads.buildBeaconBtn'); + } + showToast(c2t('c2.payloads.toastBuildFail', { msg: err.message || '' }), 'error'); + }); + }; + + // ============================================================================ + // 事件审计 + // ============================================================================ + + C2.loadEvents = function(page) { + const p = page != null ? page : (C2.eventsPage || 1); + C2.eventsPage = p; + const ps = C2.eventsPageSize || 10; + apiRequest('GET', `${API_BASE}/events?page=${encodeURIComponent(String(p))}&page_size=${encodeURIComponent(String(ps))}`).then(data => { + if (data.error) { + showToast(String(data.error), 'error'); + return; + } + C2.events = data.events || []; + C2.eventsTotal = typeof data.total === 'number' ? data.total : (C2.events.length || 0); + const maxPage = Math.max(1, Math.ceil(C2.eventsTotal / ps)); + if (p > maxPage) { + C2.loadEvents(maxPage); + return; + } + C2.renderEvents(); + C2.renderEventsPagination(); + C2.syncEventsToolbar(); + }).catch(err => { + showToast(err.message || String(err), 'error'); + }); + }; + + C2.goEventsPage = function(targetPage) { + const totalPages = Math.max(1, Math.ceil((C2.eventsTotal || 0) / (C2.eventsPageSize || 10))); + if (targetPage < 1 || targetPage > totalPages) return; + C2.loadEvents(targetPage); + const list = document.getElementById('c2-event-list'); + if (list) list.scrollIntoView({ behavior: 'smooth', block: 'start' }); + }; + + C2.changeEventsPageSize = function() { + const sel = document.getElementById('c2-events-page-size-pagination'); + if (!sel) return; + const n = parseInt(sel.value, 10); + if (n > 0) { + C2.eventsPageSize = n; + C2.loadEvents(1); + } + }; + + C2.renderEventsPagination = function() { + const paginationContainer = document.getElementById('c2-events-pagination'); + if (!paginationContainer) return; + + const total = C2.eventsTotal || 0; + const currentPage = C2.eventsPage || 1; + const pageSize = C2.eventsPageSize || 10; + const totalPages = Math.max(1, Math.ceil(total / pageSize)); + + if (total === 0) { + paginationContainer.innerHTML = ''; + return; + } + + const start = total === 0 ? 0 : (currentPage - 1) * pageSize + 1; + const end = Math.min(currentPage * pageSize, total); + + let html = '
'; + html += ` +
+ ${escapeHtml(c2t('c2.events.paginationShow', { start, end, total }))} + +
+
+ + + ${escapeHtml(c2t('c2.events.paginationPage', { current: currentPage, total: totalPages }))} + + +
+ `; + html += '
'; + paginationContainer.innerHTML = html; + if (typeof applyTranslations === 'function') applyTranslations(paginationContainer); + }; + + C2.collectCheckedEventIds = function() { + return Array.from(document.querySelectorAll('.c2-event-check:checked')).map(cb => cb.getAttribute('data-id')).filter(Boolean); + }; + + C2.syncEventsToolbar = function() { + const batchBtn = document.getElementById('c2-events-batch-delete'); + const ids = C2.collectCheckedEventIds(); + if (batchBtn) batchBtn.disabled = ids.length === 0; + + const all = document.querySelectorAll('.c2-event-check'); + const selAll = document.getElementById('c2-events-select-all'); + if (selAll && all.length) { + const nChecked = document.querySelectorAll('.c2-event-check:checked').length; + selAll.checked = nChecked === all.length; + selAll.indeterminate = nChecked > 0 && nChecked < all.length; + } else if (selAll) { + selAll.checked = false; + selAll.indeterminate = false; + } + }; + + C2.onEventsSelectAll = function(checked) { + document.querySelectorAll('.c2-event-check').forEach(cb => { cb.checked = checked; }); + C2.syncEventsToolbar(); + }; + + C2.deleteEventById = function(id) { + if (!id) return; + if (!confirm(c2t('c2.events.confirmDeleteOne'))) return; + apiRequest('DELETE', `${API_BASE}/events`, { ids: [id] }).then(data => { + if (data.error) { + showToast(String(data.error), 'error'); + return; + } + showToast(c2t('c2.events.toastDeleted', { n: data.deleted != null ? data.deleted : 1 }), 'success'); + C2.loadEvents(C2.eventsPage || 1); + }).catch(err => showToast(err.message || String(err), 'error')); + }; + + C2.deleteSelectedEvents = function() { + const ids = C2.collectCheckedEventIds(); + if (!ids.length) { + showToast(c2t('c2.events.toastSelectFirst'), 'warn'); + return; + } + if (!confirm(c2t('c2.events.confirmBatchDelete', { n: ids.length }))) return; + apiRequest('DELETE', `${API_BASE}/events`, { ids }).then(data => { + if (data.error) { + showToast(String(data.error), 'error'); + return; + } + const deleted = data.deleted != null ? data.deleted : ids.length; + showToast(c2t('c2.events.toastDeleted', { n: deleted }), 'success'); + C2.loadEvents(C2.eventsPage || 1); + }).catch(err => showToast(err.message || String(err), 'error')); + }; + + C2.renderEvents = function() { + const container = document.getElementById('c2-event-list'); + if (!container) return; + + const selAll = document.getElementById('c2-events-select-all'); + if (selAll) { + selAll.checked = false; + selAll.indeterminate = false; + } + + if (C2.events.length === 0) { + container.innerHTML = '
' + escapeHtml(c2t('c2.events.empty')) + '
'; + if (selAll) selAll.disabled = true; + C2.syncEventsToolbar(); + return; + } + if (selAll) selAll.disabled = false; + + const delTitle = escapeHtml(c2t('c2.events.deleteOne')); + container.innerHTML = C2.events.map(e => { + const eid = escapeHtml(e.id || ''); + return ` +
+ +
+
+
${escapeHtml(e.message)}
+
+ ${formatTime(e.createdAt)} · ${escapeHtml(e.category || '')}${e.sessionId ? ' · ' + escapeHtml(String(e.sessionId).substring(0, 8)) : ''} +
+
+ +
+ `; + }).join(''); + + C2.syncEventsToolbar(); + if (typeof applyTranslations === 'function') applyTranslations(container); + }; + + C2.connectEventStream = function() { + if (C2.eventSource) C2.eventSource.close(); + + let streamUrl = `${API_BASE}/events/stream`; + if (typeof authToken !== 'undefined' && authToken) { + streamUrl += `?token=${encodeURIComponent(authToken)}`; + } + C2.eventSource = new EventSource(streamUrl); + C2.eventSource.onmessage = (e) => { + try { + const event = JSON.parse(e.data); + C2.onEvent(event); + } catch (err) {} + }; + C2.eventSource.onerror = () => { + setTimeout(() => C2.connectEventStream(), 5000); + }; + }; + + C2.onEvent = function(event) { + if (window.currentPageId === 'c2-events' && (C2.eventsPage || 1) === 1) { + C2.loadEvents(1); + } + + const msg = event.message || ''; + const sessionOnline = event.category === 'session' && ( + msg.includes('上线') || msg.includes('新会话') || /new session/i.test(msg) + ); + if (event.level === 'critical' || sessionOnline) { + showToast(`[${event.category}] ${event.message}`, event.level === 'critical' ? 'error' : 'info'); + } + + C2.updateDashboardStats(); + }; + + // ============================================================================ + // Profile 管理 + // ============================================================================ + + C2.loadProfiles = function() { + apiRequest('GET', `${API_BASE}/profiles`).then(data => { + C2.profiles = data.profiles || []; + C2.renderProfiles(); + }); + }; + + C2.renderProfiles = function() { + const container = document.getElementById('c2-profile-list'); + if (!container) return; + + if (C2.profiles.length === 0) { + container.innerHTML = '
' + escapeHtml(c2t('c2.profiles.empty')) + '
'; + return; + } + + const defVal = c2t('c2.profiles.defaultValue'); + container.innerHTML = C2.profiles.map(p => ` +
+
+

${escapeHtml(p.name)}

+ +
+
+
UA: ${escapeHtml(p.userAgent || defVal)}
+
URIs: ${escapeHtml((p.uris || []).join(', ') || defVal)}
+
Jitter: ${p.jitterMinMs || 0}ms – ${p.jitterMaxMs || 0}ms
+
+
+ `).join(''); + }; + + C2.showCreateProfileModal = function() { + const modal = document.getElementById('c2-modal'); + const content = document.getElementById('c2-modal-content'); + if (!content) return; + + content.innerHTML = ` +
+

${escapeHtml(c2t('c2.profiles.modalCreateTitle'))}

+ +
+
+
+ + +
+
+ + +
${escapeHtml(c2t('c2.profiles.hintUa'))}
+
+
+ + +
${escapeHtml(c2t('c2.profiles.hintUris'))}
+
+
+
+ + +
+
+ + +
+
+
+ + +
${escapeHtml(c2t('c2.profiles.hintHeaders'))}
+
+
+ + `; + modal.style.display = 'flex'; + }; + + C2.createProfile = function() { + const name = document.getElementById('c2-profile-name')?.value.trim(); + if (!name) { + showToast(c2t('c2.profiles.toastNameRequired'), 'error'); + return; + } + + const userAgent = document.getElementById('c2-profile-ua')?.value.trim() || ''; + const urisRaw = document.getElementById('c2-profile-uris')?.value.trim() || ''; + const uris = urisRaw.split('\n').map(u => u.trim()).filter(u => u); + const jitterMinMs = parseInt(document.getElementById('c2-profile-jmin')?.value) || 100; + const jitterMaxMs = parseInt(document.getElementById('c2-profile-jmax')?.value) || 500; + + let responseHeaders = {}; + const headersRaw = document.getElementById('c2-profile-headers')?.value.trim(); + if (headersRaw) { + try { responseHeaders = JSON.parse(headersRaw); } + catch (e) { showToast(c2t('c2.profiles.toastInvalidHeadersJson'), 'error'); return; } + } + + apiRequest('POST', `${API_BASE}/profiles`, { + name, + user_agent: userAgent, + uris, + jitter_min_ms: jitterMinMs, + jitter_max_ms: jitterMaxMs, + response_headers: responseHeaders + }).then(data => { + if (data.error) { + showToast(data.error, 'error'); + } else { + showToast(c2t('c2.profiles.toastCreated'), 'success'); + C2.closeModal(); + C2.loadProfiles(); + } + }); + }; + + C2.deleteProfile = function(id) { + if (!confirm(c2t('c2.profiles.confirmDelete'))) return; + apiRequest('DELETE', `${API_BASE}/profiles/${id}`, {}).then(data => { + showToast(c2t('c2.profiles.toastDeleted'), 'success'); + C2.loadProfiles(); + }); + }; + + // ============================================================================ + // 仪表盘 + // ============================================================================ + + C2.updateDashboardStats = function() { + const runningListeners = C2.listeners.filter(l => l.status === 'running').length; + const activeSessions = C2.sessions.filter(s => s.status === 'active').length; + const pendingTasks = typeof C2.tasksPendingQueuedCount === 'number' + ? C2.tasksPendingQueuedCount + : C2.tasks.filter(t => t.status === 'queued' || t.status === 'pending').length; + + const elListeners = document.getElementById('c2-stat-listeners'); + const elSessions = document.getElementById('c2-stat-sessions'); + const elPending = document.getElementById('c2-stat-pending'); + + if (elListeners) elListeners.textContent = runningListeners; + if (elSessions) elSessions.textContent = activeSessions; + if (elPending) elPending.textContent = pendingTasks; + }; + + // ============================================================================ + // 模态框 + // ============================================================================ + + C2.closeModal = function() { + const modal = document.getElementById('c2-modal'); + if (modal) modal.style.display = 'none'; + }; + + // ============================================================================ + // 暴露到全局 + // ============================================================================ + + window.C2 = C2; + + // 页面切换监听 + window.addEventListener('pageChanged', function(e) { + if (e.detail?.pageId?.startsWith('c2')) { + C2.init(); + } + }); + + // DOM 加载完成后初始化 + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', () => { + if (window.currentPageId?.startsWith('c2')) C2.init(); + }); + } else { + if (window.currentPageId?.startsWith('c2')) C2.init(); + } + + document.addEventListener('languagechange', function () { + try { + if (!window.currentPageId || !String(window.currentPageId).startsWith('c2')) return; + if (typeof applyTranslations === 'function') applyTranslations(document); + C2.init(); + if (C2.selectedSessionId && (window.currentPageId === 'c2-sessions')) { + C2.renderSessions(); + C2.renderSessionDetail(C2.selectedSessionId); + } + } catch (e) { + console.warn('languagechange C2 refresh failed', e); + } + }); + +})(); diff --git a/web/static/js/dashboard.js b/web/static/js/dashboard.js index 93d5c660..c99a5f10 100644 --- a/web/static/js/dashboard.js +++ b/web/static/js/dashboard.js @@ -103,7 +103,8 @@ async function refreshDashboard() { recentVulnsRes, rolesRes, agentsRes, openCriticalRes, openHighRes, openMediumRes, openLowRes, toolsConfigRes, hitlPendingRes, notificationsRes, externalMcpStatsRes, - webshellRes + webshellRes, + c2ListenersRes, c2SessionsRes, c2TasksRes ] = await Promise.all([ fetchJson('/api/agent-loop/tasks'), fetchJson('/api/vulnerabilities/stats'), @@ -129,7 +130,11 @@ async function refreshDashboard() { // External MCP 健康度 fetchJson('/api/external-mcp/stats'), // WebShell 已建立的连接(pentest 落地后的 foothold,对运营场景非常关键) - fetchJson('/api/webshell/connections') + fetchJson('/api/webshell/connections'), + // C2 仪表盘条:监听器 / 会话 / 待处理任务(任务接口含 pending_queued_count) + fetchJson('/api/c2/listeners'), + fetchJson('/api/c2/sessions?limit=500'), + fetchJson('/api/c2/tasks?page=1&page_size=1') ]); // 如果在 await 期间 controller 已被 abort,说明又有新刷新启动了,丢弃本次结果 @@ -393,6 +398,9 @@ async function refreshDashboard() { // 「最近事件」内联展示(来自通知摘要,过滤掉已经被仪表盘其他位置覆盖的类型) renderRecentEvents(notificationsRes); + // C2 概览条(监听器 / 在线会话 / 待处理任务) + renderDashboardC2Overview(c2ListenersRes, c2SessionsRes, c2TasksRes); + // 关键提醒条:把所有可能的告警源(漏洞/HITL/失败率/MCP健康)合并展示 renderDashboardAlertBanner({ criticalCount: openCriticalCount, @@ -444,6 +452,8 @@ async function refreshDashboard() { ['tools', 'skills', 'knowledge', 'roles', 'agents', 'webshell'].forEach(function (k) { setEl('dashboard-resource-' + k, '-'); }); + var c2secErr = document.getElementById('dashboard-section-c2'); + if (c2secErr) c2secErr.hidden = true; setRecentVulnsError(); renderDashboardToolsBar(null); var ph = document.getElementById('dashboard-tools-pie-placeholder'); @@ -458,6 +468,53 @@ async function refreshDashboard() { } } +/** C2 概览条:依赖 /api/c2/listeners、sessions、tasks;任一路由失败则整块隐藏 */ +function renderDashboardC2Overview(listenersRes, sessionsRes, tasksRes) { + var section = document.getElementById('dashboard-section-c2'); + if (!section) return; + if (listenersRes === null && sessionsRes === null && tasksRes === null) { + section.hidden = true; + return; + } + var running = '-'; + if (listenersRes && Array.isArray(listenersRes.listeners)) { + running = String(listenersRes.listeners.filter(function (l) { + return (l && (l.status || '').toLowerCase() === 'running'); + }).length); + } else if (listenersRes === null) { + running = '-'; + } else { + running = '0'; + } + var online = '-'; + if (sessionsRes && Array.isArray(sessionsRes.sessions)) { + online = String(sessionsRes.sessions.filter(function (s) { + if (!s) return false; + var st = (s.status || '').toLowerCase(); + return st === 'active' || st === 'sleeping'; + }).length); + } else if (sessionsRes === null) { + online = '-'; + } else { + online = '0'; + } + var pending = '-'; + if (tasksRes && typeof tasksRes.pending_queued_count === 'number') { + pending = String(tasksRes.pending_queued_count); + } else if (tasksRes === null) { + pending = '-'; + } else { + pending = '0'; + } + setEl('dashboard-c2-listeners-running', running); + setEl('dashboard-c2-sessions-online', online); + setEl('dashboard-c2-tasks-pending', pending); + section.hidden = false; + if (typeof applyTranslations === 'function') { + try { applyTranslations(section); } catch (_e) { /* ignore */ } + } +} + function setEl(id, text) { const el = document.getElementById(id); if (el) el.textContent = text; diff --git a/web/static/js/monitor.js b/web/static/js/monitor.js index 851518ca..e955fdad 100644 --- a/web/static/js/monitor.js +++ b/web/static/js/monitor.js @@ -142,6 +142,11 @@ function einoMainStreamPlanningTitle(responseData) { const label = typeof window.t === 'function' ? window.t(key) : '输出'; return prefix + '📝 ' + label; } + // eino_single / deep / supervisor:主通道是模型流式输出,不是「规划」;模型偶发复述工具 stdout 时,旧文案易被误认为工具结果标题。 + if (orch != null && String(orch).trim() !== '' && orch !== 'plan_execute') { + const streamLabel = typeof window.t === 'function' ? window.t('chat.assistantStreamPhase') : '助手输出'; + return prefix + '📝 ' + streamLabel; + } const plan = typeof window.t === 'function' ? window.t('chat.planning') : '规划中'; return prefix + '📝 ' + plan; } @@ -1498,7 +1503,7 @@ function handleStreamEvent(event, progressElement, progressId, const itemId = addTimelineItem(timeline, 'thinking', { title: title, message: ' ', - data: responseData + data: Object.assign({}, responseData, { responseStreamPlaceholder: true }) }); responseStreamStateByProgressId.set(progressId, { itemId: itemId, buffer: '', streamMeta: responseData }); break; @@ -2198,6 +2203,9 @@ function addTimelineItem(timeline, type, options) { if (options.data && options.data.orchestration != null && String(options.data.orchestration).trim() !== '') { item.dataset.orchestration = String(options.data.orchestration).trim(); } + if (options.data && options.data.responseStreamPlaceholder === true) { + item.dataset.responseStreamPlaceholder = '1'; + } // 使用传入的createdAt时间,如果没有则使用当前时间(向后兼容) let eventTime; @@ -3154,7 +3162,12 @@ function refreshProgressAndTimelineI18n() { titleSpan.textContent = ap + _t('chat.iterationRound', { n: n }); } } else if (type === 'thinking') { - if (item.dataset.orchestration === 'plan_execute' && item.dataset.einoAgent && typeof einoMainStreamPlanningTitle === 'function') { + if (item.dataset.responseStreamPlaceholder === '1' && typeof einoMainStreamPlanningTitle === 'function') { + titleSpan.textContent = einoMainStreamPlanningTitle({ + orchestration: item.dataset.orchestration || '', + einoAgent: item.dataset.einoAgent || '' + }); + } else if (item.dataset.orchestration === 'plan_execute' && item.dataset.einoAgent && typeof einoMainStreamPlanningTitle === 'function') { titleSpan.textContent = einoMainStreamPlanningTitle({ orchestration: 'plan_execute', einoAgent: item.dataset.einoAgent @@ -3163,10 +3176,10 @@ function refreshProgressAndTimelineI18n() { titleSpan.textContent = ap + '\uD83E\uDD14 ' + _t('chat.aiThinking'); } } else if (type === 'planning') { - if (item.dataset.orchestration === 'plan_execute' && item.dataset.einoAgent && typeof einoMainStreamPlanningTitle === 'function') { + if (item.dataset.orchestration && typeof einoMainStreamPlanningTitle === 'function') { titleSpan.textContent = einoMainStreamPlanningTitle({ - orchestration: 'plan_execute', - einoAgent: item.dataset.einoAgent + orchestration: item.dataset.orchestration, + einoAgent: item.dataset.einoAgent || '' }); } else { titleSpan.textContent = ap + '\uD83D\uDCDD ' + _t('chat.planning'); diff --git a/web/static/js/notifications.js b/web/static/js/notifications.js index 5c49b302..110b6a86 100644 --- a/web/static/js/notifications.js +++ b/web/static/js/notifications.js @@ -129,6 +129,7 @@ if ((item.type === 'task_completed' || item.type === 'long_running_tasks') && item.conversationId) return true; if (item.type === 'task_failed' && item.executionId) return true; if (item.type === 'hitl_pending') return true; + if (item.type === 'c2_session_online' && item.sessionId) return true; return false; } @@ -153,6 +154,24 @@ } if (item.type === 'hitl_pending') { window.location.hash = 'hitl'; + return; + } + if (item.type === 'c2_session_online' && item.sessionId) { + if (typeof window.switchPage === 'function') { + window.switchPage('c2-sessions'); + } else { + window.location.hash = 'c2-sessions'; + } + const sid = item.sessionId; + window.setTimeout(function () { + if (typeof C2 === 'undefined' || !C2.loadSessions || !C2.selectSession) return; + var p = C2.loadSessions(); + if (p && typeof p.then === 'function') { + p.then(function () { C2.selectSession(sid); }).catch(function () {}); + } else { + window.setTimeout(function () { try { C2.selectSession(sid); } catch (e) {} }, 500); + } + }, 120); } } diff --git a/web/static/js/router.js b/web/static/js/router.js index e05f0cde..a53ee2a8 100644 --- a/web/static/js/router.js +++ b/web/static/js/router.js @@ -50,7 +50,7 @@ function initRouter() { if (hash) { const hashParts = hash.split('?'); const pageId = hashParts[0]; - if (pageId && ['dashboard', 'chat', 'hitl', 'info-collect', 'vulnerabilities', 'webshell', 'chat-files', 'mcp-monitor', 'mcp-management', 'knowledge-management', 'knowledge-retrieval-logs', 'roles-management', 'skills-monitor', 'skills-management', 'agents-management', 'settings', 'tasks'].includes(pageId)) { + if (pageId && ['dashboard', 'chat', 'hitl', 'info-collect', 'vulnerabilities', 'webshell', 'chat-files', 'mcp-monitor', 'mcp-management', 'knowledge-management', 'knowledge-retrieval-logs', 'roles-management', 'skills-monitor', 'skills-management', 'agents-management', 'settings', 'tasks', 'c2', 'c2-listeners', 'c2-sessions', 'c2-tasks', 'c2-payloads', 'c2-events', 'c2-profiles'].includes(pageId)) { switchPage(pageId); if (pageId === 'chat') { scheduleChatConversationFromHash(500); @@ -151,6 +151,17 @@ function updateNavState(pageId) { if (submenuItem) { submenuItem.classList.add('active'); } + } else if (pageId.startsWith('c2') || pageId === 'c2-listeners' || pageId === 'c2-sessions' || pageId === 'c2-tasks' || pageId === 'c2-payloads' || pageId === 'c2-events' || pageId === 'c2-profiles') { + // C2 子菜单项 + const c2Item = document.querySelector('.nav-item[data-page="c2"]'); + if (c2Item) { + c2Item.classList.add('active'); + c2Item.classList.add('expanded'); + } + const submenuItem = document.querySelector(`.nav-submenu-item[data-page="${pageId}"]`); + if (submenuItem) { + submenuItem.classList.add('active'); + } } else if (pageId === 'roles-management') { // 角色子菜单项 const rolesItem = document.querySelector('.nav-item[data-page="roles"]'); @@ -405,6 +416,18 @@ async function initPage(pageId) { loadMarkdownAgents(); } break; + case 'c2': + case 'c2-listeners': + case 'c2-sessions': + case 'c2-tasks': + case 'c2-payloads': + case 'c2-events': + case 'c2-profiles': + window.currentPageId = pageId; + if (window.C2 && typeof window.C2.init === 'function') { + window.C2.init(); + } + break; } // 清理其他页面的定时器 @@ -425,7 +448,7 @@ document.addEventListener('DOMContentLoaded', function() { const hashParts = hash.split('?'); const pageId = hashParts[0]; - if (pageId && ['chat', 'hitl', 'info-collect', 'tasks', 'vulnerabilities', 'webshell', 'chat-files', 'mcp-monitor', 'mcp-management', 'knowledge-management', 'knowledge-retrieval-logs', 'roles-management', 'skills-monitor', 'skills-management', 'agents-management', 'settings'].includes(pageId)) { + if (pageId && ['dashboard', 'chat', 'hitl', 'info-collect', 'tasks', 'vulnerabilities', 'webshell', 'chat-files', 'mcp-monitor', 'mcp-management', 'knowledge-management', 'knowledge-retrieval-logs', 'roles-management', 'skills-monitor', 'skills-management', 'agents-management', 'settings', 'c2', 'c2-listeners', 'c2-sessions', 'c2-tasks', 'c2-payloads', 'c2-events', 'c2-profiles'].includes(pageId)) { switchPage(pageId); if (pageId === 'chat') { scheduleChatConversationFromHash(200); diff --git a/web/templates/index.html b/web/templates/index.html index 1a8133fe..d40c6095 100644 --- a/web/templates/index.html +++ b/web/templates/index.html @@ -7,6 +7,7 @@ + @@ -178,6 +179,28 @@ WebShell管理 + + + +
+ +
+
+
+
+
+ + + + + + +
+

AI-Native C2 框架

+

以 MCP 工具为一等公民,让 LLM 可以像调用 nmap 一样调用 C2 完成"上线 → 控制 → 任务 → 横向 → 清场"全流程

+
+
+ - + 运行中监听器 +
+
+ - + 在线会话 +
+
+ - + 待审任务 +
+
+
+ + +
+
+
+
+
+
+ + +
+ +
+
+
+
+ + +
+ +
+
+
+
+
+
+
+ + +
+ +
+
+ +
+
+
+
+
+ + +
+ +
+
+
+

+ + + + 单行 Payload +

+

快速生成可在目标机直接执行的反弹命令,支持 Bash / Python / PowerShell / Curl

+
+ + +
+
+ + +
+
+ + +
+ + +
+
+

+ + + + 编译 Beacon 二进制 +

+

交叉编译支持多平台的完整 Beacon 可执行文件,支持 Linux / Windows / macOS

+
+ + + +
+
+ + +
+
+
+ + +
+
+ + +
+
+ +
+
+
+
+
+ + +
+ +
+
+ +
+
+
+
+
+ + +
+ +
+
+
+
+ + + +