Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 35 additions & 28 deletions includes/class-receiver.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,27 +68,30 @@ public static function register_meta() {
);
}
$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 );

Expand All @@ -102,10 +105,11 @@ public static function register_meta() {

// 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 );

Expand All @@ -120,26 +124,29 @@ public static function register_meta() {

// 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 );
}
Expand Down
8 changes: 7 additions & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
- Tags: webmention, pingback, trackback, linkback, indieweb
- Requires at least: 6.2
- Tested up to: 7.0
- Stable tag: 5.8.0
- Stable tag: 5.8.1
- Requires PHP: 7.4
- License: MIT
- License URI: https://opensource.org/licenses/MIT
Expand Down Expand Up @@ -101,6 +101,12 @@ While not all display options can be settings, we are looking to provide some si

Project and support maintained on github at [pfefferle/wordpress-webmention](https://github.com/pfefferle/wordpress-webmention).

### 5.8.1

* Security: Fix a stored XSS reachable from unauthenticated Webmentions where parser-derived author metadata (avatar, URL) was rendered unescaped in the comment edit metabox
* Security: Sanitize Webmention comment meta on store via `register_meta` sanitize callbacks (`esc_url_raw` for URL meta, `sanitize_key`/`sanitize_text_field` for the rest) to keep the database clean
* Security: Escape remaining template output (comment edit metabox, single-comment view, endpoint message and form, reaction headings)

### 5.8.0

* Bump minimum required PHP version to 7.4
Expand Down
2 changes: 1 addition & 1 deletion templates/api-message.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion templates/comment-form.php
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
4 changes: 2 additions & 2 deletions templates/comment.php
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down Expand Up @@ -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>
Expand Down
2 changes: 1 addition & 1 deletion templates/comments.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 ) ) {
Expand Down
12 changes: 6 additions & 6 deletions templates/edit-comment-form.php
Original file line number Diff line number Diff line change
@@ -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 } ?>
2 changes: 1 addition & 1 deletion templates/endpoint-form.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 />
Expand Down
83 changes: 83 additions & 0 deletions tests/phpunit/tests/class-test-admin-metabox.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
<?php
/**
* Test the Webmention comment edit metabox.
*
* @package Webmention
*/

use Webmention\WP_Admin\Admin;

/**
* Test the Webmention comment edit metabox for stored XSS regressions.
*/
class Test_Admin_Metabox extends WP_UnitTestCase {
/**
* An attribute-breaking payload as supplied through parsed Webmention metadata.
*
* @var string
*/
const XSS_PAYLOAD = 'https://attacker.invalid/avatar.jpg" /><script>alert(1)</script><input value="';

/**
* Render the comment metabox for a given comment.
*
* @param WP_Comment $comment The comment to render the metabox for.
*
* @return string The captured metabox HTML.
*/
private function render_metabox( $comment ) {
$GLOBALS['comment'] = $comment;
ob_start();
Admin::comment_metabox( $comment );
$output = ob_get_clean();
unset( $GLOBALS['comment'] );

return $output;
}

/**
* The URL meta should be sanitized with esc_url_raw before it is stored.
*/
public function test_url_meta_is_sanitized_on_store() {
$comment_id = self::factory()->comment->create();

foreach ( array( 'avatar', 'url', 'webmention_source_url', 'webmention_target_url' ) as $meta_key ) {
add_comment_meta( $comment_id, $meta_key, self::XSS_PAYLOAD, true );
$stored = get_comment_meta( $comment_id, $meta_key, true );

$this->assertStringNotContainsString( '<script>', $stored, "Stored {$meta_key} must not contain a script tag." );
$this->assertStringNotContainsString( '"', $stored, "Stored {$meta_key} must not contain a double quote." );
$this->assertStringNotContainsString( '<', $stored, "Stored {$meta_key} must not contain an angle bracket." );
}
}

/**
* Even a raw (pre-sanitization) value in the database must be escaped on output.
*/
public function test_metabox_escapes_unsanitized_meta_on_output() {
global $wpdb;

$comment_id = self::factory()->comment->create();
update_comment_meta( $comment_id, 'protocol', 'webmention' );

// Simulate a legacy row written before the sanitize callback existed by
// inserting the raw payload directly, bypassing the sanitize callback.
$wpdb->insert(
$wpdb->commentmeta,
array(
'comment_id' => $comment_id,
'meta_key' => 'avatar',
'meta_value' => self::XSS_PAYLOAD,
)
);
wp_cache_delete( $comment_id, 'comment_meta' );

// Confirm the unescaped payload really is in the database.
$this->assertSame( self::XSS_PAYLOAD, get_comment_meta( $comment_id, 'avatar', true ) );

$output = $this->render_metabox( get_comment( $comment_id ) );

$this->assertStringNotContainsString( '<script>alert(1)</script>', $output, 'The metabox must not emit an unescaped script tag.' );
$this->assertStringNotContainsString( 'value="' . self::XSS_PAYLOAD . '"', $output, 'The raw payload must not break out of the value attribute.' );
}
}
4 changes: 2 additions & 2 deletions webmention.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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__ ) );
Expand Down