BREAKING CHANGES: (for SeCuRiTy):

By default:
- `ssid_sessions[socket.ssid]` is now `session_data`
- `require` is no longer allowed in user scripts

To access global `socket_sessions` and `ssid_sessions`, as well as `require` additional modules, you must set `privileged: true` for the specific service. See `config.json`.
This commit is contained in:
zefie
2022-10-09 13:26:14 -04:00
parent 2491f62b89
commit 37f1ab67ad
118 changed files with 577 additions and 530 deletions

View File

@@ -6,7 +6,6 @@ const path = require('path');
const zlib = require('zlib');
const http = require('http');
const https = require('https');
const strftime = require('strftime'); // used externally by service scripts
const net = require('net');
const CryptoJS = require('crypto-js');
const { crc16 } = require('easy-crc');
@@ -40,14 +39,6 @@ var socket_sessions = new Array();
var ports = [];
// for scripts
function parseBool(val) {
if (typeof val === 'string')
val = val.toLowerCase();
return val === true || val === "true";
}
// add .reverse() feature to all JavaScript Strings in this application
// works for service vault scripts too.
if (!String.prototype.reverse) {
@@ -95,10 +86,6 @@ function getServiceString(service, overrides = {}) {
}
}
// passthrough for old scripts
function doErrorPage(code, data = null, pc_mode = false) {
return wtvshared.doErrorPage(code, data, pc_mode);
}
async function sendRawFile(socket, path) {
if (!minisrv_config.config.debug_flags.quiet) console.log(" * Found " + path + " to handle request (Direct File Mode) [Socket " + socket.id + "]");
@@ -111,70 +98,101 @@ async function sendRawFile(socket, path) {
});
}
var runScriptInVM = function (script_data, user_contextObj = {}, privileged = false, filename = null) {
// Here we define the ServiceVault Script Context Object
// The ServiceVault scripts will only be allowed to access the following fcnutions/variables.
// Furthermore, only modifications to variables in `updateFromVM` will be saved.
// Example: an attempt to change "minisrv_config" from a ServiceVault script would be discarded
var WTVGuide = null;
if (fs.existsSync(__dirname + "/WTVGuide.js")) WTVGuide = require("./WTVGuide.js");
var WTVBGMusic = require("./WTVBGMusic.js");
// create global context object
var contextObj = {
// node core variables and functions
"console": console, // needed for per-script debugging
"__dirname": __dirname, // needed by services such as wtv-flashrom and wtv-disk
// Our modules
"wtvmime": wtvmime,
"http": http,
"https": https,
"wtvshared": wtvshared,
"zlib": zlib,
"clientShowAlert": clientShowAlert,
"WTVClientSessionData": WTVClientSessionData,
"WTVClientCapabilities": WTVClientCapabilities,
"WTVFlashrom": WTVFlashrom,
"strftime": require('strftime'),
"CryptoJS": CryptoJS,
"fs": fs,
"path": path,
// Our variables and functions
"minisrv_config": minisrv_config,
"socket": null,
"headers": null,
"data": null,
"request_is_async": false,
"minisrv_version_string": z_title,
"getServiceString": getServiceString,
"sendToClient": sendToClient,
"service_vaults": service_vaults,
"cwd": __dirname, // current working directory
// Our prototype overrides
"Buffer": Buffer,
"String": String,
"Object": Object,
// add any additional context objects provided with function call
...user_contextObj
}
if (contextObj.socket) {
if (contextObj.socket.ssid) {
if (WTVGuide) contextObj.wtvguide = new WTVGuide(minisrv_config, ssid_sessions[contextObj.socket.ssid], contextObj.socket, runScriptInVM);
contextObj.wtvbgm = new WTVBGMusic(minisrv_config, ssid_sessions[contextObj.socket.ssid]);
}
if (contextObj.socket.id)
if (socket_sessions[contextObj.socket.id]) contextObj.wtv_encrypted = (socket_sessions[contextObj.socket.id].secure === true);
}
if (privileged) {
contextObj = {
...contextObj,
"privileged": true,
"require": require, // this is dangerous but needed for some scripts at this time
"SessionStore": SessionStore,
"ssid_sessions": ssid_sessions,
"socket_sessions": socket_sessions,
"reloadConfig": reloadConfig
}
}
var options = {};
if (filename) options = { "filename": filename };
var eval_ctx = new vm.Script(script_data, options)
eval_ctx.runInNewContext(contextObj, {
"breakOnSigint": true
});
return contextObj; // updated context object with whatever global varibles the script set
}
async function processPath(socket, service_vault_file_path, request_headers = new Array(), service_name, shared_romcache = null) {
var headers, data = null;
var request_is_async = false;
var service_vault_found = false;
var service_path = unescape(service_vault_file_path);
var usingSharedROMCache = false;
// Here we define the ServiceVault Script Context Object
// The ServiceVault scripts will only be allowed to access the following fcnutions/variables.
// Furthermore, only modifications to variables in `updateFromVM` will be saved.
// Example: an attempt to change "minisrv_config" from a ServiceVault script would be discarded
// node core variables and functions
var contextObj = {
console: console, // needed for per-script debugging
require: require, // this is dangerous but needed for some scripts at this time
__dirname: __dirname // needed by services such as wtv-flashrom and wtv-disk
}
// Our modules
contextObj = {
...contextObj,
wtvmime: wtvmime,
http: http,
https: https,
wtvshared: wtvshared,
zlib: zlib,
clientShowAlert: clientShowAlert,
WTVClientSessionData: WTVClientSessionData,
WTVClientCapabilities: WTVClientCapabilities,
WTVFlashrom: WTVFlashrom,
strftime: strftime,
CryptoJS: CryptoJS,
fs: fs,
path: path
}
// Our variables and functions
contextObj = {
...contextObj,
minisrv_config: minisrv_config,
getServiceString: getServiceString,
sendToClient: sendToClient,
socket: socket,
SessionStore: SessionStore,
request_headers: request_headers,
service_name: service_name,
service_vaults: service_vaults,
ssid_sessions: ssid_sessions,
socket_sessions: socket_sessions,
headers: headers,
data: data,
request_is_async: request_is_async,
minisrv_version_string: z_title,
parseBool: parseBool,
reloadConfig: reloadConfig,
cwd: __dirname // current working directory, updated below in function
}
// Our prototype overrides
contextObj = {
...contextObj,
Buffer: Buffer,
String: String,
Object: Object
"privileged": false,
"socket": socket,
"session_data": ssid_sessions[socket.ssid],
"request_headers": request_headers,
"service_name": service_name,
"cwd": __dirname // current working directory, updated below in function
}
// Define the variables that we want to assign from the evaluated script.
@@ -186,8 +204,9 @@ async function processPath(socket, service_vault_file_path, request_headers = ne
["headers", "headers"], // we need to be able to read the script's response headers
["data", "data"], // we need to be able to read the script's response data
["request_is_async", "request_is_async"], // we need to know if the script is async or not
["ssid_sessions", "ssid_sessions"], // certain service scripts need to update the user session, such as wtv-setup, wtv-mail, etc
["socket_sessions", "socket_sessions"] // certain service scripts need to update the socket session, such as wtv-1800, etc
["ssid_sessions", "ssid_sessions"], // global ssid_sessions object for privileged service scripts, such as wtv-setup, wtv-head-waiter, etc
["socket_sessions", "socket_sessions"], // global socket_sessions object for privileged service scripts, such as wtv-1800, etc
[`ssid_sessions[${socket.ssid}]`, "session_data"] // user-specific session data from unprivileged scripts
];
try {
@@ -347,18 +366,17 @@ async function processPath(socket, service_vault_file_path, request_headers = ne
// expose var service_dir for script path to the root of the wtv-service
socket_sessions[socket.id].starttime = Math.floor(new Date().getTime() / 1000);
var script_data = fs.readFileSync(service_vault_file_path + ".js").toString();
var eval_ctx = new vm.Script(script_data, {
"filename": service_vault_file_path + ".js"
})
eval_ctx.runInNewContext(contextObj, {
"breakOnSigint": true
});
var priv = (minisrv_config.services[service_name].privileged) ? true : false;
var vmResults = runScriptInVM(script_data, contextObj, priv, service_vault_file_path + ".js");
// Here we read back certain data from the ServiceVault Script Context Object
updateFromVM.forEach((item) => {
eval(item[0] +' = contextObj["'+item[1]+'"]');
try {
if (typeof vmResults[item[1]] !== "undefined") eval(item[0] + ' = vmResults["' + item[1] + '"]');
} catch (e) {
}
})
if (request_is_async && !minisrv_config.config.debug_flags.quiet) console.log(" * Script requested Asynchronous mode");
}
else if (fs.existsSync(service_vault_file_path + ".html")) {
@@ -389,19 +407,18 @@ async function processPath(socket, service_vault_file_path, request_headers = ne
if (!minisrv_config.config.debug_flags.quiet) console.log(" * Found catchall at " + catchall_file + " to handle request (JS Interpreter Mode) [Socket " + socket.id + "]");
request_headers.service_file_path = catchall_file;
var script_data = fs.readFileSync(catchall_file).toString();
var eval_ctx = new vm.Script(script_data, {
"filename": catchall_file
})
eval_ctx.runInNewContext(contextObj, {
"breakOnSigint": true
});
var priv = (minisrv_config.services[service_name].privileged) ? true : false;
runScriptInVM(script_data, contextObj, priv, catchall_file);
// 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] + '"]');
} catch (e) {
}
// Here we read back certain data from the ServiceVault Script Context Object
updateFromVM.forEach((item) => {
eval(item[0] +' = contextObj["'+item[1]+'"]');
})
if (request_is_async && !minisrv_config.config.debug_flags.quiet) console.log(" * Script requested Asynchronous mode");
break;
break;
} else {
service_check_dir.pop();
}
@@ -452,7 +469,7 @@ function verifyServicePort(service_name, socket) {
return minisrv_config.services[service_name].servicevault_dir;
else
return service_name;
}
}
}
}
return false;
@@ -558,7 +575,7 @@ async function processURL(socket, request_headers) {
}
}
} catch (e) {
console.log("error:",e)
console.log("error:", e)
}
}
if ((shortURL.indexOf("http") != 0 && shortURL.indexOf("ftp") != 0 && shortURL.indexOf(":") > 0 && shortURL.indexOf(":/") == -1)) {
@@ -1047,7 +1064,7 @@ async function sendToClient(socket, headers_obj, data) {
var compression_percentage = ((1 - (compressed_content_length / uncompressed_content_length)) * 100).toFixed(1);
if (uncompressed_content_length != compressed_content_length) if (minisrv_config.config.debug_flags.debug) console.log(" # Compression stats: Orig Size:", uncompressed_content_length, "~ Comp Size:", compressed_content_length, "~ Ratio:", compression_ratio, "~ Saved:", compression_percentage.toString() + "%");
}
// encrypt if needed
if (socket_sessions[socket.id].secure == true && !socket_sessions[socket.id].do_not_encrypt) {
@@ -1301,7 +1318,7 @@ async function processRequest(socket, data_hex, skipSecure = false, encryptedReq
}
}
}
}
}
if (!headers) return;
@@ -1314,7 +1331,7 @@ async function processRequest(socket, data_hex, skipSecure = false, encryptedReq
}
if (!ssid_sessions[socket.ssid].data_store.sockets) ssid_sessions[socket.ssid].data_store.sockets = new Set();
ssid_sessions[socket.ssid].data_store.sockets.add(socket);
}
}
}
if (!socket.ssid) {
@@ -1521,7 +1538,7 @@ async function processRequest(socket, data_hex, skipSecure = false, encryptedReq
console.log(socket)
console.log("pc https?");
} else {
// handle streaming POST
// handle streaming POST
if (socket_sessions[socket.id].expecting_post_data && headers) {
socket_sessions[socket.id].headers = headers;
if (socket_sessions[socket.id].post_data.length < (socket_sessions[socket.id].post_data_length * 2)) {
@@ -1866,7 +1883,7 @@ if (!minisrv_config.config.bind_ip) minisrv_config.config.bind_ip = "0.0.0.0";
bind_ports.every(function (v) {
try {
var server = net.createServer(handleSocket);
server.listen(v, minisrv_config.config.bind_ip);
server.listen(v, minisrv_config.config.bind_ip);
initstring += v + ", ";
return true;
} catch (e) {