From 181b5177ab0fa236ac48b3a8d093ba99c6d26787 Mon Sep 17 00:00:00 2001 From: zefie Date: Tue, 5 Aug 2025 23:24:13 -0400 Subject: [PATCH] security and optimizations --- zefie_wtvp_minisrv/app.js | 128 +++++++++++------- .../includes/classes/WTVClientSessionData.js | 4 +- zefie_wtvp_minisrv/includes/classes/WTVIRC.js | 22 --- 3 files changed, 79 insertions(+), 75 deletions(-) diff --git a/zefie_wtvp_minisrv/app.js b/zefie_wtvp_minisrv/app.js index 189cada2..e9eea7d9 100644 --- a/zefie_wtvp_minisrv/app.js +++ b/zefie_wtvp_minisrv/app.js @@ -1,6 +1,6 @@ 'use strict'; const path = require('path'); -var classPath = path.resolve(__dirname + path.sep + "includes" + path.sep + "classes" + path.sep) + path.sep; +const classPath = path.resolve(__dirname + path.sep + "includes" + path.sep + "classes" + path.sep) + path.sep; require(classPath + "Prototypes.js"); const { WTVShared, clientShowAlert } = require(classPath + "WTVShared.js"); const wtvshared = new WTVShared(); // creates minisrv_config @@ -60,8 +60,8 @@ function getServiceEnabled(service) { } function getServiceByPort(port) { - var service_name = null; - Object.keys(minisrv_config.services).forEach(function (k) { + let service_name; + Object.keys(minisrv_config.services).forEach((k) => { if (service_name) return; if (minisrv_config.services[k].port) { if (port == parseInt(minisrv_config.services[k].port) && getServiceEnabled(k)) @@ -72,8 +72,8 @@ function getServiceByPort(port) { } function getServiceByVHost(vhost) { - var service_name = null; - Object.keys(minisrv_config.services).forEach(function (k) { + let service_name; + Object.keys(minisrv_config.services).forEach((k) => { if (service_name) return; if (minisrv_config.services[k].vhost) { if (vhost.toLowerCase() == minisrv_config.services[k].vhost.toLowerCase()) @@ -89,7 +89,7 @@ function getPortByService(service) { } function getSocketServer(socket) { - var server = null; + let server; if (socket._server) { if (socket._server._connectionKey) server = socket._server; @@ -150,7 +150,7 @@ function configureService(service_name, service_obj, initial = false) { service_obj.service_name = service_name; if (!service_obj.host) { - service_obj.host = service_ip; + service_obj.host = minisrv_config.config.service_ip; } if (service_obj.port && !service_obj.nobind && initial) { if (service_obj.pc_services) pc_ports.push(service_obj.port); @@ -167,15 +167,16 @@ function configureService(service_name, service_obj, initial = false) { }); } } + let outstr = ''; if ((service_name == "wtv-star" && self.no_star_word != true) || service_name != "wtv-star") { - var outstr = "wtv-service: name=" + self.service_name + " host=" + self.host + " port=" + self.port; - if (self.flags) outstr += " flags=" + self.flags; - if (self.connections) outstr += " connections=" + self.connections; + outstr = `wtv-service: name=${self.service_name} host=${self.host} port=${self.port}`; + if (self.flags) outstr += ` flags=${self.flags}`; + if (self.connections) outstr += ` connections=${self.connections}`; } if (service_name == "wtv-star") { - outstr += "\nwtv-service: name=wtv-* host=" + self.host + " port=" + self.port; - if (self.flags) outstr += " flags=" + self.flags; - if (self.connections) outstr += " connections=" + self.connections; + outstr += `\nwtv-service: name=wtv-* host=${self.host} port=${self.port}`; + if (self.flags) outstr += ` flags=${self.flags}`; + if (self.connections) outstr += ` connections=${self.connections}`; } return outstr; } @@ -184,8 +185,8 @@ function configureService(service_name, service_obj, initial = false) { } // Where we store our session information -var ssid_sessions = new Array(); -var socket_sessions = new Array(); +var ssid_sessions = []; +var socket_sessions = []; var ports = []; var pc_ports = []; @@ -362,7 +363,7 @@ async function handleCGI(executable, cgi_file, socket, request_headers, vault, s } var env = wtvshared.cloneObj(process.env); env.QUERY_STRING = ""; - var request_data = new Array(); + var request_data = []; var split_req = request_headers.request.split(' '); request_data.method = split_req[0]; var request_type = (request_headers.request_url.indexOf(":/")) ? request_headers.request_url.split(":/")[0] : 'http'; @@ -485,7 +486,7 @@ async function handlePHP(socket, request_headers, php_file, vault, service_name, await handleCGI(minisrv_config.config.php_binpath, php_file, socket, request_headers, vault, service_name, session_data, extra_path); } -async function processPath(socket, service_vault_file_path, request_headers = new Array(), service_name, shared_romcache = null, pc_services = false) { +async function processPath(socket, service_vault_file_path, request_headers = [], service_name, shared_romcache = null, pc_services = false) { var headers, data = null; var request_is_async = false; var service_vault_found = false; @@ -535,7 +536,7 @@ async function processPath(socket, service_vault_file_path, request_headers = ne if (service_vault_found) return; if (!usingSharedROMCache) { if (minisrv_config.config.SharedROMCache && shared_romcache) { - if (shared_romcache.indexOf(minisrv_config.config.SharedROMCache) != -1) { + if (shared_romcache.includes(minisrv_config.config.SharedROMCache)) { var service_path_presplit = shared_romcache.split(path.sep); service_path_presplit.splice(service_path_presplit.findIndex((element) => element === 'ROMCache'), 1); var service_path_romcache = service_vault_dir + path.sep + service_path_presplit.join(path.sep); @@ -575,7 +576,8 @@ async function processPath(socket, service_vault_file_path, request_headers = ne } var is_dir = false; var file_exists = false; - minisrv_catchall, service_path_split, service_path_request_file = null; + // Clear variables to free memory + minisrv_catchall = service_path_split = service_path_request_file = null; if (fs.existsSync(service_vault_file_path)) { file_exists = true; is_dir = fs.lstatSync(service_vault_file_path).isDirectory() @@ -629,7 +631,20 @@ async function processPath(socket, service_vault_file_path, request_headers = ne // Here we read back certain data from the ServiceVault Script Context Object updateFromVM.forEach((item) => { try { - if (typeof vmResults[item[1]] !== "undefined") eval(item[0] + ' = vmResults["' + item[1] + '"]'); + if (typeof vmResults[item[1]] !== "undefined") { + // Safely assign without eval + if (item[0] === `ssid_sessions['${socket.ssid}']` && privileged) { + ssid_sessions[socket.ssid] = vmResults[item[1]]; + } else if (item[0] === 'headers') { + headers = vmResults[item[1]]; + } else if (item[0] === 'data') { + data = vmResults[item[1]]; + } else if (item[0] === 'request_is_async') { + request_is_async = vmResults[item[1]]; + } else if (item[0] === 'socket_sessions' && privileged) { + socket_sessions = vmResults[item[1]]; + } + } } catch (e) { console.error("vm readback error", e, item[0] + ' = vmResults[' + item[1] + ']'); } @@ -800,9 +815,23 @@ async function processPath(socket, service_vault_file_path, request_headers = ne updateFromVM.forEach((item) => { // Here we read back certain data from the ServiceVault Script Context Object try { - if (typeof vmResults[item[1]] !== "undefined") eval(item[0] + ' = vmResults["' + item[1] + '"]'); + if (typeof vmResults[item[1]] !== "undefined") { + console.log(item[0]) + // Safely assign without eval + if (item[0] === `ssid_sessions['${socket.ssid}']` && privileged) { + ssid_sessions[socket.ssid] = vmResults[item[1]]; + } else if (item[0] === 'headers') { + headers = vmResults[item[1]]; + } else if (item[0] === 'data') { + data = vmResults[item[1]]; + } else if (item[0] === 'request_is_async') { + request_is_async = vmResults[item[1]]; + } else if (item[0] === 'socket_sessions' && privileged) { + socket_sessions = vmResults[item[1]]; + } + } } catch (e) { - console.error("vm readback error", e); + console.error("vm readback error", e, item[0] + ' = vmResults[' + item[1] + ']'); } }); } else if (catchall_file.endsWith(".php")) { @@ -885,43 +914,38 @@ function getDirectoryIndex(svpath) { } async function processURL(socket, request_headers, pc_services = false) { - var shortURL, headers, data, service_name, original_service_name = ""; - var allow_double_slash, enable_multi_query, use_external_proxy = false; + var shortURL, headers, data, service_name; + var original_service_name = ""; + var allow_double_slash = false, enable_multi_query = false, use_external_proxy = false; request_headers.query = {}; + if (request_headers.request_url) { service_name = socket.service_name; if (pc_services) { original_service_name = socket.service_name; // store service name service_name = verifyServicePort(socket.service_name, socket); // get the actual ServiceVault path } - if (request_headers.request_url.indexOf('?') >= 0) { + if (request_headers.request_url.includes('?')) { shortURL = request_headers.request_url.split('?')[0]; - - if (request_headers.request_url.indexOf('?') >= 0) { - shortURL = request_headers.request_url.split('?')[0]; - var qraw = request_headers.request_url.split('?')[1]; - if (qraw.length > 0) { - qraw = qraw.split("&"); - for (let i = 0; i < qraw.length; i++) { - var qraw_split = qraw[i].split("="); - if (qraw_split.length == 2) { - var k = qraw_split[0]; - if (request_headers.query[k] && enable_multi_query) { - if (typeof request_headers.query[k] === 'string') { - var keyarray = [request_headers.query[k]]; - request_headers.query[k] = keyarray; - } - request_headers.query[k].push(unescape(qraw[i].split("=")[1].replace(/\+/g, "%20"))); - } else { - request_headers.query[k] = unescape(qraw[i].split("=")[1].replace(/\+/g, "%20")); + const qraw = request_headers.request_url.split('?')[1]; + if (qraw.length > 0) { + qraw.split("&").forEach(param => { + const qraw_split = param.split("="); + if (qraw_split.length == 2) { + const k = qraw_split[0]; + const value = unescape(qraw_split[1].replace(/\+/g, "%20")); + if (request_headers.query[k] && enable_multi_query) { + if (typeof request_headers.query[k] === 'string') { + request_headers.query[k] = [request_headers.query[k]]; } - } else if (qraw[i].length == 1) { - request_headers.query[qraw[i]] = null; + request_headers.query[k].push(value); + } else { + request_headers.query[k] = value; } + } else if (param.length == 1) { + request_headers.query[param] = null; } - } - } else { - shortURL = unescape(request_headers.request_url); + }); } } else { shortURL = unescape(request_headers.request_url); @@ -1088,7 +1112,7 @@ minisrv-no-mail-count: true`; } var urlToPath = wtvshared.fixPathSlashes(service_name + path.sep + shortURL.split(':/')[1]); var shared_romcache = null; - if ((shortURL.indexOf(":/ROMCache/") != -1 || shortURL.indexOf("://ROMCache/") != -1) && minisrv_config.config.enable_shared_romcache) { + if ((shortURL.includes(":/ROMCache/") || shortURL.includes("://ROMCache/")) && minisrv_config.config.enable_shared_romcache) { shared_romcache = wtvshared.fixPathSlashes(minisrv_config.config.SharedROMCache + path.sep + shortURL.split(':/')[1]); } if (minisrv_config.config.debug_flags.show_headers) console.debug(" * Incoming", (pc_services) ? "HTTP" : "WTVP", "headers on", (pc_services) ? "HTTP" : "WTVP", "socket ID", socket.id, await wtvshared.decodePostData(await wtvshared.filterRequestLog(await wtvshared.filterSSID(request_headers)))); @@ -1198,7 +1222,7 @@ function handleProxy(socket, request_type, request_headers, res, data) { // if Connection: close header, set our internal variable to close the socket if (headers['Connection']) { - if (headers['Connection'].toLowerCase().indexOf('close') !== -1) { + if (headers['Connection'].toLowerCase().includes('close')) { headers["wtv-connection-close"] = true; } } @@ -1235,7 +1259,7 @@ async function doHTTPProxy(socket, request_headers) { break; } - var request_data = new Array(); + var request_data = []; request_data.method = request_headers.request.split(' ')[0]; var request_url_split = request_headers.request.split(' ')[1].split('/'); request_data.host = request_url_split[2]; @@ -1690,7 +1714,7 @@ async function sendToSocket(socket, data) { if (socket_sessions[socket.id].post_data_length) delete socket_sessions[socket.id].post_data_length; if (socket_sessions[socket.id].post_data_percents_shown) delete socket_sessions[socket.id].post_data_percents_shown; socket.setTimeout(minisrv_config.config.socket_timeout * 1000); - if (socket_sessions[socket.id].close_me) socket.end(); + if (socket_sessions[socket.id] && socket_sessions[socket.id].close_me) socket.end(); if (socket_sessions[socket.id].destroy_me) socket.destroy(); } } diff --git a/zefie_wtvp_minisrv/includes/classes/WTVClientSessionData.js b/zefie_wtvp_minisrv/includes/classes/WTVClientSessionData.js index 5bccc731..5bc9624a 100644 --- a/zefie_wtvp_minisrv/includes/classes/WTVClientSessionData.js +++ b/zefie_wtvp_minisrv/includes/classes/WTVClientSessionData.js @@ -469,7 +469,9 @@ class WTVClientSessionData { "contentType": contentType })); return true; - } catch {} + } catch (e) { + console.error("Error in addToScrapbook:", e); + } return false; } diff --git a/zefie_wtvp_minisrv/includes/classes/WTVIRC.js b/zefie_wtvp_minisrv/includes/classes/WTVIRC.js index 7c82e9f0..8dad3638 100644 --- a/zefie_wtvp_minisrv/includes/classes/WTVIRC.js +++ b/zefie_wtvp_minisrv/includes/classes/WTVIRC.js @@ -93,7 +93,6 @@ class WTVIRC { this.maxtargets = this.irc_config.max_targets || 4; this.socket_timeout = 75; // Default socket timeout to 75 seconds, most clients will send PINGs every 60 seconds, so this should be enough to catch lost connections this.server_hello = this.irc_config.server_hello || `zefIRCd v${this.version} IRC server powered by minisrv`; - this.enable_eval = this.debug || false; // Enable eval in debug mode only this.serverId = this.irc_config.server_id || '00A'; // Default server ID, can be overridden in config this.allow_public_vhosts = this.irc_config.allow_public_vhosts || true; // If true, users can set their host to a virtual host that is not a real hostname or IP address, if false, only opers can. this.kick_insecure_users_on_secure = this.irc_config.kick_insecure_users_on_secure || true; // If true, users without SSL connections will be kicked from a channel when +Z is applied @@ -2796,27 +2795,6 @@ class WTVIRC { } } break; - case 'EVAL': - // VERY DANGEROUS - if (!this.checkRegistered(socket)) { - break; - } - if (!this.isIRCOp(socket.nickname)) { - await this.safeWriteToSocket(socket, `:${this.servername} 481 ${socket.nickname} :Permission denied - you are not an IRC operator\r\n`); - this.debugLog('warn', `EVAL command attempted by non-IRCOp: ${socket.nickname}`); - break; - } - if (!this.enable_eval) { - await this.safeWriteToSocket(socket, `:${this.servername} 404 ${socket.nickname} :Eval is disabled\r\n`); - break; - } - try { - const result = eval(params.join(' ')); - await this.safeWriteToSocket(socket, `:${this.servername} 200 ${socket.nickname} :${result}\r\n`); - } catch (error) { - await this.safeWriteToSocket(socket, `:${this.servername} 500 ${socket.nickname} :Error evaluating expression\r\n`); - } - break; case 'KILL': if (!this.checkRegistered(socket)) { break;