tag. * * @package advanced-analytics * * @since 5.1.0 */ declare(strict_types=1); namespace ADVAN\Checks\Security; use ADVAN\Checks\Scanner; use ADVAN\Checks\Abstract_Check; // Exit if accessed directly. if ( ! defined( 'ABSPATH' ) ) { exit; } if ( ! class_exists( 'ADVAN\Checks\Security\Version_Disclosure_Check' ) ) { /** * WordPress Version Disclosure check class. * * @since 5.1.0 */ class Version_Disclosure_Check extends Abstract_Check { /** * Check identifier (slug). * * @var string Check identifier (slug). * * @since 5.1.0 */ protected static string $id = 'version-disclosure'; /** * Check category. * * @var string Check category. * * @since 5.1.0 */ protected static string $category = 'security'; /** * Check weight (1-10). * * @var int Check weight (1-10). * * @since 5.1.0 */ protected static int $weight = 5; /** * Check title (translatable). * * @return string Check title. * * @since 5.1.0 */ public static function get_title(): string { return __( 'Version Disclosure', '0-day-analytics' ); } /** * Check description (translatable). * * @return string Check description. * * @since 5.1.0 */ public static function get_description(): string { return __( 'Checks if the WordPress version is exposed in the HTML source code.', '0-day-analytics' ); } /** * Run the check. * * @return array Check result. * * @since 5.1.0 */ public static function run(): array { $site_url = \get_site_url(); // Reuse shared HTTP response when available (R-02). $response = Scanner::cached_get( $site_url, array( 'timeout' => 10, 'sslverify' => false, 'redirection' => 2, ) ); if ( \is_wp_error( $response ) ) { return static::info( __( 'Could not connect to your site to check for version disclosure. This may be a temporary issue.', '0-day-analytics' ), array( 'error' => $response->get_error_message() ) ); } $body = \wp_remote_retrieve_body( $response ); $headers = \wp_remote_retrieve_headers( $response ); $details = array(); $issues = array(); // Check for in body. if ( preg_match( '/]+name=["\']generator["\'][^>]+content=["\']WordPress\s+([^"\']+)["\'][^>]*>/i', $body, $m ) ) { $issues[] = 'generator_meta'; $details['generator_meta'] = $m[1]; } // Check for X-Powered-By header exposing PHP version. if ( isset( $headers['x-powered-by'] ) ) { $powered = is_array( $headers['x-powered-by'] ) ? implode( ', ', $headers['x-powered-by'] ) : $headers['x-powered-by']; $issues[] = 'x-powered-by'; $details['x-powered-by'] = $powered; } // Check for WordPress version in RSS/Atom feeds link (common leak vector). if ( preg_match( '/]+type=["\']application\/(rss|atom)\+xml["\'][^>]*>/i', $body ) ) { $details['feed_links_present'] = 'yes'; } // Check for wp-emoji and wp-embed scripts that reveal WP version via ?ver= param. if ( preg_match( '/[?&]ver=(\d+\.\d+[\.\d]*)/i', $body, $ver_match ) ) { $details['script_version_leak'] = $ver_match[1]; // Only flag if it matches the actual WP version. global $wp_version; if ( $ver_match[1] === $wp_version ) { $issues[] = 'script_ver_param'; } } if ( empty( $issues ) ) { return static::pass( __( 'Your WordPress version is not exposed in the HTML source. Attackers cannot easily determine which version you are running.', '0-day-analytics' ), $details ); } $messages = array(); if ( in_array( 'generator_meta', $issues, true ) ) { $messages[] = sprintf( /* translators: %s: WordPress version */ __( 'The generator meta tag reveals WordPress version %s', '0-day-analytics' ), \esc_html( $details['generator_meta'] ) ); } if ( in_array( 'x-powered-by', $issues, true ) ) { $messages[] = sprintf( /* translators: %s: Header value */ __( 'The X-Powered-By header exposes: %s', '0-day-analytics' ), \esc_html( $details['x-powered-by'] ) ); } if ( in_array( 'script_ver_param', $issues, true ) ) { $messages[] = __( 'Enqueued scripts expose the WordPress version via ?ver= parameter', '0-day-analytics' ); } $severity = in_array( 'generator_meta', $issues, true ) ? 'medium' : 'low'; return static::fail( $severity, implode( '. ', $messages ) . '. ' . __( 'Exposing your WordPress version helps attackers identify known vulnerabilities for that specific version.', '0-day-analytics' ), sprintf( /* translators: %s: link to WP Panel Security & API section */ __( 'Remove the generator tag by adding to your theme\'s functions.php: remove_action(\'wp_head\', \'wp_generator\'); For the X-Powered-By header, configure your server (Apache/Nginx) or add: header_remove(\'X-Powered-By\'); in PHP. Or use this plugin\'s WP Panel module (Front-end & Performance section) to disable WP Generator Tag.', '0-day-analytics' ), \esc_url( \admin_url( 'admin.php?page=advan_wp_panel#frontend-and-performance' ) ), ), $details ); } } }