CVE-2021-3837 (JavaScript, published 2022-01-03T15:15Z)
Description: value: openwhyd is vulnerable to Improper Authorization
Severity: MEDIUM (CVSS v3 base score: 6.1)
Weakness: NVD-CWE-Other \u2014 Other
NVD is only using a subset of CWE for mapping instead of the entire CWE, and the weakness type is not covered by that subset.
Repository: https://github.com/openwhyd/openwhyd
Files changed:
app/controllers/api/login.js
app/models/logging.js
app/snip.js
test/api/security.api.tests.js
Commit message: fix(security): Make login redirects more secure (#519)

Addresses:
- [Improper Authorization vulnerability found in openwhyd](https://huntr.dev/bounties/d66f90d6-1b5f-440d-8be6-cdffc9d4587e/)
- [Open Redirect vulnerability found in openwhyd](https://huntr.dev/bounties/8852bb22-7312-4af5-b9c2-507a68e37a6a/)
- [Cross-site Scripting (XSS) - Reflected vulnerability found in openwhyd](https://huntr.dev/bounties/84a14deb-5dd8-4cc0-810f-d0987566f845/)

Tests:
* test: /login should allow redirect to /stream
* test: /login should NOT allow javascript in redirect URL
* test: /login should should NOT allow redirect to other domain
* test: /login should NOT allow script element in redirect URL


Vulnerable code:
        if (!dbUser) {
  const fullURL = new URL(url, config.urlPrefix);
  if (`${fullURL.protocol}//${fullURL.host}` !== config.urlPrefix)
    return this.forbidden();
  describe('Open Redirect', () => {

Fixed code:
const snip = require('../../snip.js');
const config = require('../../models/config.js');
        if (
          form.redirect &&
          snip.getSafeOpenwhydURL(form.redirect, config.urlPrefix) === false
        ) {
          form.error = 'Unsafe redirect target';
        } else if (!dbUser) {
  const safeURL = snip.getSafeOpenwhydURL(url, config.urlPrefix);
  if (safeURL === false) return this.forbidden();
exports.getSafeOpenwhydURL = function (url, safeUrlPrefix) {
  if (typeof url !== 'string' || url.includes('<script')) return false;
  const fullURL = new URL(url, safeUrlPrefix);
  if (`${fullURL.protocol}//${fullURL.host}` !== safeUrlPrefix) return false;
  else return fullURL;
};

  describe('Open Redirect from /login', () => {
    it('should allow redirect to /stream', async () => {
      const target = `/stream`;
      const { response } = await postRaw(null, `/login`, {
        action: 'login',
        email: ADMIN_USER.email,
        md5: ADMIN_USER.md5,
        redirect: target,
      });
      assert(
        response.body.includes(`window.location.href="${target}"`) === true,
        `page body should include redirect to ${target}`
      );
    });

    it('should NOT allow redirect to other domain', async () => {
      const target = `https://google.com`;
      const { response } = await postRaw(null, `/login`, {
        action: 'login',
        email: ADMIN_USER.email,
        md5: ADMIN_USER.md5,
        redirect: target,
      });
      assert(
        response.body.includes(`window.location.href="${target}"`) === false,
        `page body should NOT include redirect to ${target}`
      );
    });

    it('should NOT allow javascript in redirect URL', async () => {
      const target = `javascript:alert()`;
      const { response } = await postRaw(null, `/login`, {
        action: 'login',
        email: ADMIN_USER.email,
        md5: ADMIN_USER.md5,
        redirect: target,
      });
      assert(
        response.body.includes(`window.location.href="${target}"`) === false,
        `page body should NOT include redirect to ${target}`
      );
    });

    it('should NOT allow script element in redirect URL', async () => {
      const target = `<script>alert(document.cookie)</script>`;
      const { response } = await postRaw(null, `/login`, {
        action: 'login',
        email: ADMIN_USER.email,
        md5: ADMIN_USER.md5,
        redirect: target,
      });
      assert(
        response.body.includes(`window.location.href="${target}"`) === false,
        `page body should NOT include redirect to ${target}`
      );
    });
  });

  describe('Open Redirect from /consent', () => {