CVE-2026-9013

Bogo <= 3.9.1 - Missing Authorization to Authenticated (Subscriber+) Sensitive Information Exposure via REST API

mediumMissing Authorization
4.3
CVSS Score
4.3
CVSS Score
medium
Severity
3.9.2
Patched in
1d
Time to patch

Description

The Bogo plugin for WordPress is vulnerable to Sensitive Information Exposure in all versions up to, and including, 3.9.1 via the bogo_rest_create_post_translation. This makes it possible for authenticated attackers, with subscriber-level access and above, to extract the raw title, content, excerpt, and password of any private, draft, or password-protected post by triggering its duplication via the translation endpoint and reading the returned title.raw, content.raw, and excerpt.raw fields of the duplicated post. This vulnerability is exploitable against posts written in a non-default locale, as authenticated subscribers can request a translation into the site's default locale to pass the locale-only permission gate. While subscribers can trigger the endpoint, this is only impactful at the Contributor-level as they can actually read the duplicated content.

CVSS Vector Breakdown

CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N
Attack Vector
Network
Attack Complexity
Low
Privileges Required
Low
User Interaction
None
Scope
Unchanged
Low
Confidentiality
None
Integrity
None
Availability

Technical Details

Affected versions<=3.9.1
PublishedJune 18, 2026
Last updatedJune 19, 2026
Affected pluginbogo

What Changed in the Fix

Changes introduced in v3.9.2

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# Exploitation Research Plan: CVE-2026-9013 (Bogo Sensitive Information Exposure) ## 1. Vulnerability Summary The Bogo plugin for WordPress is vulnerable to **Sensitive Information Exposure** in versions up to 3.9.1. The vulnerability exists within the REST API endpoint used to create post translat…

Show full research plan

Exploitation Research Plan: CVE-2026-9013 (Bogo Sensitive Information Exposure)

1. Vulnerability Summary

The Bogo plugin for WordPress is vulnerable to Sensitive Information Exposure in versions up to 3.9.1. The vulnerability exists within the REST API endpoint used to create post translations (bogo_rest_create_post_translation).

The plugin fails to perform adequate authorization checks on the source post being translated. Specifically, the permission_callback for the translation creation route only verifies if the current user has access to the target locale (bogo_access_locale), but does not verify if the user has permission to read or edit the source post. An authenticated attacker with Subscriber-level permissions can trigger a translation of a private, draft, or password-protected post into a locale they have access to (such as the site's default locale), and the REST API response will return the raw content of the newly created (duplicated) post, effectively leaking the original sensitive content.

2. Attack Vector Analysis

  • Endpoint: /wp-json/bogo/v1/posts/(?P<id>\d+)/translations/(?P<locale>[a-z]{2,3}...)
  • Method: POST
  • Vulnerable Function: bogo_rest_create_post_translation in includes/rest-api.php.
  • Authentication Required: Authenticated (Subscriber or higher).
  • Preconditions:
    • The target post must be of a "localizable" post type (standard posts/pages).
    • The target post must be in a non-default locale (to bypass simple locale gates if the subscriber only has access to the default locale).
    • The attacker needs a valid wp_rest nonce for the authenticated session.

3. Code Flow

  1. Entry Point: An authenticated user sends a POST request to /wp-json/bogo/v1/posts/[ID]/translations/[LOCALE].
  2. Route Registration (includes/rest-api.php):
    • The route calls the permission_callback which checks current_user_can( 'bogo_access_locale', $locale ).
    • Subscribers typically have access to the site's default locale.
  3. Vulnerable Callback (bogo_rest_create_post_translation):
    • $post_id is retrieved from the request parameters.
    • $post = get_post( $post_id ) retrieves the source post.
    • The Bug: The function proceeds to duplicate the post using bogo_duplicate_post( $post, $locale ) without checking if the user has read_post or edit_post capabilities for the source $post_id.
  4. Information Leak:
    • After duplication, the function constructs a response array:
      $response[$locale] = array(
          // ...
          'title' => array( 'rendered' => '', 'raw' => $new_post->post_title ),
          'content' => array( 'rendered' => '', 'raw' => $new_post->post_content ),
          'excerpt' => array( 'rendered' => '', 'raw' => $new_post->post_excerpt ),
      );
      
    • Because $new_post is a duplicate of the private source post, content.raw contains the full, sensitive text of the original post.

4. Nonce Acquisition Strategy

The Bogo REST API requires a standard WordPress REST API nonce (wp_rest action).

  1. Identify Trigger: Bogo enqueues its admin scripts on post editing screens.
  2. Creation of Environment:
    • The agent should log in as a Subscriber.
    • Navigate to the WordPress Dashboard (even if limited, Subscribers can access wp-admin/profile.php).
  3. Extraction:
    • In the browser context, WordPress typically exposes the REST nonce via the wpApiSettings object.
    • Action: browser_eval("window.wpApiSettings?.nonce")
  4. Alternative: If wpApiSettings is not available, the nonce is often found in the _wpnonce attribute of the localized script for Bogo (though the JS source provided suggests it uses apiFetch, which relies on the standard wp_rest nonce).

5. Exploitation Strategy

Step 1: Authentication

Authenticate the session as a Subscriber.

Step 2: Identification

Identify a sensitive post ID (e.g., a "Private" post with ID 123 currently in a locale like fr_FR). Identify the site's default locale (e.g., en_US).

Step 3: Trigger Duplication via REST API

Using the http_request tool, send the following request:

  • Method: POST
  • URL: http://[TARGET]/wp-json/bogo/v1/posts/123/translations/en_US
  • Headers:
    • Content-Type: application/json
    • X-WP-Nonce: [EXTRACTED_NONCE]
    • Cookie: [SUBSCRIBER_COOKIES]
  • Body: {} (Parameters are in the URL path).

Step 4: Parse Response

The server will respond with a 200 OK and a JSON object. Locate the en_US key (or whichever locale was requested) and extract:

  • title.raw
  • content.raw

6. Test Data Setup

  1. Administrator Action:
    • Install and activate Bogo.
    • Install a secondary language pack (e.g., French).
    • Create a "Private" post in French:
      wp post create --post_title='Secret French Post' --post_content='This is sensitive internal data.' --post_status='private' --post_author=1
      # Assign French locale (Bogo stores this as a term or meta, verify with bogo_set_post_locale)
      wp eval "bogo_set_post_locale( [POST_ID], 'fr_FR' );"
      
  2. Attacker Account:
    • Create a Subscriber user:
      wp user create attacker attacker@example.com --role=subscriber --user_pass=password123
      

7. Expected Results

  • The REST API call should return a JSON object representing the newly created translation.
  • The content.raw field in the JSON response must match the content of the private post created by the administrator ('This is sensitive internal data.').
  • The attacker should receive this data despite not having permission to view private posts.

8. Verification Steps

  1. Post-Exploit CLI Check:
    • Use wp post list --post_status=any to verify that a new post was actually created in the target locale.
    • Compare the ID returned in the REST response with the ID of the new post in the database.
  2. Compare Content:
    • wp post get [ORIGINAL_ID] --field=post_content
    • Compare with the data captured in the content.raw field of the exploit response.

9. Alternative Approaches

  • Targeting Drafts: If no private posts are available, target draft status posts.
  • Targeting Password-Protected: Target posts where post_password is set. The bogo_duplicate_post function usually copies the content regardless of the password protection status.
  • Locale Hunting: If en_US is forbidden, use wp-json/bogo/v1/languages to list all available locales and iterate through them until one passes the bogo_access_locale check.
Research Findings
Static analysis — not yet PoC-verified

Summary

The Bogo plugin for WordPress is vulnerable to Sensitive Information Exposure via its REST API due to missing authorization checks on the source post during translation creation. Authenticated users with Subscriber-level access can duplicate private, draft, or password-protected posts into a locale they have access to and receive the raw content of those posts in the API response.

Vulnerable Code

// includes/rest-api.php line 40
register_rest_route( 'bogo/v1',
		'/posts/(?P<id>\d+)/translations/(?P<locale>' . $locale_pattern . ')',
		array( 
			'methods' => WP_REST_Server::CREATABLE,
			'callback' => 'bogo_rest_create_post_translation',
			'permission_callback' => static function( WP_REST_Request $request ) {
				$locale = $request->get_param( 'locale' );

				if ( current_user_can( 'bogo_access_locale', $locale ) ) {
					return true;
				} else {
					return new WP_Error( 'bogo_locale_forbidden',
						__( 'You are not allowed to access posts in the requested locale.', 'bogo' ),
						array( 'status' => 403 )
					);
				}
			},
		)
	);

---

// includes/rest-api.php line 186
function bogo_rest_create_post_translation( WP_REST_Request $request ) {
	$post_id = $request->get_param( 'id' );

	$post = get_post( $post_id );

	// ... (missing check to see if user can read $post) ...

	$new_post_id = bogo_duplicate_post( $post, $locale );

	if ( ! $new_post_id ) {
		return new WP_Error( 'bogo_post_duplication_failed',
			__( 'Failed to duplicate a post.', 'bogo' ),
			array( 'status' => 500 )
		);
	}

	$new_post = get_post( $new_post_id );
	$response = array();

	$response[$locale] = array(
		'lang' => array( 'tag' => bogo_language_tag( $locale ) ),
		'id' => $new_post->ID,
		'link' => get_permalink( $new_post->ID ),
		'edit_link' => get_edit_post_link( $new_post->ID, 'raw' ),
		'slug' => $new_post->post_name,
		'type' => $new_post->post_type,
		'date' => mysql_to_rfc3339( $new_post->post_date ),
		'date_gmt' => mysql_to_rfc3339( $new_post->post_date_gmt ),
		'modified' => mysql_to_rfc3339( $new_post->post_modified ),
		'modified_gmt' => mysql_to_rfc3339( $new_post->post_modified_gmt ),
		'guid' => array( 'rendered' => '', 'raw' => $new_post->guid ),
		'title' => array( 'rendered' => '', 'raw' => $new_post->post_title ),
		'content' => array( 'rendered' => '', 'raw' => $new_post->post_content ),
		'excerpt' => array( 'rendered' => '', 'raw' => $new_post->post_excerpt ),
	);

Security Fix

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/bogo/3.9.1/includes/rest-api.php /home/deploy/wp-safety.org/data/plugin-versions/bogo/3.9.2/includes/rest-api.php
--- /home/deploy/wp-safety.org/data/plugin-versions/bogo/3.9.1/includes/rest-api.php	2025-09-21 08:06:34.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/bogo/3.9.2/includes/rest-api.php	2026-06-16 09:20:42.000000000 +0000
@@ -31,14 +31,28 @@
 			'permission_callback' => static function( WP_REST_Request $request ) {
 				$locale = $request->get_param( 'locale' );
 
-				if ( current_user_can( 'bogo_access_locale', $locale ) ) {
-					return true;
-				} else {
+				if ( ! current_user_can( 'bogo_access_locale', $locale ) ) {
 					return new WP_Error( 'bogo_locale_forbidden',
 						__( 'You are not allowed to access posts in the requested locale.', 'bogo' ),
-						array( 'status' => 403 )
+						array( 'status' => rest_authorization_required_code() )
 					);
 				}
+
+				$post_id = $request->get_param( 'id' );
+
+				if ( $post = get_post( $post_id ) ) {
+					if (
+						! $post_type_object = get_post_type_object( $post->post_type ) or
+						! current_user_can( $post_type_object->cap->create_posts )
+					) {
+						return new WP_Error( 'bogo_post_cannot_create',
+							__( 'You are not allowed to create posts as this user.', 'bogo' ),
+							array( 'status' => rest_authorization_required_code() )
+						);
+					}
+				}
+
+				return true;
 			},
 		)
 	);
@@ -222,10 +249,10 @@
 		'date_gmt' => mysql_to_rfc3339( $new_post->post_date_gmt ),
 		'modified' => mysql_to_rfc3339( $new_post->post_modified ),
 		'modified_gmt' => mysql_to_rfc3339( $new_post->post_modified_gmt ),
-		'guid' => array( 'rendered' => '', 'raw' => $new_post->guid ),
-		'title' => array( 'rendered' => '', 'raw' => $new_post->post_title ),
-		'content' => array( 'rendered' => '', 'raw' => $new_post->post_content ),
-		'excerpt' => array( 'rendered' => '', 'raw' => $new_post->post_excerpt ),
+		'guid' => array( 'rendered' => '', 'raw' => '' ),
+		'title' => array( 'rendered' => '', 'raw' => '' ),
+		'content' => array( 'rendered' => '', 'raw' => '' ),
+		'excerpt' => array( 'rendered' => '', 'raw' => '' ),
 	);
 
 	$lang = bogo_get_language( $locale );
@@ -235,16 +262,28 @@
 	if ( ! empty( $new_post->guid ) ) {
 		$response[$locale]['guid']['rendered'] =
 			apply_filters( 'get_the_guid', $new_post->guid );
+
+		if ( current_user_can( $edit_post_cap, $new_post->ID ) ) {
+			$response[$locale]['guid']['raw'] = $new_post->guid;
+		}
 	}
 
 	if ( ! empty( $new_post->post_title ) ) {
 		$response[$locale]['title']['rendered'] =
 			get_the_title( $new_post->ID );
+
+		if ( current_user_can( $edit_post_cap, $new_post->ID ) ) {
+			$response[$locale]['title']['raw'] = $new_post->post_title;
+		}
 	}
 
 	if ( ! empty( $new_post->post_content ) ) {
 		$response[$locale]['content']['rendered'] =
 			apply_filters( 'the_content', $new_post->post_content );
+
+		if ( current_user_can( $edit_post_cap, $new_post->ID ) ) {
+			$response[$locale]['content']['raw'] = $new_post->post_content;
+		}
 	}
 
 	if ( ! empty( $new_post->post_excerpt ) ) {
@@ -252,6 +291,10 @@
 			'the_excerpt',
 			apply_filters( 'get_the_excerpt', $new_post->post_excerpt )
 		);
+
+		if ( current_user_can( $edit_post_cap, $new_post->ID ) ) {
+			$response[$locale]['excerpt']['raw'] = $new_post->post_excerpt;
+		}
 	}

Exploit Outline

The exploit involves an authenticated attacker with Subscriber-level permissions sending a POST request to the `/wp-json/bogo/v1/posts/[ID]/translations/[LOCALE]` endpoint. By targeting a private or draft post ID and requesting a translation into a locale the subscriber has permission to access (such as the site's default locale), the plugin duplicates the post and returns the full `raw` title, content, and excerpt in the JSON response. This allows the attacker to bypass standard WordPress post visibility controls.

Check if your site is affected.

Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.