mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-07-12 15:16:43 +02:00
Add files via upload
This commit is contained in:
+1123
-651
File diff suppressed because it is too large
Load Diff
+149
-28
@@ -39,6 +39,100 @@ let webshellStreamingTypingId = 0;
|
||||
let webshellProbeStatusById = {};
|
||||
let webshellBatchProbeRunning = false;
|
||||
|
||||
/** 允许的响应编码,与后端 normalizeWebshellEncoding 对齐 */
|
||||
const WEBSHELL_ALLOWED_ENCODINGS = ['auto', 'utf-8', 'gbk', 'gb18030'];
|
||||
|
||||
/** 归一化连接的 encoding 字段,返回 'auto' | 'utf-8' | 'gbk' | 'gb18030'(空/未知 → auto) */
|
||||
function normalizeWebshellEncoding(v) {
|
||||
var s = (v == null ? '' : String(v)).trim().toLowerCase();
|
||||
if (s === 'utf8') s = 'utf-8';
|
||||
if (!s) return 'auto';
|
||||
return WEBSHELL_ALLOWED_ENCODINGS.indexOf(s) >= 0 ? s : 'auto';
|
||||
}
|
||||
|
||||
/** 从连接对象取编码,便于透传到 /api/webshell/exec 与 /api/webshell/file */
|
||||
function webshellConnEncoding(conn) {
|
||||
return normalizeWebshellEncoding(conn && conn.encoding);
|
||||
}
|
||||
|
||||
/** 允许的目标 OS,与后端 normalizeWebshellOS 对齐 */
|
||||
const WEBSHELL_ALLOWED_OS = ['auto', 'linux', 'windows'];
|
||||
|
||||
/** 归一化连接的 os 字段,返回 'auto' | 'linux' | 'windows'(空/未知 → auto) */
|
||||
function normalizeWebshellOS(v) {
|
||||
var s = (v == null ? '' : String(v)).trim().toLowerCase();
|
||||
if (!s) return 'auto';
|
||||
return WEBSHELL_ALLOWED_OS.indexOf(s) >= 0 ? s : 'auto';
|
||||
}
|
||||
|
||||
/** 从连接对象取目标 OS,便于透传到 /api/webshell/exec 与 /api/webshell/file */
|
||||
function webshellConnOS(conn) {
|
||||
return normalizeWebshellOS(conn && conn.os);
|
||||
}
|
||||
|
||||
/**
|
||||
* 组装 /api/webshell/file 的公共请求体。
|
||||
* 所有文件管理调用点都应走此函数,避免遗漏字段(如 connection_id)。
|
||||
* @param {Object} conn 连接对象
|
||||
* @param {Object} extra 额外字段(action / path / content / target_path / chunk_index ...)
|
||||
* @returns {string} JSON 字符串
|
||||
*/
|
||||
function webshellFileRequestBody(conn, extra) {
|
||||
const base = {
|
||||
url: conn.url,
|
||||
password: conn.password || '',
|
||||
type: conn.type || 'php',
|
||||
method: (conn.method || 'post').toLowerCase(),
|
||||
cmd_param: conn.cmdParam || '',
|
||||
encoding: webshellConnEncoding(conn),
|
||||
os: webshellConnOS(conn),
|
||||
connection_id: conn.id || ''
|
||||
};
|
||||
const merged = Object.assign(base, extra || {});
|
||||
return JSON.stringify(merged);
|
||||
}
|
||||
|
||||
/**
|
||||
* 当服务端探活命中目标系统(仅 auto 连接首次列目录时出现)时,
|
||||
* 把结果同步到本地 webshellConnections 缓存 + 持久化到数据库。
|
||||
* 后续刷新不再探活,AI 也能直接看到正确的 OS 上下文。
|
||||
*/
|
||||
function applyWebshellDetectedOS(conn, data) {
|
||||
if (!conn || !data || !data.detected_os) return;
|
||||
const detected = normalizeWebshellOS(data.detected_os);
|
||||
if (detected !== 'linux' && detected !== 'windows') return;
|
||||
if (webshellConnOS(conn) !== 'auto') return; // 用户已显式配置,尊重之
|
||||
conn.os = detected;
|
||||
if (Array.isArray(webshellConnections)) {
|
||||
for (var i = 0; i < webshellConnections.length; i++) {
|
||||
if (webshellConnections[i] && webshellConnections[i].id === conn.id) {
|
||||
webshellConnections[i].os = detected;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (typeof renderWebshellList === 'function') {
|
||||
try { renderWebshellList(); } catch (e) {}
|
||||
}
|
||||
// 服务端已经回写了 DB;但极少数情况下调用方未带 connection_id,这里再兜底 PUT 一次
|
||||
if (conn.id && typeof apiFetch === 'function') {
|
||||
apiFetch('/api/webshell/connections/' + encodeURIComponent(conn.id), {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
url: conn.url,
|
||||
password: conn.password || '',
|
||||
type: conn.type || 'php',
|
||||
method: conn.method || 'post',
|
||||
cmd_param: conn.cmdParam || '',
|
||||
remark: conn.remark || '',
|
||||
encoding: conn.encoding || 'auto',
|
||||
os: detected
|
||||
})
|
||||
}).catch(function () {});
|
||||
}
|
||||
}
|
||||
|
||||
/** 与主对话页一致:Eino 模式走 /api/multi-agent/stream,body 带 orchestration */
|
||||
function resolveWebshellAiStreamRequest() {
|
||||
if (typeof apiFetch === 'undefined') {
|
||||
@@ -335,6 +429,17 @@ function wsT(key) {
|
||||
'webshell.addConnection': '添加连接',
|
||||
'webshell.cmdParam': '命令参数名',
|
||||
'webshell.cmdParamPlaceholder': '不填默认为 cmd,如填 xxx 则请求为 xxx=命令',
|
||||
'webshell.encoding': '响应编码',
|
||||
'webshell.encodingAuto': '自动检测',
|
||||
'webshell.encodingUtf8': 'UTF-8',
|
||||
'webshell.encodingGbk': 'GBK(中文 Windows)',
|
||||
'webshell.encodingGb18030': 'GB18030',
|
||||
'webshell.encodingHint': '中文 Windows 目标若出现乱码,请切换为 GBK 或 GB18030',
|
||||
'webshell.os': '目标系统',
|
||||
'webshell.osAuto': '自动(按 Shell 类型推断)',
|
||||
'webshell.osLinux': 'Linux / Unix',
|
||||
'webshell.osWindows': 'Windows',
|
||||
'webshell.osHint': '决定文件管理/上传使用 Linux 还是 Windows 命令;PHP/JSP 跑在 Windows 上请选 Windows',
|
||||
'webshell.connections': '连接列表',
|
||||
'webshell.noConnections': '暂无连接,请点击「添加连接」',
|
||||
'webshell.selectOrAdd': '请从左侧选择连接,或添加新的 WebShell 连接',
|
||||
@@ -661,9 +766,20 @@ function renderWebshellList() {
|
||||
} else if (probe && probe.state === 'fail') {
|
||||
probeHtml = '<span class="webshell-probe-badge fail" title="' + escapeHtml(probe.message || '') + '">' + (wsT('webshell.probeOffline') || '离线') + '</span>';
|
||||
}
|
||||
var encNorm = normalizeWebshellEncoding(conn.encoding);
|
||||
var encHtml = '';
|
||||
if (encNorm && encNorm !== 'auto') {
|
||||
encHtml = '<span class="webshell-probe-badge" title="' + escapeHtml(wsT('webshell.encoding') || '响应编码') + '">' + escapeHtml(encNorm.toUpperCase()) + '</span>';
|
||||
}
|
||||
var osNorm = normalizeWebshellOS(conn.os);
|
||||
var osHtml = '';
|
||||
if (osNorm && osNorm !== 'auto') {
|
||||
var osLabel = osNorm === 'windows' ? 'WIN' : 'LINUX';
|
||||
osHtml = '<span class="webshell-probe-badge" title="' + escapeHtml(wsT('webshell.os') || '目标系统') + '">' + osLabel + '</span>';
|
||||
}
|
||||
return (
|
||||
'<div class="webshell-item' + active + '" data-id="' + safeId + '">' +
|
||||
'<div class="webshell-item-remark-row"><div class="webshell-item-remark" title="' + urlTitle + '">' + remark + '</div>' + probeHtml + '</div>' +
|
||||
'<div class="webshell-item-remark-row"><div class="webshell-item-remark" title="' + urlTitle + '">' + remark + '</div>' + probeHtml + osHtml + encHtml + '</div>' +
|
||||
'<div class="webshell-item-url" title="' + urlTitle + '">' + url + '</div>' +
|
||||
'<div class="webshell-item-actions">' +
|
||||
'<details class="webshell-conn-actions"><summary class="btn-ghost btn-sm webshell-conn-actions-btn" title="' + actionsLabel + '">' + actionsLabel + '</summary>' +
|
||||
@@ -709,6 +825,8 @@ function probeWebshellConnection(conn) {
|
||||
type: conn.type || 'php',
|
||||
method: ((conn.method || 'post').toLowerCase() === 'get') ? 'get' : 'post',
|
||||
cmd_param: conn.cmdParam || '',
|
||||
encoding: webshellConnEncoding(conn),
|
||||
os: webshellConnOS(conn),
|
||||
command: 'echo 1'
|
||||
})
|
||||
})
|
||||
@@ -3365,6 +3483,8 @@ function execWebshellCommand(conn, command) {
|
||||
type: conn.type || 'php',
|
||||
method: (conn.method || 'post').toLowerCase(),
|
||||
cmd_param: conn.cmdParam || '',
|
||||
encoding: webshellConnEncoding(conn),
|
||||
os: webshellConnOS(conn),
|
||||
command: command
|
||||
})
|
||||
}).then(function (r) { return r.json(); })
|
||||
@@ -3391,17 +3511,10 @@ function webshellFileListDir(conn, path) {
|
||||
apiFetch('/api/webshell/file', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
url: conn.url,
|
||||
password: conn.password || '',
|
||||
type: conn.type || 'php',
|
||||
method: (conn.method || 'post').toLowerCase(),
|
||||
cmd_param: conn.cmdParam || '',
|
||||
action: 'list',
|
||||
path: path
|
||||
})
|
||||
body: webshellFileRequestBody(conn, { action: 'list', path: path })
|
||||
}).then(function (r) { return r.json(); })
|
||||
.then(function (data) {
|
||||
applyWebshellDetectedOS(conn, data);
|
||||
if (!data.ok && data.error) {
|
||||
listEl.innerHTML = '<div class="webshell-file-error">' + escapeHtml(data.error) + '</div><pre class="webshell-file-raw">' + escapeHtml(data.output || '') + '</pre>';
|
||||
return;
|
||||
@@ -3497,16 +3610,9 @@ function fetchWebshellDirectoryItems(conn, path) {
|
||||
return apiFetch('/api/webshell/file', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
url: conn.url,
|
||||
password: conn.password || '',
|
||||
type: conn.type || 'php',
|
||||
method: (conn.method || 'post').toLowerCase(),
|
||||
cmd_param: conn.cmdParam || '',
|
||||
action: 'list',
|
||||
path: path
|
||||
})
|
||||
body: webshellFileRequestBody(conn, { action: 'list', path: path })
|
||||
}).then(function (r) { return r.json(); }).then(function (data) {
|
||||
applyWebshellDetectedOS(conn, data);
|
||||
if (!data || data.error || !data.ok) return [];
|
||||
return parseWebshellListItems(data.output || '');
|
||||
}).catch(function () {
|
||||
@@ -3801,7 +3907,7 @@ function webshellFileMkdir(conn, pathInput) {
|
||||
var name = prompt(wsT('webshell.newDir') || '新建目录', 'newdir');
|
||||
if (name == null || !name.trim()) return;
|
||||
var path = base === '.' ? name.trim() : base + '/' + name.trim();
|
||||
apiFetch('/api/webshell/file', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ url: conn.url, password: conn.password || '', type: conn.type || 'php', method: (conn.method || 'post').toLowerCase(), cmd_param: conn.cmdParam || '', action: 'mkdir', path: path }) })
|
||||
apiFetch('/api/webshell/file', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: webshellFileRequestBody(conn, { action: 'mkdir', path: path }) })
|
||||
.then(function (r) { return r.json(); })
|
||||
.then(function () { webshellFileListDir(conn, base); })
|
||||
.catch(function () { webshellFileListDir(conn, base); });
|
||||
@@ -3848,7 +3954,7 @@ function webshellFileUpload(conn, pathInput) {
|
||||
webshellFileListDir(conn, base);
|
||||
return;
|
||||
}
|
||||
apiFetch('/api/webshell/file', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ url: conn.url, password: conn.password || '', type: conn.type || 'php', method: (conn.method || 'post').toLowerCase(), cmd_param: conn.cmdParam || '', action: 'upload_chunk', path: path, content: base64Chunks[idx], chunk_index: idx }) })
|
||||
apiFetch('/api/webshell/file', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: webshellFileRequestBody(conn, { action: 'upload_chunk', path: path, content: base64Chunks[idx], chunk_index: idx }) })
|
||||
.then(function (r) { return r.json(); })
|
||||
.then(function () { idx++; sendNext(); })
|
||||
.catch(function () { idx++; sendNext(); });
|
||||
@@ -3867,7 +3973,7 @@ function webshellFileRename(conn, oldPath, oldName, listEl) {
|
||||
var parts = oldPath.split('/');
|
||||
var dir = parts.length > 1 ? parts.slice(0, -1).join('/') + '/' : '';
|
||||
var newPath = dir + newName.trim();
|
||||
apiFetch('/api/webshell/file', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ url: conn.url, password: conn.password || '', type: conn.type || 'php', method: (conn.method || 'post').toLowerCase(), cmd_param: conn.cmdParam || '', action: 'rename', path: oldPath, target_path: newPath }) })
|
||||
apiFetch('/api/webshell/file', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: webshellFileRequestBody(conn, { action: 'rename', path: oldPath, target_path: newPath }) })
|
||||
.then(function (r) { return r.json(); })
|
||||
.then(function () { webshellFileListDir(conn, document.getElementById('webshell-file-path').value.trim() || '.'); })
|
||||
.catch(function () { webshellFileListDir(conn, document.getElementById('webshell-file-path').value.trim() || '.'); });
|
||||
@@ -3906,7 +4012,7 @@ function webshellFileDownload(conn, path) {
|
||||
apiFetch('/api/webshell/file', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ url: conn.url, password: conn.password || '', type: conn.type || 'php', method: (conn.method || 'post').toLowerCase(), cmd_param: conn.cmdParam || '', action: 'read', path: path })
|
||||
body: webshellFileRequestBody(conn, { action: 'read', path: path })
|
||||
}).then(function (r) { return r.json(); })
|
||||
.then(function (data) {
|
||||
var content = (data && data.output) != null ? data.output : (data.error || '');
|
||||
@@ -3927,7 +4033,7 @@ function webshellFileRead(conn, path, listEl, browsePath) {
|
||||
apiFetch('/api/webshell/file', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ url: conn.url, password: conn.password || '', type: conn.type || 'php', method: (conn.method || 'post').toLowerCase(), cmd_param: conn.cmdParam || '', action: 'read', path: path })
|
||||
body: webshellFileRequestBody(conn, { action: 'read', path: path })
|
||||
}).then(function (r) { return r.json(); })
|
||||
.then(function (data) {
|
||||
const out = (data && data.output) ? data.output : (data.error || '');
|
||||
@@ -3956,7 +4062,7 @@ function webshellFileEdit(conn, path, listEl) {
|
||||
apiFetch('/api/webshell/file', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ url: conn.url, password: conn.password || '', type: conn.type || 'php', method: (conn.method || 'post').toLowerCase(), cmd_param: conn.cmdParam || '', action: 'read', path: path })
|
||||
body: webshellFileRequestBody(conn, { action: 'read', path: path })
|
||||
}).then(function (r) { return r.json(); })
|
||||
.then(function (data) {
|
||||
const content = (data && data.output) ? data.output : (data.error || '');
|
||||
@@ -3992,7 +4098,7 @@ function webshellFileWrite(conn, path, content, onDone, listEl) {
|
||||
apiFetch('/api/webshell/file', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ url: conn.url, password: conn.password || '', type: conn.type || 'php', method: (conn.method || 'post').toLowerCase(), cmd_param: conn.cmdParam || '', action: 'write', path: path, content: content })
|
||||
body: webshellFileRequestBody(conn, { action: 'write', path: path, content: content })
|
||||
}).then(function (r) { return r.json(); })
|
||||
.then(function (data) {
|
||||
if (data && !data.ok && data.error && listEl) {
|
||||
@@ -4011,7 +4117,7 @@ function webshellFileDelete(conn, path, onDone) {
|
||||
apiFetch('/api/webshell/file', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ url: conn.url, password: conn.password || '', type: conn.type || 'php', method: (conn.method || 'post').toLowerCase(), cmd_param: conn.cmdParam || '', action: 'delete', path: path })
|
||||
body: webshellFileRequestBody(conn, { action: 'delete', path: path })
|
||||
}).then(function (r) { return r.json(); })
|
||||
.then(function () { if (onDone) onDone(); })
|
||||
.catch(function () { if (onDone) onDone(); });
|
||||
@@ -4063,6 +4169,10 @@ function showAddWebshellModal() {
|
||||
document.getElementById('webshell-type').value = 'php';
|
||||
document.getElementById('webshell-method').value = 'post';
|
||||
document.getElementById('webshell-cmd-param').value = '';
|
||||
var osSelEl = document.getElementById('webshell-os');
|
||||
if (osSelEl) osSelEl.value = 'auto';
|
||||
var encSelEl = document.getElementById('webshell-encoding');
|
||||
if (encSelEl) encSelEl.value = 'auto';
|
||||
document.getElementById('webshell-remark').value = '';
|
||||
var titleEl = document.getElementById('webshell-modal-title');
|
||||
if (titleEl) titleEl.textContent = wsT('webshell.addConnection');
|
||||
@@ -4081,6 +4191,10 @@ function showEditWebshellModal(connId) {
|
||||
document.getElementById('webshell-type').value = conn.type || 'php';
|
||||
document.getElementById('webshell-method').value = (conn.method || 'post').toLowerCase();
|
||||
document.getElementById('webshell-cmd-param').value = conn.cmdParam || '';
|
||||
var osEditEl = document.getElementById('webshell-os');
|
||||
if (osEditEl) osEditEl.value = normalizeWebshellOS(conn.os);
|
||||
var encEditEl = document.getElementById('webshell-encoding');
|
||||
if (encEditEl) encEditEl.value = normalizeWebshellEncoding(conn.encoding);
|
||||
document.getElementById('webshell-remark').value = conn.remark || '';
|
||||
var titleEl = document.getElementById('webshell-modal-title');
|
||||
if (titleEl) titleEl.textContent = wsT('webshell.editConnectionTitle');
|
||||
@@ -4308,6 +4422,8 @@ function testWebshellConnection() {
|
||||
var method = ((document.getElementById('webshell-method') || {}).value || 'post').toLowerCase();
|
||||
var cmdParam = (document.getElementById('webshell-cmd-param') || {}).value;
|
||||
if (cmdParam && typeof cmdParam.trim === 'function') cmdParam = cmdParam.trim(); else cmdParam = '';
|
||||
var osTag = normalizeWebshellOS((document.getElementById('webshell-os') || {}).value);
|
||||
var encoding = normalizeWebshellEncoding((document.getElementById('webshell-encoding') || {}).value);
|
||||
var btn = document.getElementById('webshell-test-btn');
|
||||
if (btn) { btn.disabled = true; btn.textContent = (typeof wsT === 'function' ? wsT('common.refresh') : '刷新') + '...'; }
|
||||
if (typeof apiFetch === 'undefined') {
|
||||
@@ -4315,6 +4431,7 @@ function testWebshellConnection() {
|
||||
alert(wsT('webshell.testFailed') || '连通性测试失败');
|
||||
return;
|
||||
}
|
||||
// 连通性使用 Windows/Linux 都识别的最小内建命令作为探测(echo 1 在 cmd 和 sh 下行为等价)
|
||||
apiFetch('/api/webshell/exec', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
@@ -4324,6 +4441,8 @@ function testWebshellConnection() {
|
||||
type: type,
|
||||
method: method === 'get' ? 'get' : 'post',
|
||||
cmd_param: cmdParam || '',
|
||||
encoding: encoding,
|
||||
os: osTag,
|
||||
command: 'echo 1'
|
||||
})
|
||||
})
|
||||
@@ -4369,12 +4488,14 @@ function saveWebshellConnection() {
|
||||
var method = ((document.getElementById('webshell-method') || {}).value || 'post').toLowerCase();
|
||||
var cmdParam = (document.getElementById('webshell-cmd-param') || {}).value;
|
||||
if (cmdParam && typeof cmdParam.trim === 'function') cmdParam = cmdParam.trim(); else cmdParam = '';
|
||||
var osTag = normalizeWebshellOS((document.getElementById('webshell-os') || {}).value);
|
||||
var encoding = normalizeWebshellEncoding((document.getElementById('webshell-encoding') || {}).value);
|
||||
var remark = (document.getElementById('webshell-remark') || {}).value;
|
||||
if (remark && typeof remark.trim === 'function') remark = remark.trim(); else remark = '';
|
||||
|
||||
var editIdEl = document.getElementById('webshell-edit-id');
|
||||
var editId = editIdEl ? editIdEl.value.trim() : '';
|
||||
var body = { url: url, password: password, type: type, method: method === 'get' ? 'get' : 'post', cmd_param: cmdParam, remark: remark || url };
|
||||
var body = { url: url, password: password, type: type, method: method === 'get' ? 'get' : 'post', cmd_param: cmdParam, encoding: encoding, os: osTag, remark: remark || url };
|
||||
if (typeof apiFetch === 'undefined') return;
|
||||
|
||||
var reqUrl = editId ? ('/api/webshell/connections/' + encodeURIComponent(editId)) : '/api/webshell/connections';
|
||||
|
||||
Reference in New Issue
Block a user