Last active
July 25, 2026 00:46
-
-
Save szepeviktor/a6e665c6303e176d13bc8fd15f2da19c to your computer and use it in GitHub Desktop.
Temporary mitigation for chained RCE in WordPress core: CVE-2026-60137 and CVE-2026-63030
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| /* | |
| * Plugin Name: Fix REST Batch | |
| * Description: Rejects malformed REST batch paths before affected WordPress core versions process them. | |
| */ | |
| declare(strict_types=1); | |
| namespace SzepeViktor\FixRestBatch; | |
| final class REST_Server extends \WP_REST_Server | |
| { | |
| /** | |
| * Reject malformed batch paths before delegating valid requests to WordPress core. | |
| * | |
| * @param \WP_REST_Request $batch_request The batch request. | |
| * @return \WP_REST_Response|\WP_Error | |
| */ | |
| public function serve_batch_request_v1(\WP_REST_Request $batch_request) | |
| { | |
| $requests = $batch_request['requests']; | |
| if (!\is_array($requests)) { | |
| return new \WP_Error( | |
| 'rest_invalid_batch', | |
| \__('The batch requests parameter must be an array.'), | |
| ['status' => 400] | |
| ); | |
| } | |
| foreach ($requests as $args) { | |
| if ( | |
| !\is_array($args) | |
| || !isset($args['path']) | |
| || !\is_string($args['path']) | |
| || false === \wp_parse_url($args['path']) | |
| ) { | |
| return new \WP_Error( | |
| 'parse_path_failed', | |
| \__('Could not parse the path.'), | |
| ['status' => 400] | |
| ); | |
| } | |
| } | |
| return parent::serve_batch_request_v1($batch_request); | |
| } | |
| } | |
| final class Plugin | |
| { | |
| /** | |
| * Register the REST server replacement before WordPress creates the server instance. | |
| */ | |
| public static function boot(): void | |
| { | |
| \add_filter( | |
| 'wp_rest_server_class', | |
| [self::class, 'filter_rest_server_class'], | |
| PHP_INT_MAX | |
| ); | |
| } | |
| /** | |
| * Use the patched server only while core lacks safe REST batch error handling. | |
| * | |
| * @param string $class_name Current REST server class name. | |
| * @return string | |
| */ | |
| public static function filter_rest_server_class($class_name) | |
| { | |
| global $wp_version; | |
| $core_requires_fix = \version_compare($wp_version, '5.6', '>=') | |
| && ( | |
| \version_compare($wp_version, '6.8.6', '<=') | |
| || ( | |
| \version_compare($wp_version, '6.9-alpha', '>=') | |
| && \version_compare($wp_version, '6.9.5', '<') | |
| ) | |
| || ( | |
| \version_compare($wp_version, '7.0-alpha', '>=') | |
| && \version_compare($wp_version, '7.0.2', '<') | |
| ) | |
| || ( | |
| \version_compare($wp_version, '7.1-alpha', '>=') | |
| && \version_compare($wp_version, '7.1-beta2', '<') | |
| ) | |
| ); | |
| if (!$core_requires_fix) { | |
| return $class_name; | |
| } | |
| if (\WP_REST_Server::class !== $class_name) { | |
| \error_log( | |
| 'Fix REST Batch did not replace the REST server because another custom server class is active.' | |
| ); | |
| return $class_name; | |
| } | |
| return REST_Server::class; | |
| } | |
| } | |
| Plugin::boot(); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| /* | |
| * Plugin Name: Emergency Security Guard | |
| * Description: Temporary mitigation for chained RCE: CVE-2026-60137 and CVE-2026-63030 | |
| */ | |
| /** | |
| * Sanitize author exclusions before WP_Query builds SQL. | |
| */ | |
| function emergency_wp_sanitize_author_not_in($query) | |
| { | |
| $value = $query->get('author__not_in'); | |
| if ($value === null || $value === '' || $value === []) { | |
| return; | |
| } | |
| if (is_int($value)) { | |
| $value = [$value]; | |
| } | |
| if (!is_array($value) && !is_string($value)) { | |
| $query->set('author__not_in', []); | |
| return; | |
| } | |
| $ids = array_values(array_filter(wp_parse_id_list($value))); | |
| $query->set('author__not_in', $ids); | |
| } | |
| add_action('parse_query', 'emergency_wp_sanitize_author_not_in', PHP_INT_MAX); | |
| /** | |
| * Disable the REST batch endpoint. | |
| */ | |
| function emergency_wp_block_rest_batch($result, $server, $request) | |
| { | |
| if (untrailingslashit($request->get_route()) === '/batch/v1') { | |
| return new WP_Error( | |
| 'emergency_batch_disabled', | |
| 'REST batch requests are temporarily disabled.', | |
| ['status' => 403] | |
| ); | |
| } | |
| return $result; | |
| } | |
| add_filter('rest_pre_dispatch', 'emergency_wp_block_rest_batch', -9999, 3); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment