LightPress Lightbox <= 2.3.4 - Authenticated (Contributor+) Stored Cross-Site Scripting via 'group' Shortcode Attribute
Description
The LightPress Lightbox plugin for WordPress is vulnerable to Stored Cross-Site Scripting via the `group` attribute in the `[gallery]` shortcode in all versions up to, and including, 2.3.4. This is due to the plugin modifying gallery shortcode output to include the `group` attribute value without proper 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
<=2.3.4What Changed in the Fix
Changes introduced in v2.3.5
Source Code
WordPress.org SVNThis research plan outlines the process for exploiting a Stored Cross-Site Scripting (XSS) vulnerability in the LightPress Lightbox plugin (versions <= 2.3.4). ## 1. Vulnerability Summary The **LightPress Lightbox** plugin is vulnerable to Stored XSS via the `group` attribute of the WordPress `[gal…
Show full research plan
This research plan outlines the process for exploiting a Stored Cross-Site Scripting (XSS) vulnerability in the LightPress Lightbox plugin (versions <= 2.3.4).
1. Vulnerability Summary
The LightPress Lightbox plugin is vulnerable to Stored XSS via the group attribute of the WordPress [gallery] shortcode. The plugin hooks into the post_gallery filter to capture the group attribute and subsequently modifies the HTML output (likely via the_content or by overriding the gallery output) to inject this value into the rel attribute of image links. Because the group value is concatenated into the HTML string without using esc_attr() or similar sanitization, an authenticated attacker with Contributor-level permissions can break out of the HTML attribute and inject arbitrary JavaScript.
2. Attack Vector Analysis
- Target Endpoint: Post/Page creation/editor (
/wp-admin/post-new.phpor REST API/wp/v2/posts). - Vulnerable Component: The
[gallery]shortcode processing logic. - Vulnerable Parameter: The
groupattribute within the shortcode. - Authentication Level: Contributor or higher (any role capable of using shortcodes in posts).
- Preconditions: The plugin must be active. A post containing the malicious shortcode must be published or previewed.
3. Code Flow
- Entry Point: An authenticated user saves or previews a post containing:
[gallery group='"><script>alert(1)</script>']. - Shortcode Handling: When the post is rendered, WordPress triggers the
post_galleryfilter. - Plugin Hook: In
lightboxes/wp-jquery-lightbox/class-wp-jquery-lightbox.php, the constructor registers the filter:add_filter( 'post_gallery', array( $this, 'filter_groups' ), 10, 2 ); - State Storage: The
filter_groupsfunction (orjqlb_filter_groupsin legacy mode) extracts thegroupattribute:// In wp-jquery-lightbox-legacy.php function jqlb_filter_groups($output, $attr) { global $jqlb_group; if(isset($attr['group'])){ $jqlb_group = $attr['group']; // Raw input assigned to global } return $output; } - Sink: The plugin later processes the content, likely in
filter_content(registered at line 91 ofclass-wp-jquery-lightbox.phpon thethe_contenthook). It searches for image links within the gallery and appendsrel="lightbox[GROUP_VALUE]". - XSS Trigger: Because
GROUP_VALUE(thegroupattribute) is not escaped withesc_attr(), the payload"><script>alert(1)</script>closes therelattribute and the<a>tag, allowing the script to execute.
4. Nonce Acquisition Strategy
This vulnerability does not require a plugin-specific nonce for the execution phase, as the XSS is stored and executes when the page is viewed. However, the injection phase (saving the post) requires standard WordPress authentication and nonces.
Strategy for Automated Agent:
- Use the
wp_clitool to create a post directly. This bypasses the need to manually extract CSRF nonces from the WordPress editor UI. - Alternatively, if using the UI via
browser_navigate:- The
_wpnonceand_wp_http_refererare standard in thepost.phpform. - No specialized
wp_localize_scriptnonce is needed because we are not targeting an AJAX endpoint, but rather the shortcode rendering engine.
- The
5. Exploitation Strategy
The exploitation will involve creating a post as a Contributor and then verifying the XSS as an unauthenticated visitor.
- Login as Contributor: Authenticate as a user with the
contributorrole. - Upload Media (Optional but Recommended): Ensure at least one image exists in the media library so the
[gallery]shortcode renders actual<a>tags. - Create Malicious Post: Use
wp_clito create a post containing the payload.- Payload:
[gallery ids="1" group='"><script>alert(window.origin)</script>']
- Payload:
- View Post: Use the
http_requesttool to navigate to the frontend URL of the newly created post. - Verify Payload Execution: Inspect the response body to confirm the presence of the unescaped script tag within the gallery HTML.
6. Test Data Setup
- User: A user with the username
contributor_userand rolecontributor. - Media: At least one attachment. We can use
wp media importto add a placeholder image. - Shortcode:
[gallery ids="ID_HERE" group='"><img src=x onerror=alert(document.domain)>']
7. Expected Results
- The HTTP response for the post's frontend page should contain the string:
rel="lightbox["><img src=x onerror=alert(document.domain)>]" - The browser (if rendered) would trigger the
alert. - The raw HTML will show the
relattribute being prematurely closed by the">characters in our payload.
8. Verification Steps
- Check Post Content:
wp post get <POST_ID> --field=post_content - Verify HTML Output:
Usehttp_requestto fetch the post and grep for the payload:grep -P 'rel="lightbox\["><script' - Confirm Lack of Escaping:
Verify that the"and>characters are NOT converted to"and>.
9. Alternative Approaches
- Legacy Mode: If the plugin is configured to use the legacy file (
JQLB_LEGACYconstant set totrue), target thejqlb_filter_groupsfunction inlightboxes/wp-jquery-lightbox/wp-jquery-lightbox-legacy.php. - Attribute Variations: If
groupis not reflected inrel, check for it indata-lightbox-groupor other custom data attributes the plugin might use to categorize images. - Comments XSS: The plugin also has a
lightbox_commentfilter (line 94 ofclass-wp-jquery-lightbox.php). If this filter uses the samelightbox_grouplogic, XSS might be possible via comment text if the plugin attempts to "lightbox" links within comments.
Summary
The LightPress Lightbox plugin is vulnerable to Stored Cross-Site Scripting via the 'group' attribute in the WordPress [gallery] shortcode. This occurs because the plugin fails to escape the group value before injecting it into the 'rel' attribute of image links, allowing attackers with Contributor-level permissions or higher to execute arbitrary JavaScript in the context of a user's browser.
Vulnerable Code
// File: lightboxes/wp-jquery-lightbox/class-wp-jquery-lightbox.php (around line 395) // Grouping. return str_replace( '<a', '<a rel="lightbox[' . $this->lightbox_group . ']"', $html ); --- // File: lightboxes/wp-jquery-lightbox/wp-jquery-lightbox-legacy.php (around line 329) function jqlb_lightbox_gallery_links($html){ //honors our custom group-attribute of the gallery shortcode. global $jqlb_group; if(!isset($jqlb_group) || $jqlb_group == -1){return $html;} return str_replace('<a','<a rel="lightbox['.$jqlb_group.']"', $html); }
Security Fix
@@ -392,7 +392,7 @@ } // Grouping. - return str_replace( '<a', '<a rel="lightbox[' . $this->lightbox_group . ']"', $html ); + return str_replace( '<a', '<a rel="lightbox[' . esc_attr( $this->lightbox_group ) . ']"', $html ); } /** @@ -326,7 +326,7 @@ function jqlb_lightbox_gallery_links($html){ //honors our custom group-attribute of the gallery shortcode. global $jqlb_group; if(!isset($jqlb_group) || $jqlb_group == -1){return $html;} - return str_replace('<a','<a rel="lightbox['.$jqlb_group.']"', $html); + return str_replace('<a','<a rel="lightbox['.esc_attr($jqlb_group).']"', $html); }
Exploit Outline
1. Authenticate as a user with at least Contributor-level access (capable of creating and editing posts). 2. Create a new post or edit an existing one. 3. Insert a [gallery] shortcode using the 'group' attribute. The payload should use a closing quote and bracket to break out of the HTML attribute. 4. Payload example: `[gallery group='"><script>alert(document.domain)</script>']`. 5. Publish or preview the post. 6. When the gallery is rendered on the frontend, the plugin's `post_gallery` filter captures the group value and its content filter injects it directly into the `<a>` tag's `rel` attribute. The payload executes because the value is not passed through `esc_attr()`.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.