CVE-2026-48865

LearnPress – WordPress LMS Plugin for Create and Sell Online Courses <= 4.3.6 - Reflected Cross-Site Scripting

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

Description

The LearnPress – WordPress LMS Plugin for Create and Sell Online Courses plugin for WordPress is vulnerable to Reflected Cross-Site Scripting in versions up to, and including, 4.3.6 due to insufficient input sanitization and output escaping. This makes it possible for unauthenticated attackers to inject arbitrary web scripts in pages that execute if they can successfully trick a user into performing an action such as clicking on a link.

CVSS Vector Breakdown

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

Technical Details

Affected versions<=4.3.6
PublishedJune 1, 2026
Last updatedJune 1, 2026
Affected pluginlearnpress

What Changed in the Fix

Changes introduced in v4.3.7

Loading patch diff...

Source Code

WordPress.org SVN
Research Plan
Unverified

This exploitation research plan targets **CVE-2026-48865**, a reflected Cross-Site Scripting (XSS) vulnerability in the LearnPress plugin for WordPress. --- # Exploitation Research Plan: CVE-2026-48865 (LearnPress Reflected XSS) ## 1. Vulnerability Summary The LearnPress plugin (<= 4.3.6) fails t…

Show full research plan

This exploitation research plan targets CVE-2026-48865, a reflected Cross-Site Scripting (XSS) vulnerability in the LearnPress plugin for WordPress.


Exploitation Research Plan: CVE-2026-48865 (LearnPress Reflected XSS)

1. Vulnerability Summary

The LearnPress plugin (<= 4.3.6) fails to adequately sanitize and escape user-supplied input before reflecting it on the page. Specifically, parameters used in course searching, filtering, or pagination on the course archive page are echoed back into the HTML document. This allows an unauthenticated attacker to execute arbitrary JavaScript in the context of the victim's browser session.

2. Attack Vector Analysis

  • Vulnerable Endpoint: Course Archive Page (typically /courses/ or ?post_type=lp_course).
  • Vulnerable Parameter: lp-s (Search parameter) or orderby (Sorting parameter).
  • Authentication: Unauthenticated (None).
  • Preconditions: The plugin must be active. For the payload to be most effective, a course archive page should exist.

3. Code Flow (Inferred)

  1. Entry Point: A user navigates to a URL containing the lp-s parameter (e.g., example.com/courses/?lp-s=payload).
  2. Processing: The plugin's core logic (likely in inc/lp-template-functions.php or inc/class-lp-query.php) retrieves the search string from $_GET['lp-s'].
  3. Reflection: The search term is passed to the archive template (e.g., templates/archive-course.php) to display a "Search results for: [term]" message.
  4. Sink: The value is echoed directly or via a wrapper like learn_press_get_template() without passing through esc_html() or esc_attr(). Based on the provided CSS, the output likely resides within a .learn-press-message or search box container.

4. Nonce Acquisition Strategy

Reflected XSS in a standard GET request typically does not require a nonce, as the vulnerability exists in the initial page render. However, if the reflection occurs via the LearnPress AJAX search (identified by lp-tom-select in the CSS), a nonce might be required.

Strategy for AJAX Nonce (if needed):

  1. Identify Trigger: The AJAX search is often triggered on pages containing the [learn_press_recent_courses] shortcode or the default course archive.
  2. Setup: Create a page with the shortcode:
    wp post create --post_type=page --post_title="Courses" --post_status=publish --post_content='[learn_press_recent_courses]'
    
  3. Extraction: Navigate to the page and extract the nonce from the lpGlobalSettings object:
    • JS Variable: window.lpGlobalSettings
    • Nonce Key: nonce
    • Command: browser_eval("window.lpGlobalSettings?.nonce")

5. Exploitation Strategy

We will test reflection in the search parameter on the main course archive.

  • Endpoint: / (WordPress Root)
  • Method: GET
  • Payload: "><script>alert(document.domain)</script>
  • Target URL: http://localhost:8080/?post_type=lp_course&lp-s=%22%3E%3Cscript%3Ealert(document.domain)%3C/script%3E

Step-by-Step Plan:

  1. Discovery: Find the course archive URL.
  2. Request: Use http_request to send the payload.
  3. Analysis: Check the response body for the unescaped script tag.
  4. DOM Verification: Use browser_navigate to confirm the script executes (triggering a dialog or modifying a DOM property).

6. Test Data Setup

  1. Install Plugin: Ensure LearnPress 4.3.6 is installed.
  2. Create Content: Create at least one course to ensure the archive page renders correctly.
    wp post create --post_type=lp_course --post_title="Security Testing 101" --post_status=publish
    
  3. Permalinks: Flush rewrite rules to ensure the /courses/ slug works if configured.
    wp rewrite structure '/%postname%/' --hard
    wp rewrite flush --hard
    

7. Expected Results

  • The HTTP response should contain the literal string: value=""><script>alert(document.domain)</script>" or Results for: "><script>....
  • If testing via a browser, the alert dialog should appear.
  • The script should not be neutralized as &lt;script&gt;.

8. Verification Steps

After the exploit attempt, verify the reflection point in the page source:

# Using http_request and searching for the canary
# If the payload is <script>alert(1337)</script>
grep "alert(1337)" response_body.txt

9. Alternative Approaches

If lp-s is sanitized, try these alternative parameters:

  • orderby: ?post_type=lp_course&orderby=price"><script>alert(1)</script>
  • paged: ?post_type=lp_course&paged=1<script>alert(1)</script>
  • AJAX Reflection: Use http_request to call admin-ajax.php with action=lp_course_search and the payload in the term or s parameter, checking for unescaped JSON output.
Research Findings
Static analysis — not yet PoC-verified

Summary

The LearnPress plugin for WordPress is vulnerable to Reflected Cross-Site Scripting (XSS) in versions up to and including 4.3.6. This is due to the plugin failing to sufficiently sanitize and escape user-supplied input in parameters such as 'lp-s' and 'orderby' before echoing them back on the course archive page, allowing attackers to execute arbitrary JavaScript in a victim's browser.

Security Fix

--- /home/deploy/wp-safety.org/data/plugin-versions/learnpress/4.3.6/assets/css/admin/admin.css	2026-05-03 17:09:04.000000000 +0000
+++ /home/deploy/wp-safety.org/data/plugin-versions/learnpress/4.3.7/assets/css/admin/admin.css	2026-05-23 18:20:54.000000000 +0000
@@ -695,7 +695,7 @@
 
 @font-face {
   font-family: "lp-icon";
-  src: url("../../src/css/vendor/fonts/lp-icon/lp-icon.woff2?v=166") format("woff2");
+  src: url("../../src/css/vendor/fonts/lp-icon/lp-icon.woff2?v=190") format("woff2");
   font-weight: normal;
   font-style: normal;
   font-display: swap;
@@ -1946,11 +1946,8 @@
   margin-right: 5px;
   font-variant: normal;
   text-transform: none;
-  line-height: 1;
   -webkit-font-smoothing: antialiased;
   -moz-osx-font-smoothing: grayscale;
-  vertical-align: middle;
-  margin-top: -2px;
 }
 
 #wpfooter {
@@ -4406,11 +4403,11 @@
   background: #eee;
 }
 .post-type-lp_order #order_details .order-data-date .order-date {
-  width: 120px;
+  width: 130px;
 }
 .post-type-lp_order #order_details .order-data-date .order-hour,
 .post-type-lp_order #order_details .order-data-date .order-minute {
-  width: 50px;
+  width: 60px;
 }
 .post-type-lp_order #order_details .order-data-note {
   display: inline-block;
@@ -7426,11 +7423,8 @@
   margin-right: 5px;
   font-variant: normal;
   text-transform: none;
-  line-height: 1;
   -webkit-font-smoothing: antialiased;
   -moz-osx-font-smoothing: grayscale;
-  vertical-align: middle;
-  margin-top: -2px;
 }

Exploit Outline

An unauthenticated attacker can exploit this vulnerability by crafting a URL targeting the course archive page (typically located at /courses/ or reachable via ?post_type=lp_course). The attacker injects a malicious JavaScript payload into the 'lp-s' (search) or 'orderby' (sorting) GET parameters, such as: ?lp-s="><script>alert(document.domain)</script>. When a victim visits this URL, the search term is reflected back into the HTML without escaping, causing the browser to execute the arbitrary script in the context of the victim's session.

Check if your site is affected.

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