CVE-2021-4260: URL Redirection to Untrusted Site ('Open Redirect') (CWE-601)
value: A vulnerability was found in oils-js. It has been declared as critical. This vulnerability affects unknown code of the file core/Web.js. The manipulation leads to open redirect. The attack can be initiated remotely. The name of the patch is fad8fbae824a7d367dacb90d56cb02c5cb999d42. It is recommended to apply a patch to fix this issue. The identifier of this vulnerability is VDB-216268.
CWE description: A web application accepts a user-controlled input that specifies a link to an external site, and uses that link in a Redirect. This simplifies phishing attacks.
CVSS v3 base score: 6.1
Published: 2022-12-19T14:15Z
Repository: https://github.com/mannyvergel/oils-js
Language: JSON
Files changed: core/Web.js, core/conf/conf-default.js, package-lock.json, package.json
Commit date: 2021-03-23 20:43:39 +0800
Commit message: Framework fix for open redirect vulnerability

Diff: diff --git a/core/Web.js b/core/Web.js
index 7e9f489..dcd5336 100644
--- a/core/Web.js
+++ b/core/Web.js
@@ -120,10 +120,13 @@ class Web {
     if (console.isDebug) {
       console.debug('Oils config: ' + JSON.stringify(this.conf, null, 2));
     }
 
     this.app = express();
+
+    fixOpenRedirect(this);
+
     this.events = {};
     this.modelCache = new Object();
     this.plugins = [];
 
     this.overrideResponse();
@@ -761,10 +764,46 @@ function defaultRedirectToHttpsMiddleware(req, res) {
   }
   res.writeHead(302, {'Location': 'https://' + hostStr + nonStandardPort + req.url});
   res.end();
 }
 
+function fixOpenRedirect(web) {
+  // Fix for open redirect security
+  let redirectSafe = web.app.response.redirect;
+  web.app.response.redirectSafe = function(url) {
+    return redirectSafe.call(this, url);
+  }
+
+  var addHostOnceFlag = true;
+
+  web.app.response.redirect = function(url) {
+  
+    if (url.indexOf('://') != -1) {
+
+      let req = this.req;
+
+      if (addHostOnceFlag) {
+        var host = req.protocol + '://' + req.headers.host;
+        web.conf.allowedRedirectHosts.push(host);
+        addHostOnceFlag = false;
+        console.log("Added host once: " + host);
+      }
+
+      const found = web.conf.allowedRedirectHosts.find(el => url.indexOf(el) == 0);
+
+      if (!found) {
+        var ip = web.utils.getClientIp(req);
+
+        console.warn("Open redirect was triggered: ", req.method, req.user ? req.user.email : "unsigned user", ip, "accessed", req.url, req.headers['user-agent']);
+        throw new Error("Action not allowed.");
+      }
+
+    }
+    return redirectSafe.call(this, url);
+  }
+}
+
 
 function startServer(web, cb) {
 
   const http = require('http');
 
diff --git a/core/conf/conf-default.js b/core/conf/conf-default.js
index 1719c29..36a8656 100644
--- a/core/conf/conf-default.js
+++ b/core/conf/conf-default.js
@@ -9,10 +9,11 @@ module.exports = function(webSel) {
     isProd: isProd,
     isProduction: isProd,
 
     dataDir: 'data',
     tmpDir: 'data/tmp',
+    allowedRedirectHosts: [],
 
     extendWeb: {
       enabled: true,
       path: '/conf/ext',
       context: {
diff --git a/package-lock.json b/package-lock.json
index ca72fd7..722113e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,8 +1,8 @@
 {
   "name": "oils",
-  "version": "7.8.6",
+  "version": "8.0.0",
   "lockfileVersion": 1,
   "requires": true,
   "dependencies": {
     "@root/mkdirp": {
       "version": "1.0.0",
@@ -12,10 +12,32 @@
     "@root/request": {
       "version": "1.5.0",
       "resolved": "https://registry.npmjs.org/@root/request/-/request-1.5.0.tgz",
       "integrity": "sha512-J9RUIwVU99/cOVuDVYlNpr4G0A1/3ZxhCXIRiTZzu8RntOnb0lmDBMckhaus5ry9x/dBqJKDplFIgwHbLi6rLA=="
     },
+    "@types/bson": {
+      "version": "4.0.3",
+      "resolved": "https://registry.npmjs.org/@types/bson/-/bson-4.0.3.tgz",
+      "integrity": "sha512-mVRvYnTOZJz3ccpxhr3wgxVmSeiYinW+zlzQz3SXWaJmD1DuL05Jeq7nKw3SnbKmbleW5qrLG5vdyWe/A9sXhw==",
+      "requires": {
+        "@types/node": "*"
+      }
+    },
+    "@types/mongodb": {
+      "version": "3.6.10",
+      "resolved": "https://registry.npmjs.org/@types/mongodb/-/mongodb-3.6.10.tgz",
+      "integrity": "sha512-BkwAHFiZSSWdTIqbUVGmgvIsiXXjqAketeK7Izy7oSs6G3N8Bn993tK9eq6QEovQDx6OQ2FGP2KWDDxBzdlJ6Q==",
+      "requires": {
+        "@types/bson": "*",
+        "@types/node": "*"
+      }
+    },
+    "@types/node": {
+      "version": "14.14.35",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.35.tgz",
+      "integrity": "sha512-Lt+wj8NVPx0zUmUwumiVXapmaLUcAk3yPuHCFVXras9k5VT9TdhJqKqGVUQCD60OTMCl0qxJ57OiTL0Mic3Iag=="
+    },
     "a-sync-waterfall": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/a-sync-waterfall/-/a-sync-waterfall-1.0.1.tgz",
       "integrity": "sha512-RYTOHHdWipFUliRFMCS4X2Yn2X8M87V/OpSqWzKKOGhzqyUxzyVmhHDH9sAvG+ZuQf/TAOFsLCpMw09I1ufUnA=="
     },
@@ -247,13 +269,13 @@
       "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz",
       "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==",
       "dev": true
     },
     "bson": {
-      "version": "1.1.5",
-      "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.5.tgz",
-      "integrity": "sha512-kDuEzldR21lHciPQAIulLs1LZlCXdLziXI6Mb/TDkwXhb//UORJNPXgcRs2CuO4H0DcMkpfT3/ySsP3unoZjBg=="
+      "version": "1.1.6",
+      "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.6.tgz",
+      "integrity": "sha512-EvVNVeGo4tHxwi8L6bPj3y3itEvStdwvvlojVxxbyYfoaxJ6keLgrTuKdyfEAszFK+H3olzBuafE0yoh0D1gdg=="
     },
     "bytes": {
       "version": "3.1.0",
       "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz",
       "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg=="
@@ -628,13 +650,13 @@
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
       "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk="
     },
     "denque": {
-      "version": "1.4.1",
-      "resolved": "https://registry.npmjs.org/denque/-/denque-1.4.1.tgz",
-      "integrity": "sha512-OfzPuSZKGcgr96rf1oODnfjqBFmr1DVoc/TrItj3Ohe0Ah1C5WX5Baquw/9U9KovnQ88EqmJbD66rKYUQYN1tQ=="
+      "version": "1.5.0",
+      "resolved": "https://registry.npmjs.org/denque/-/denque-1.5.0.tgz",
+      "integrity": "sha512-CYiCSgIF1p6EUByQPlGkKnP1M9g0ZV3qMIrqMqZqdwazygIA/YP2vrbcyl1h/WppKJTdl1F85cXIle+394iDAQ=="
     },
     "depd": {
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
       "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak="
@@ -1345,13 +1367,13 @@
         "json-schema": "0.2.3",
         "verror": "1.10.0"
       }
     },
     "kareem": {
-      "version": "2.3.1",
-      "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.3.1.tgz",
-      "integrity": "sha512-l3hLhffs9zqoDe8zjmb/mAN4B8VT3L56EUvKNqLFVs9YlFA+zx7ke1DO8STAdDyYNkeSo1nKmjuvQeI12So8Xw=="
+      "version": "2.3.2",
+      "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.3.2.tgz",
+      "integrity": "sha512-STHz9P7X2L4Kwn72fA4rGyqyXdmrMSdxqHx9IXon/FXluXieaFA6KJ2upcHAHxQPQ0LeM/OjLrhFxifHewOALQ=="
     },
     "keygrip": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.1.0.tgz",
       "integrity": "sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ==",
@@ -1736,33 +1758,34 @@
       "requires": {
         "moment": ">= 2.9.0"
       }
     },
     "mongodb": {
-      "version": "3.6.2",
-      "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.6.2.tgz",
-      "integrity": "sha512-sSZOb04w3HcnrrXC82NEh/YGCmBuRgR+C1hZgmmv4L6dBz4BkRse6Y8/q/neXer9i95fKUBbFi4KgeceXmbsOA==",
+      "version": "3.6.5",
+      "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.6.5.tgz",
+      "integrity": "sha512-mQlYKw1iGbvJJejcPuyTaytq0xxlYbIoVDm2FODR+OHxyEiMR021vc32bTvamgBjCswsD54XIRwhg3yBaWqJjg==",
       "requires": {
         "bl": "^2.2.1",
         "bson": "^1.1.4",
         "denque": "^1.4.1",
         "require_optional": "^1.0.1",
         "safe-buffer": "^5.1.2",
         "saslprep": "^1.0.0"
       }
     },
     "mongoose": {
-      "version": "5.10.6",
-      "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-5.10.6.tgz",
-      "integrity": "sha512-p32oja2ydzFMS0K4P7G408Kagju6Qz/LmiAFAcP6BqnIi1McLTd6VZ3+ZQtQmaHM7w8iNupLLambCrsPKNJpYw==",
+      "version": "5.12.2",
+      "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-5.12.2.tgz",
+      "integrity": "sha512-kT9t6Nvu9WPsfssn7Gzke446Il8UdMilY7Sa5vALtwoOoNOGtZEVjekZBFwsBFzTWtBA/x5gBmJoYFP+1LeDlg==",
       "requires": {
+        "@types/mongodb": "^3.5.27",
         "bson": "^1.1.4",
-        "kareem": "2.3.1",
-        "mongodb": "3.6.2",
+        "kareem": "2.3.2",
+        "mongodb": "3.6.5",
         "mongoose-legacy-pluralize": "1.0.2",
-        "mpath": "0.7.0",
-        "mquery": "3.2.2",
+        "mpath": "0.8.3",
+        "mquery": "3.2.4",
         "ms": "2.1.2",
         "regexp-clone": "1.0.0",
         "safe-buffer": "5.2.1",
         "sift": "7.0.1",
         "sliced": "1.0.1"
@@ -1784,18 +1807,18 @@
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/mongoose-legacy-pluralize/-/mongoose-legacy-pluralize-1.0.2.tgz",
       "integrity": "sha512-Yo/7qQU4/EyIS8YDFSeenIvXxZN+ld7YdV9LqFVQJzTLye8unujAWPZ4NWKfFA+RNjh+wvTWKY9Z3E5XM6ZZiQ=="
     },
     "mpath": {
-      "version": "0.7.0",
-      "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.7.0.tgz",
-      "integrity": "sha512-Aiq04hILxhz1L+f7sjGyn7IxYzWm1zLNNXcfhDtx04kZ2Gk7uvFdgZ8ts1cWa/6d0TQmag2yR8zSGZUmp0tFNg=="
+      "version": "0.8.3",
+      "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.8.3.tgz",
+      "integrity": "sha512-eb9rRvhDltXVNL6Fxd2zM9D4vKBxjVVQNLNijlj7uoXUy19zNDsIif5zR+pWmPCWNKwAtqyo4JveQm4nfD5+eA=="
     },
     "mquery": {
-      "version": "3.2.2",
-      "resolved": "https://registry.npmjs.org/mquery/-/mquery-3.2.2.tgz",
-      "integrity": "sha512-XB52992COp0KP230I3qloVUbkLUxJIu328HBP2t2EsxSFtf4W1HPSOBWOXf1bqxK4Xbb66lfMJ+Bpfd9/yZE1Q==",
+      "version": "3.2.4",
+      "resolved": "https://registry.npmjs.org/mquery/-/mquery-3.2.4.tgz",
+      "integrity": "sha512-uOLpp7iRX0BV1Uu6YpsqJ5b42LwYnmu0WeF/f8qgD/On3g0XDaQM6pfn0m6UxO6SM8DioZ9Bk6xxbWIGHm2zHg==",
       "requires": {
         "bluebird": "3.5.1",
         "debug": "3.1.0",
         "regexp-clone": "^1.0.0",
         "safe-buffer": "5.1.2",
diff --git a/package.json b/package.json
index 8c56975..24fc7f9 100644
--- a/package.json
+++ b/package.json
@@ -1,8 +1,8 @@
 {
   "name": "oils",
-  "version": "7.8.6",
+  "version": "8.0.0",
   "description": "A slightly opinionated web framework built on top of Express 4.",
   "keywords": [
     "OilsJs",
     "Oils",
     "MVC",
@@ -39,11 +39,11 @@
     "jsdom": "^16.2.2",
     "marked": "^0.8.2",
     "method-override": "~3.0.0",
     "moment": "~2.24.0",
     "moment-timezone": "^0.5.27",
-    "mongoose": "^5.10.6",
+    "mongoose": "^5.12.2",
     "nanoid": "^3.1.20",
     "nomnom": "1.8.x",
     "nunjucks": "^3.2.1",
     "simple-mime": "0.1.x",
     "winston": "~3.2.1",

Vulnerable code:   "version": "7.8.6",
      "version": "1.1.5",
      "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.5.tgz",
      "integrity": "sha512-kDuEzldR21lHciPQAIulLs1LZlCXdLziXI6Mb/TDkwXhb//UORJNPXgcRs2CuO4H0DcMkpfT3/ySsP3unoZjBg=="
      "version": "1.4.1",
      "resolved": "https://registry.npmjs.org/denque/-/denque-1.4.1.tgz",
      "integrity": "sha512-OfzPuSZKGcgr96rf1oODnfjqBFmr1DVoc/TrItj3Ohe0Ah1C5WX5Baquw/9U9KovnQ88EqmJbD66rKYUQYN1tQ=="
      "version": "2.3.1",
      "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.3.1.tgz",
      "integrity": "sha512-l3hLhffs9zqoDe8zjmb/mAN4B8VT3L56EUvKNqLFVs9YlFA+zx7ke1DO8STAdDyYNkeSo1nKmjuvQeI12So8Xw=="
      "version": "3.6.2",
      "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.6.2.tgz",
      "integrity": "sha512-sSZOb04w3HcnrrXC82NEh/YGCmBuRgR+C1hZgmmv4L6dBz4BkRse6Y8/q/neXer9i95fKUBbFi4KgeceXmbsOA==",
      "version": "5.10.6",
      "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-5.10.6.tgz",
      "integrity": "sha512-p32oja2ydzFMS0K4P7G408Kagju6Qz/LmiAFAcP6BqnIi1McLTd6VZ3+ZQtQmaHM7w8iNupLLambCrsPKNJpYw==",
        "kareem": "2.3.1",
        "mongodb": "3.6.2",
        "mpath": "0.7.0",
        "mquery": "3.2.2",
      "version": "0.7.0",
      "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.7.0.tgz",
      "integrity": "sha512-Aiq04hILxhz1L+f7sjGyn7IxYzWm1zLNNXcfhDtx04kZ2Gk7uvFdgZ8ts1cWa/6d0TQmag2yR8zSGZUmp0tFNg=="
      "version": "3.2.2",
      "resolved": "https://registry.npmjs.org/mquery/-/mquery-3.2.2.tgz",
      "integrity": "sha512-XB52992COp0KP230I3qloVUbkLUxJIu328HBP2t2EsxSFtf4W1HPSOBWOXf1bqxK4Xbb66lfMJ+Bpfd9/yZE1Q==",
  "version": "7.8.6",
    "mongoose": "^5.10.6",
Fixed code: 
    fixOpenRedirect(this);

function fixOpenRedirect(web) {
  // Fix for open redirect security
  let redirectSafe = web.app.response.redirect;
  web.app.response.redirectSafe = function(url) {
    return redirectSafe.call(this, url);
  }

  var addHostOnceFlag = true;

  web.app.response.redirect = function(url) {
  
    if (url.indexOf('://') != -1) {

      let req = this.req;

      if (addHostOnceFlag) {
        var host = req.protocol + '://' + req.headers.host;
        web.conf.allowedRedirectHosts.push(host);
        addHostOnceFlag = false;
        console.log("Added host once: " + host);
      }

      const found = web.conf.allowedRedirectHosts.find(el => url.indexOf(el) == 0);

      if (!found) {
        var ip = web.utils.getClientIp(req);

        console.warn("Open redirect was triggered: ", req.method, req.user ? req.user.email : "unsigned user", ip, "accessed", req.url, req.headers['user-agent']);
        throw new Error("Action not allowed.");
      }

    }
    return redirectSafe.call(this, url);
  }
}

    allowedRedirectHosts: [],
  "version": "8.0.0",
    "@types/bson": {
      "version": "4.0.3",
      "resolved": "https://registry.npmjs.org/@types/bson/-/bson-4.0.3.tgz",
      "integrity": "sha512-mVRvYnTOZJz3ccpxhr3wgxVmSeiYinW+zlzQz3SXWaJmD1DuL05Jeq7nKw3SnbKmbleW5qrLG5vdyWe/A9sXhw==",
      "requires": {
        "@types/node": "*"
      }
    },
    "@types/mongodb": {
      "version": "3.6.10",
      "resolved": "https://registry.npmjs.org/@types/mongodb/-/mongodb-3.6.10.tgz",
      "integrity": "sha512-BkwAHFiZSSWdTIqbUVGmgvIsiXXjqAketeK7Izy7oSs6G3N8Bn993tK9eq6QEovQDx6OQ2FGP2KWDDxBzdlJ6Q==",
      "requires": {
        "@types/bson": "*",
        "@types/node": "*"
      }
    },
    "@types/node": {
      "version": "14.14.35",
      "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.35.tgz",
      "integrity": "sha512-Lt+wj8NVPx0zUmUwumiVXapmaLUcAk3yPuHCFVXras9k5VT9TdhJqKqGVUQCD60OTMCl0qxJ57OiTL0Mic3Iag=="
    },
      "version": "1.1.6",
      "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.6.tgz",
      "integrity": "sha512-EvVNVeGo4tHxwi8L6bPj3y3itEvStdwvvlojVxxbyYfoaxJ6keLgrTuKdyfEAszFK+H3olzBuafE0yoh0D1gdg=="
      "version": "1.5.0",
      "resolved": "https://registry.npmjs.org/denque/-/denque-1.5.0.tgz",
      "integrity": "sha512-CYiCSgIF1p6EUByQPlGkKnP1M9g0ZV3qMIrqMqZqdwazygIA/YP2vrbcyl1h/WppKJTdl1F85cXIle+394iDAQ=="
      "version": "2.3.2",
      "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.3.2.tgz",
      "integrity": "sha512-STHz9P7X2L4Kwn72fA4rGyqyXdmrMSdxqHx9IXon/FXluXieaFA6KJ2upcHAHxQPQ0LeM/OjLrhFxifHewOALQ=="
      "version": "3.6.5",
      "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.6.5.tgz",
      "integrity": "sha512-mQlYKw1iGbvJJejcPuyTaytq0xxlYbIoVDm2FODR+OHxyEiMR021vc32bTvamgBjCswsD54XIRwhg3yBaWqJjg==",
      "version": "5.12.2",
      "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-5.12.2.tgz",
      "integrity": "sha512-kT9t6Nvu9WPsfssn7Gzke446Il8UdMilY7Sa5vALtwoOoNOGtZEVjekZBFwsBFzTWtBA/x5gBmJoYFP+1LeDlg==",
        "@types/mongodb": "^3.5.27",
        "kareem": "2.3.2",
        "mongodb": "3.6.5",
        "mpath": "0.8.3",
        "mquery": "3.2.4",
      "version": "0.8.3",
      "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.8.3.tgz",
      "integrity": "sha512-eb9rRvhDltXVNL6Fxd2zM9D4vKBxjVVQNLNijlj7uoXUy19zNDsIif5zR+pWmPCWNKwAtqyo4JveQm4nfD5+eA=="
      "version": "3.2.4",
      "resolved": "https://registry.npmjs.org/mquery/-/mquery-3.2.4.tgz",
      "integrity": "sha512-uOLpp7iRX0BV1Uu6YpsqJ5b42LwYnmu0WeF/f8qgD/On3g0XDaQM6pfn0m6UxO6SM8DioZ9Bk6xxbWIGHm2zHg==",
  "version": "8.0.0",
    "mongoose": "^5.12.2",