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