weDocs <= 2.1.16 - Missing Authorization to Authenticated (Subscriber+) Documentation Post Update
Description
The weDocs: AI Powered Knowledge Base, Docs, Documentation, Wiki & AI Chatbot plugin for WordPress is vulnerable to unauthorized modification or loss of data due to a missing capability check on the 'wedocs_user_documentation_handling_capabilities' function in all versions up to, and including, 2.1.16. This makes it possible for authenticated attackers, with Subscriber-level access and above, to edit any documentation post. The vulnerability was partially patched in version 2.1.16.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:NTechnical Details
What Changed in the Fix
Changes introduced in v2.1.17
Source Code
WordPress.org SVN# Exploitation Research Plan: CVE-2025-13921 weDocs Missing Authorization ## 1. Vulnerability Summary The **weDocs** plugin for WordPress (up to version 2.1.16) contains an authorization vulnerability where documentation-specific capabilities are incorrectly granted to all user roles, including the…
Show full research plan
Exploitation Research Plan: CVE-2025-13921 weDocs Missing Authorization
1. Vulnerability Summary
The weDocs plugin for WordPress (up to version 2.1.16) contains an authorization vulnerability where documentation-specific capabilities are incorrectly granted to all user roles, including the Subscriber role. This occurs in the add_documentation_handling_capabilities function within the upgrade handler. Specifically, the plugin grants edit_others_docs and edit_published_docs to every registered role, effectively allowing any authenticated user to modify documentation posts created by others.
2. Attack Vector Analysis
- Endpoint: WordPress REST API (
/wp-json/wp/v2/docs/<id>) or standard AJAX handlers. - Payload Parameter:
content,title, or any other post field. - Authentication: Required (Subscriber-level or higher).
- Preconditions: The upgrade logic (specifically the code in
includes/Upgrader/Upgrades/V_2_0_2.php) must have executed, which typically happens automatically upon installation or update of the plugin.
3. Code Flow
- Capability Assignment:
- File:
includes/Upgrader/Upgrades/V_2_0_2.php - Function:
add_documentation_handling_capabilities() - Logic:
$roles = $wp_roles->get_names(); // Retrieves ALL roles (subscriber, contributor, etc.) $capabilities = array( 'edit_post', 'edit_docs', 'publish_docs', 'edit_others_docs', 'read_private_docs', 'edit_private_docs', 'edit_published_docs' ); foreach ( $capabilities as $capability ) { foreach ( $roles as $role_key => $role ) { $wp_roles->add_cap( $role_key, $capability ); // BUG: Grants caps to ALL roles } }
- File:
- Accessing the Sink:
- WordPress registers the
docspost type (likely inincludes/Post_Types.php, though file content is not provided). - When a user attempts to edit a
docspost via the REST API or Admin dashboard, WordPress core checkscurrent_user_can( 'edit_post', $post_id ). - Because the Subscriber now has
edit_others_docs, this check passes for any documentation post.
- WordPress registers the
4. Nonce Acquisition Strategy
Since the attacker is an authenticated Subscriber, they can log into the WordPress dashboard (/wp-admin/).
- Identify Script Variable: WordPress naturally exposes the REST API nonce to authenticated users in the
wp-admindashboard via thewpApiSettingsobject. - Strategy:
- Log in as the Subscriber.
- Navigate to
/wp-admin/profile.php. - Use
browser_evalto extract the nonce:browser_eval("window.wpApiSettings?.nonce")
- Alternative (if REST is disabled): Look for the
wedocslocalized JS object for AJAX nonces, often found on documentation pages.
5. Exploitation Strategy
Step 1: Confirm Capability Grant
The upgrade code in V_2_0_2.php runs if the wedocs_version option in the database is less than 2.0.2. To trigger it in a test environment, the version might need to be spoofed or the plugin simply activated.
Step 2: Identify Target Doc
Determine the ID of a docs post created by an Administrator.
Step 3: Perform Unauthorized Update
Use the Subscriber's session and the REST nonce to update the post.
HTTP Request:
POST /wp-json/wp/v2/docs/[DOC_ID] HTTP/1.1
Host: [TARGET_HOST]
Content-Type: application/json
X-WP-Nonce: [EXTRACTED_NONCE]
Cookie: [SUBSCRIBER_COOKIES]
{
"content": "<h1>Hacked by Subscriber</h1>",
"title": "Unauthorized Change"
}
6. Test Data Setup
- Install weDocs 2.1.16.
- Create Content: As an Admin, create a new Documentation post (post type
docs) and publish it. Note its ID (e.g.,123). - Create Attacker: Create a user with the Subscriber role.
- Trigger Upgrade: Visit the Admin dashboard once as Admin to ensure
UpgradeHandler::check()runs and invokesV_2_0_2::handle_upgrade().
7. Expected Results
- The REST API should return a
200 OK(or201 Created) response. - The response body should contain the updated content:
"content": {"rendered": "<h1>Hacked by Subscriber</h1>", ...}. - The Subscriber, who normally lacks permissions to edit posts (especially others' posts), successfully bypasses authorization.
8. Verification Steps
- Check Capabilities via CLI:
wp user cap list [SUBSCRIBER_ID]
Verifyedit_others_docsandedit_published_docsappear in the list. - Verify Post Content:
wp post get [DOC_ID] --field=post_content
Confirm the content matches the payload sent by the Subscriber.
9. Alternative Approaches
- Standard AJAX: If the REST API is restricted, search for AJAX actions. Based on
vendor/composer/autoload_classmap.php,WeDevs\WeDocs\Ajaxexists. Grep forwp_ajax_in the plugin directory to find documentation saving handlers (e.g.,wedocs_save_doc_data). - Classic Editor: Attempt to access
wp-admin/post.php?post=[DOC_ID]&action=editas the Subscriber. If the capabilities were granted, the WordPress UI will allow the Subscriber to view the editor for that post.
Summary
The weDocs plugin incorrectly grants documentation-specific capabilities, such as edit_others_docs and edit_published_docs, to all registered WordPress roles during its version 2.0.2 upgrade process. This allows authenticated users with low-level privileges, like Subscribers, to modify or delete any documentation post via the WordPress REST API or Admin dashboard.
Vulnerable Code
// includes/Upgrader/Upgrades/V_2_0_2.php /** * Add weDocs documentation handling capabilities for users. * * @since 2.0.2 * * @return void */ private function add_documentation_handling_capabilities() { global $wp_roles; if ( class_exists( 'WP_Roles' ) && ! isset( $wp_roles ) ) { $wp_roles = new \WP_Roles(); // @codingStandardsIgnoreLine } $roles = $wp_roles->get_names(); $capabilities = array( 'edit_post', 'edit_docs', 'publish_docs', 'edit_others_docs', 'read_private_docs', 'edit_private_docs', 'edit_published_docs' ); // Push documentation handling access to users. foreach ( $capabilities as $capability ) { foreach ( $roles as $role_key => $role ) { $wp_roles->add_cap( $role_key, $capability ); } } }
Security Fix
@@ -44,8 +44,11 @@ if ( $need_upgrade ) { $this->handle_upgrade(); update_option( 'wedocs_version', $this->version ); - $this->next(); } + + // Always call next() to continue the upgrade chain, + // even if this upgrade didn't need to run + $this->next(); } /** @@ -11,7 +11,10 @@ * * @since 2.0.2 */ - public $class_list = array( '2.0.2' => V_2_0_2::class ); + public $class_list = array( + '2.0.2' => V_2_0_2::class, + '2.1.17' => V_2_1_17::class, + ); /** * Get wedocs installed version number. @@ -91,28 +91,7 @@ * @return void */ private function add_documentation_handling_capabilities() { - global $wp_roles; - - if ( class_exists( 'WP_Roles' ) && ! isset( $wp_roles ) ) { - $wp_roles = new \WP_Roles(); // @codingStandardsIgnoreLine - } - - $roles = $wp_roles->get_names(); - $capabilities = array( - 'edit_post', - 'edit_docs', - 'publish_docs', - 'edit_others_docs', - 'read_private_docs', - 'edit_private_docs', - 'edit_published_docs' - ); - - // Push documentation handling access to users. - foreach ( $capabilities as $capability ) { - foreach ( $roles as $role_key => $role ) { - $wp_roles->add_cap( $role_key, $capability ); - } - } + // Use the centralized function that restricts capabilities to administrator and editor only. + wedocs_user_documentation_handling_capabilities(); } }
Exploit Outline
The exploit methodology involves leveraging the erroneously granted capabilities to a Subscriber-level account. 1. Authentication: The attacker must log into the WordPress site as a user with the Subscriber role. 2. Nonce Acquisition: Once logged in, the attacker can find a valid REST API nonce in the page source of the WordPress dashboard (e.g., via the `wpApiSettings` JavaScript object). 3. Target Identification: The attacker identifies the ID of a documentation post (post type `docs`) that they wish to modify. 4. Endpoint Hit: The attacker sends an authenticated POST request to the WordPress REST API endpoint `/wp-json/wp/v2/docs/<ID>`. 5. Payload: The request body includes updated post content or titles. Because the Subscriber's role contains the `edit_others_docs` and `edit_published_docs` capabilities (granted by the vulnerable upgrade handler), WordPress core permits the update despite the user normally lacking such permissions.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.