diff --git a/src/index.js b/src/index.js index a6cc5de..949ffd8 100644 --- a/src/index.js +++ b/src/index.js @@ -35,6 +35,7 @@ if (process.serverConfig.secure) { if (process.serverConfig.spubport === undefined) process.serverConfig.spubport = 443; if (process.serverConfig.sni === undefined) process.serverConfig.sni = {}; if (process.serverConfig.enableOCSPStapling === undefined) process.serverConfig.enableOCSPStapling = false; + } if (process.serverConfig.port === undefined) process.serverConfig.port = 80; if (process.serverConfig.pubport === undefined) process.serverConfig.pubport = 80; @@ -70,6 +71,8 @@ if (process.serverConfig.useWebRootServerSideScript === undefined) process.serve if (process.serverConfig.exposeModsInErrorPages === undefined) process.serverConfig.exposeModsInErrorPages = true; if (process.serverConfig.disableTrailingSlashRedirects === undefined) process.serverConfig.disableTrailingSlashRedirects = false; if (process.serverConfig.environmentVariables === undefined) process.serverConfig.environmentVariables = {}; +if (process.serverConfig.customHeadersVHost === undefined) process.serverConfig.customHeadersVHost = []; +if (process.serverConfig.enableDirectoryListingVHost === undefined) process.serverConfig.enableDirectoryListingVHost = []; if (process.serverConfig.wwwrootPostfixesVHost === undefined) process.serverConfig.wwwrootPostfixesVHost = []; if (process.serverConfig.wwwrootPostfixPrefixesVHost === undefined) process.serverConfig.wwwrootPostfixPrefixesVHost = []; if (process.serverConfig.allowDoubleSlashes === undefined) process.serverConfig.allowDoubleSlashes = false; @@ -111,7 +114,8 @@ let middleware = [ // TODO: blocklist require("./middleware/webRootPostfixes.js"), require("./middleware/rewriteURL.js"), - require("./middleware/responseHeaders.js") + require("./middleware/responseHeaders.js"), + require("./middleware/checkForbiddenPaths.js") ]; function addMiddleware(mw) { diff --git a/src/middleware/checkForbiddenPaths.js b/src/middleware/checkForbiddenPaths.js new file mode 100644 index 0000000..9f065a2 --- /dev/null +++ b/src/middleware/checkForbiddenPaths.js @@ -0,0 +1,110 @@ +const os = require("os"); +const path = require("path"); + +// Function to get URL path for use in forbidden path adding. +function getInitializePath(to) { + var cwd = process.cwd(); + if (os.platform() == "win32") { + to = to.replace(/\//g, "\\"); + if (to[0] == "\\") to = cwd.split("\\")[0] + to; + } + var absoluteTo = path.isAbsolute(to) ? to : (__dirname + (os.platform() == "win32" ? "\\" : "/") + to); + if (os.platform() == "win32" && cwd[0] != absoluteTo[0]) return ""; + var relative = path.relative(cwd, absoluteTo); + if (os.platform() == "win32") { + return "/" + relative.replace(/\\/g, "/"); + } else { + return "/" + relative; + } + } + + // Function to check if URL path name is a forbidden path. + function isForbiddenPath(decodedHref, match) { + var forbiddenPath = forbiddenPaths[match]; + if (!forbiddenPath) return false; + if (typeof forbiddenPath === "string") { + return decodedHref === forbiddenPath || (os.platform() === "win32" && decodedHref.toLowerCase() === forbiddenPath.toLowerCase()); + } + if (typeof forbiddenPath === "object") { + return forbiddenPath.some(function (forbiddenPathSingle) { + return (decodedHref === forbiddenPathSingle || (os.platform() === "win32" && decodedHref.toLowerCase() === forbiddenPathSingle.toLowerCase())); + }); + } + return false; + } + + // Function to check if URL path name is index of one of defined forbidden paths. + function isIndexOfForbiddenPath(decodedHref, match) { + var forbiddenPath = forbiddenPaths[match]; + if (!forbiddenPath) return false; + if (typeof forbiddenPath === "string") { + return decodedHref === forbiddenPath || decodedHref.indexOf(forbiddenPath + "/") === 0 || (os.platform() === "win32" && (decodedHref.toLowerCase() === forbiddenPath.toLowerCase() || decodedHref.toLowerCase().indexOf(forbiddenPath.toLowerCase() + "/") === 0)); + } + if (typeof forbiddenPath === "object") { + return forbiddenPath.some(function (forbiddenPathSingle) { + return (decodedHref === forbiddenPathSingle || decodedHref.indexOf(forbiddenPathSingle + "/") === 0 || (os.platform() === "win32" && (decodedHref.toLowerCase() === forbiddenPathSingle.toLowerCase() || decodedHref.toLowerCase().indexOf(forbiddenPathSingle.toLowerCase() + "/") === 0))); + }); + } + return false; + } + + // Set up forbidden paths + var forbiddenPaths = {}; + + forbiddenPaths.config = getInitializePath("./config.json"); + forbiddenPaths.certificates = []; + if (process.serverConfig.secure) { + forbiddenPaths.certificates.push(getInitializePath(configJSON.cert)); + forbiddenPaths.certificates.push(getInitializePath(configJSON.key)); + Object.keys(sni).forEach(function (sniHostName) { + forbiddenPaths.certificates.push(getInitializePath(sni[sniHostName].cert)); + forbiddenPaths.certificates.push(getInitializePath(sni[sniHostName].key)); + }); + } + forbiddenPaths.svrjs = getInitializePath("./" + ((__dirname[__dirname.length - 1] != "/") ? __filename.replace(__dirname + "/", "") : __filename.replace(__dirname, ""))); + forbiddenPaths.serverSideScripts = []; + if (process.serverConfig.useWebRootServerSideScript) { + forbiddenPaths.serverSideScripts.push("/serverSideScript.js"); + } else { + forbiddenPaths.serverSideScripts.push(getInitializePath("./serverSideScript.js")); + } + forbiddenPaths.serverSideScriptDirectories = []; + forbiddenPaths.serverSideScriptDirectories.push(getInitializePath("./node_modules")); + forbiddenPaths.serverSideScriptDirectories.push(getInitializePath("./mods")); + forbiddenPaths.temp = getInitializePath("./temp"); + forbiddenPaths.log = getInitializePath("./log"); + + +module.exports = (req, res, logFacilities, config, next) => { + let decodedHrefWithoutDuplicateSlashes = ""; + try { + decodedHrefWithoutDuplicateSlashes = decodeURIComponent(req.parsedURL.pathname).replace(/\/+/g,"/"); + } catch (err) { + res.error(400); + } + + // Check if path is forbidden + if ((isForbiddenPath(decodedHrefWithoutDuplicateSlashes, "config") || isForbiddenPath(decodedHrefWithoutDuplicateSlashes, "certificates")) && !req.isProxy) { + res.error(403); + logFacilities.errmessage("Access to configuration file/certificates is denied."); + return; + } else if (isIndexOfForbiddenPath(decodedHrefWithoutDuplicateSlashes, "temp") && !isProxy) { + res.error(403); + logFacilities.errmessage("Access to temporary folder is denied."); + return; + } else if (isIndexOfForbiddenPath(decodedHrefWithoutDuplicateSlashes, "log") && !isProxy && (config.enableLogging || config.enableLogging == undefined) && !config.enableRemoteLogBrowsing) { + res.error(403); + logFacilities.errmessage("Access to log files is denied."); + return; + } else if (isForbiddenPath(decodedHrefWithoutDuplicateSlashes, "svrjs") && !isProxy && !exposeServerVersion) { + res.error(403); + logFacilities.errmessage("Access to SVR.JS script is denied."); + return; + } else if ((isForbiddenPath(decodedHrefWithoutDuplicateSlashes, "svrjs") || isForbiddenPath(decodedHrefWithoutDuplicateSlashes, "serverSideScripts") || isIndexOfForbiddenPath(decodedHrefWithoutDuplicateSlashes, "serverSideScriptDirectories")) && !isProxy && (config.disableServerSideScriptExpose || config.disableServerSideScriptExpose === undefined)) { + res.error(403); + logFacilities.errmessage("Access to sources is denied."); + return; + } + + next(); +} \ No newline at end of file