GetGenie <= 4.3.2 - Insecure Direct Object Reference to Authenticated (Author+) Stored Cross-Site Scripting via REST API
Description
The GetGenie plugin for WordPress is vulnerable to Insecure Direct Object Reference in all versions up to, and including, 4.3.2 due to missing validation on a user controlled key in the `action` function. This makes it possible for authenticated attackers, with Author-level access and above, to update post metadata for arbitrary posts. Combined with a lack of input sanitization, this leads to Stored Cross-Site Scripting when a higher-privileged user (such as an Administrator) views the affected post's "Competitor" tab in the GetGenie sidebar.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:L/I:L/A:NTechnical Details
What Changed in the Fix
Changes introduced in v4.3.3
Source Code
WordPress.org SVN# Exploitation Research Plan: CVE-2026-2257 (GetGenie IDOR to Stored XSS) ## 1. Vulnerability Summary The **GetGenie** plugin (<= 4.3.2) contains an Insecure Direct Object Reference (IDOR) vulnerability in its REST API implementation. The `GenieAi\App\Api\Store::action` function handles metadata up…
Show full research plan
Exploitation Research Plan: CVE-2026-2257 (GetGenie IDOR to Stored XSS)
1. Vulnerability Summary
The GetGenie plugin (<= 4.3.2) contains an Insecure Direct Object Reference (IDOR) vulnerability in its REST API implementation. The GenieAi\App\Api\Store::action function handles metadata updates but fails to verify if the authenticated user has permission to edit the specific post identified by the post_id parameter. While it checks for the publish_posts capability (typically held by Authors and above), it does not restrict updates to the user's own posts.
Furthermore, the data stored in the serpData key is not sanitized before being saved via update_post_meta. This allows an attacker to inject malicious scripts into post metadata. When a higher-privileged user (e.g., an Administrator) views the "Competitor" tab in the GetGenie sidebar while editing the affected post, the script executes in their session (Stored XSS).
2. Attack Vector Analysis
- REST Endpoint:
POST /wp-json/getgenie/v1/store/{post_id}/{key} - Vulnerable Parameter:
post_id(URL path),key(URL path), and the Request Body (JSON). - Authentication: Authenticated, Author-level access (
publish_postscapability). - Nonce: Requires a standard WordPress REST API nonce (
wp_rest). - Target Key:
serpData(allowed viagetgenie_blogwizard_store_objects()).
3. Code Flow
- Route Registration:
app/Api/Store.phpregisters the routegetgenie/v1/store/(?P<post_id>[\d]+)/(?P<key>[\w-]+)withpermission_callbackset to__return_true. - Authorization Check: Inside
action($request):- Line 31: Verifies
wp_restnonce. - Line 38: Checks
current_user_can('publish_posts'). This is the only permission check.
- Line 31: Verifies
- IDOR/Sink:
- Line 46: Extracts
$post_iddirectly from the request. - Line 50: Checks if
$keyis in the allowed list returned bygetgenie_blogwizard_store_objects(). - Line 76: Calls
update_post_meta($post_id, $prefix . $key, wp_slash($data))without checking if the current user is the author of$post_idor an administrator.
- Line 46: Extracts
- XSS Trigger: When the GetGenie sidebar is loaded in the Gutenberg editor,
genie_header_script_data()(ingetgenie.php) retrieves the meta:- Line 148: Loops through
getgenie_blogwizard_store_objects(). - Line 151: Fetches meta using
get_post_meta(get_the_ID(), GETGENIE_BLOGWIZARD_PREFIX . $object, true). - The data is localized into the
configJS object and rendered in the React/Vue sidebar without proper output escaping.
- Line 148: Loops through
4. Nonce Acquisition Strategy
The plugin localizes a configuration object containing the REST nonce for the WordPress editor.
- Identify Script Localization: The function
genie_header_script_datacreates a$configarray. - Access Method:
- Create a post as the Author user.
- Navigate to the Edit Post page in the browser.
- The nonce is stored in a global JavaScript variable. Based on line 171 of
getgenie.php, the variable is likely namedgetgenie_configor similar (this is the standard pattern for this plugin).
- Execution Agent Steps:
wp post create --post_type=post --post_status=publish --post_author=author_id --post_title='Nonce Page'- Use
browser_navigateto the edit URL of the new post. browser_eval("window.getgenie_config?.restNonce")to extract thewp_restnonce.
5. Exploitation Strategy
Step 1: Data Preparation
Identify the post_id of a post owned by an Administrator (usually ID=1).
Step 2: Craft Payload
The serpData logic (lines 58-74) expects a JSON structure. To trigger the "Competitor" tab XSS, we target the competitorData array.
Payload:
{
"competitorData": [
{
"url": "http://example.com",
"title": "Malicious Site",
"content": "Test content",
"some_rendered_field": "<img src=x onerror=alert('CVE-2026-2257_XSS')>"
}
]
}
Step 3: Send Attack Request
Perform a POST request to the store endpoint targeting the Admin's post.
- Method:
POST - URL:
/wp-json/getgenie/v1/store/1/serpData(assuming Admin post ID is 1) - Headers:
X-WP-Nonce: [EXTRACTED_NONCE]Content-Type: application/json
- Body: The JSON payload from Step 2.
6. Test Data Setup
- Target User: Create an Administrator user (
admin_user). - Target Post: Create a post as
admin_userwithID=1. - Attacker User: Create a user with the
Authorrole (attacker_author). - Environment: Ensure GetGenie <= 4.3.2 is active.
7. Expected Results
- The REST API should return a
200 OKstatus with a JSON response:{"status": "success", "message": ["Successfully stored"], ...}. - The database entry for
post_id=1with meta keygetgenie_blogwizard_serpDatawill now contain the XSS payload. - When an administrator edits Post 1 and opens the GetGenie sidebar (specifically the Competitor Analysis section), the
alertwill trigger.
8. Verification Steps
After the HTTP request, verify the injection using WP-CLI:
wp post meta get 1 getgenie_blogwizard_serpData
Expected output should contain: <img src=x onerror=alert('CVE-2026-2257_XSS')>
9. Alternative Approaches
If the serpData key is not processed as expected, try other keys in getgenie_blogwizard_store_objects() that are rendered in the UI:
generatedTitlesgeneratedIntrosselectedTitle
If the nonce is not found in window.getgenie_config, check window.wpApiSettings.nonce which is the default WordPress REST nonce location often used as a fallback.
Summary
The GetGenie plugin for WordPress is vulnerable to an Insecure Direct Object Reference (IDOR) via its REST API, allowing authenticated users with Author-level permissions and above to modify metadata for arbitrary posts. Because the plugin also fails to sanitize this user-supplied metadata, attackers can inject malicious scripts that execute in the session of a higher-privileged user (like an Administrator) when they view the affected post's Competitor Analysis tab in the plugin's sidebar, resulting in Stored Cross-Site Scripting (XSS).
Vulnerable Code
// app/Api/Store.php:21 if (!is_user_logged_in() || !current_user_can('publish_posts')) { return [ 'status' => 'fail', 'message' => ['Access denied.'], ]; } $data = $request->get_body(); $post_id = $request['post_id']; --- // app/Api/Store.php:77 update_post_meta($post_id, $prefix . $key, wp_slash($data));
Security Fix
@@ -75,6 +75,15 @@ $conversation_id = wp_insert_post($record); $message = 'Chat created successfully.'; } else { + // Verify the post exists, belongs to current user, and is the correct post type + $post = get_post($conversation_id); + if (!$post || $post->post_type !== 'getgenie_chat' || (int) $post->post_author !== get_current_user_id()) { + return [ + 'status' => 'fail', + 'message' => ['Access denied. You can only update your own chat conversations.'], + ]; + } + $record = array( 'ID' => $conversation_id, 'post_title' => $req->templateSlug . '-' . date('Y-m-d H:i:s'), @@ -29,7 +29,9 @@ ]; } - if (!is_user_logged_in() || !current_user_can('publish_posts')) { + $post_id = $request['post_id']; + + if (!is_user_logged_in() || !current_user_can('edit_post', $post_id)) { return [ 'status' => 'fail', 'message' => ['Access denied.'], @@ -37,8 +39,6 @@ } $data = $request->get_body(); - -$post_id = $request['post_id']; $key = $request['key']; $prefix = GETGENIE_BLOGWIZARD_PREFIX; @@ -71,6 +71,9 @@ } + // Sanitize the data to prevent XSS attacks + $data = wp_kses_post($data); + update_post_meta($post_id, $prefix . $key, wp_slash($data)); return [
Exploit Outline
1. Authenticate to the target WordPress site as a user with at least the 'Author' role. 2. Extract a valid WordPress REST API nonce from the page source of the block editor (typically found in the `getgenie_config` JavaScript object or `wpApiSettings`). 3. Identify the `post_id` of a target post owned by an Administrator. 4. Craft a JSON payload containing a malicious script within the `competitorData` array, such as: `{"competitorData": [{"url": "http://example.com", "title": "XSS", "content": "test", "some_rendered_field": "<img src=x onerror=alert(document.cookie)>"}]}`. 5. Send a POST request to `/wp-json/getgenie/v1/store/{target_post_id}/serpData` with the `X-WP-Nonce` header set to the extracted nonce and the JSON payload in the body. 6. The XSS will trigger when an Administrator edits the target post and opens the GetGenie sidebar's 'Competitor' section.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.