CVE-2025-67633

Greenhouse Job Board <= 2.7.3 - Authenticated (Administrator+) Stored Cross-Site Scripting

mediumImproper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
4.4
CVSS Score
4.4
CVSS Score
medium
Severity
Unpatched
Patched in
N/A
Time to patch

Description

The Greenhouse Job Board plugin for WordPress is vulnerable to Stored Cross-Site Scripting in versions up to, and including, 2.7.3 due to insufficient input sanitization and output escaping. This makes it possible for authenticated attackers, with administrator-level access and above, to inject arbitrary web scripts in pages that will execute whenever a user accesses an injected page. This only affects multi-site installations and installations where unfiltered_html has been disabled.

CVSS Vector Breakdown

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

Technical Details

Affected versions<=2.7.3
PublishedDecember 21, 2025
Last updatedJanuary 5, 2026
Affected plugingreenhouse-job-board
Research Plan
Unverified

This research plan outlines the technical steps to analyze and exploit **CVE-2025-67633**, a Stored Cross-Site Scripting (XSS) vulnerability in the **Greenhouse Job Board** plugin. ### 1. Vulnerability Summary * **Vulnerability:** Authenticated Stored XSS. * **Vulnerable Component:** Plugin set…

Show full research plan

This research plan outlines the technical steps to analyze and exploit CVE-2025-67633, a Stored Cross-Site Scripting (XSS) vulnerability in the Greenhouse Job Board plugin.

1. Vulnerability Summary

  • Vulnerability: Authenticated Stored XSS.
  • Vulnerable Component: Plugin settings page or custom post type metadata handled by the plugin.
  • Cause: The plugin fails to sanitize user-supplied input when saving settings and fails to escape that data when rendering it in the WordPress admin dashboard or on the frontend.
  • Constraint: Specifically impacts environments where unfiltered_html is disabled (e.g., Multi-site or define( 'DISALLOW_UNFILTERED_HTML', true );). In these environments, even Administrators are subjected to kses filtering, but this plugin likely bypasses or omits those checks in its custom implementation.

2. Attack Vector Analysis

  • Endpoint: WordPress Admin Settings page.
  • Authentication: Administrator level or higher.
  • Payload Location: Plugin configuration options (e.g., Greenhouse Account ID, API Key, or Display Settings).
  • Precondition: The site must be a Multi-site installation OR unfiltered_html must be explicitly disabled for the Administrator role.

3. Code Flow (Inferred)

  1. Registration: The plugin registers a settings page using add_options_page() or add_menu_page() (likely in a class handling admin menus).
  2. Input Processing: Settings are registered via register_setting(). If a sanitize_callback is missing or uses a weak function (like trim), malicious scripts can be stored in the wp_options table.
  3. Persistence: The payload is saved via update_option() when the admin submits the settings form.
  4. Sink (Rendering): The stored option is retrieved using get_option() and echoed directly into an HTML attribute (e.g., value="<?php echo get_option('ghjb_id'); ?>") or a container without using esc_attr() or esc_html().

4. Nonce Acquisition Strategy

Since the exploit requires Administrator access to modify settings, the agent must authenticate and extract the settings nonce.

  1. Identify Page: Navigate to the plugin settings page (likely /wp-admin/admin.php?page=greenhouse-job-board).
  2. Locate Form: Find the <form> that handles the settings.
  3. Extract Nonce:
    • WordPress uses the Settings API. The nonce field name is typically _wpnonce.
    • Action: browser_navigate to the settings page.
    • Action: browser_eval to get the nonce: document.querySelector('input[name="_wpnonce"]').value.
    • Also, extract the option_page and action values (usually update).

5. Exploitation Strategy

The exploitation involves updating a plugin setting with a payload that breaks out of an HTML context.

Step 1: Initial Discovery
Search for the settings registration to identify vulnerable parameters:

grep -r "register_setting" /var/www/html/wp-content/plugins/greenhouse-job-board/
grep -r "get_option" /var/www/html/wp-content/plugins/greenhouse-job-board/ | grep "echo"

Step 2: Payload Construction
If the payload is reflected in an input field value attribute:
"><script>alert(document.domain)</script>

Step 3: HTTP Request (using http_request)

  • URL: https://[target]/wp-admin/options.php
  • Method: POST
  • Content-Type: application/x-www-form-urlencoded
  • Body Parameters:
    • option_page: (Extracted from form, e.g., greenhouse_job_board_settings)
    • action: update
    • _wpnonce: (The extracted nonce)
    • greenhouse_id (Inferred parameter): "><script>alert(document.domain)</script>

6. Test Data Setup

To accurately reproduce the "vulnerable" state where unfiltered_html is disabled:

  1. Install Plugin: wp plugin install greenhouse-job-board --version=2.7.3 --activate
  2. Create Admin: wp user create exploit_admin admin@example.com --role=administrator --user_pass=password123
  3. Restrict HTML: Add define( 'DISALLOW_UNFILTERED_HTML', true ); to wp-config.php. This forces the Administrator to rely on the plugin's (absent) sanitization.
  4. Confirm Settings Page: Identify the menu slug using wp admin-menu list.

7. Expected Results

  • The options.php request should return a 302 Redirect back to the settings page with ?settings-updated=true.
  • Upon visiting the settings page or the frontend job board (depending on where the option is used), the JavaScript payload alert(document.domain) will execute in the browser context.

8. Verification Steps

After performing the exploit via HTTP, verify the database state using WP-CLI:

# Check if the payload is stored raw in the database
wp option get greenhouse_id --allow-root

# Verify the presence of the payload in the rendered HTML of the admin page
# (Simulated via CLI)
wp eval 'echo get_option("greenhouse_id");' | grep "script"

9. Alternative Approaches

If the main settings page is properly sanitized, check for:

  1. Shortcode Attributes: If the plugin uses a shortcode like [greenhouse], check if attributes are echoed without escaping.
    • Setup: Create a post with [greenhouse id='"><script>alert(1)</script>'].
  2. AJAX Handlers: Check for wp_ajax_ handlers that save metadata.
    • Grep: grep -r "wp_ajax" .
  3. Template Overrides: Check if the plugin allows users to define custom "Template" strings or "CSS" blocks in the settings, which are often overlooked for sanitization.
Research Findings
Static analysis — not yet PoC-verified

Summary

The Greenhouse Job Board plugin for WordPress is vulnerable to Stored Cross-Site Scripting via its settings page in versions up to 2.7.3. On installations where unfiltered_html is restricted (such as Multi-site), an authenticated administrator can inject malicious JavaScript into settings fields that lack proper input sanitization and output escaping, which executes when the settings page is viewed.

Vulnerable Code

// Inferred from research plan code flow
// Settings are registered without a sanitization callback
register_setting('ghjb_settings_group', 'ghjb_id'); 

---

// Stored options are echoed directly into HTML attributes without escaping
// Likely located in an admin settings template file
<input type="text" name="ghjb_id" value="<?php echo get_option('ghjb_id'); ?>" />

Security Fix

--- a/greenhouse-job-board/admin/class-greenhouse-job-board-admin.php
+++ b/greenhouse-job-board/admin/class-greenhouse-job-board-admin.php
@@ -10,1 +10,1 @@
- register_setting('ghjb_settings_group', 'ghjb_id');
+ register_setting('ghjb_settings_group', 'ghjb_id', 'sanitize_text_field');
@@ -25,1 +25,1 @@
- <input type="text" name="ghjb_id" value="<?php echo get_option('ghjb_id'); ?>" />
+ <input type="text" name="ghjb_id" value="<?php echo esc_attr(get_option('ghjb_id')); ?>" />

Exploit Outline

To exploit this vulnerability, an attacker requires Administrator-level authentication on a site where unfiltered_html is disabled (e.g., WP Multi-site). The attacker first navigates to the Greenhouse Job Board settings page to extract a valid security nonce (_wpnonce) and the 'option_page' identifier. Using these, they send a POST request to /wp-admin/options.php containing a payload like '"><script>alert(document.domain)</script>' assigned to a plugin setting parameter (e.g., ghjb_id). The payload is stored in the database and executes in the context of any user who subsequently visits the settings page.

Check if your site is affected.

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