solutions( [ '', Vulnerability::R_DEACTIVATED, Vulnerability::R_MUTED, Vulnerability::R_PATCHED, ] ) ); if ( $unresolved_vulnerabilities->is_success() ) { foreach ( $unresolved_vulnerabilities->get_data() as $vulnerability ) { if ( ! $vulnerability->is_software_installed() ) { if ( ! $vulnerability->is_muted() ) { $vulnerability->deleted(); } } elseif ( ! in_array( $vulnerability->get_id(), $vulnerability_ids, true ) ) { $vulnerability->updated( false ); } /** * Fires if a vulnerability was not seen in the latest Site Scan. * * @param Vulnerability $vulnerability */ do_action( 'itsec_vulnerability_not_seen', $vulnerability ); $this->vulnerabilities->persist( $vulnerability ); } } $existing = ITSEC_Modules::get_setting( 'site-scanner', 'vulnerabilities' ); if ( $existing !== $vulnerabilities ) { ITSEC_Modules::set_setting( 'site-scanner', 'vulnerabilities', $vulnerabilities ); /** * Fires when the detected software vulnerabilities have changed. * * @param array $vulnerabilities The new vulnerabilities set. * @param array $existing The existing vulnerabilities. */ do_action( 'itsec_software_vulnerabilities_changed', $vulnerabilities, $existing ); } $counts['total'] = array_sum( $counts ); do_action( 'stellarwp/telemetry/ithemes-security/event', 'site-scan', [ 'vulnerabilities' => $counts, ] ); } /** * When a plugin is activated, check if there are any vulnerabilities * that were resolved through deactivation that we need to unresolve. * * @param string $plugin The plugin file. * * @return void */ public function unresolve_activated_plugin( $plugin ) { if ( ! $slug = dirname( $plugin ) ) { return; } $vulnerabilities = $this->vulnerabilities->get_vulnerabilities( ( new Vulnerabilities_Options() ) ->add_software( Vulnerability::T_PLUGIN, $slug ) ->set_resolutions( [ Vulnerability::R_DEACTIVATED, Vulnerability::R_DELETED ] ) ); if ( ! $vulnerabilities->is_success() ) { return; } foreach ( $vulnerabilities->get_data() as $vulnerability ) { $vulnerability->unresolve(); $this->vulnerabilities->persist( $vulnerability ); /** * Fires when a vulnerability has been seen. * * @param Vulnerability $model * @param Vulnerability_Issue $issue */ do_action( 'itsec_vulnerability_was_seen', $vulnerability, $vulnerability->as_issue() ); } } /** * When a plugin is deactivated, check if there are any vulnerabilities we need to resolve. * * @param string $plugin The plugin file. * * @return void */ public function resolve_deactivated_plugin( $plugin ) { if ( ! $slug = dirname( $plugin ) ) { return; } $vulnerabilities = $this->vulnerabilities->get_vulnerabilities( ( new Vulnerabilities_Options() ) ->add_software( Vulnerability::T_PLUGIN, $slug ) ->set_resolutions( [ '' ] ) ); if ( ! $vulnerabilities->is_success() ) { return; } $by = null; if ( is_user_logged_in() && ! wp_doing_cron() ) { $by = wp_get_current_user(); } foreach ( $vulnerabilities->get_data() as $vulnerability ) { $vulnerability->deactivated( $by ); do_action( 'itsec_vulnerability_not_seen', $vulnerability ); $this->vulnerabilities->persist( $vulnerability ); } } /** * When a plugin is deleted, check if there are any vulnerabilities we need to resolve. * * @param string $plugin The plugin file. * @param bool $deleted If the plugin was deleted. * * @return void */ public function resolve_deleted_plugin( $plugin, $deleted ) { if ( ! $deleted ) { return; } if ( ! $slug = dirname( $plugin ) ) { return; } $vulnerabilities = $this->vulnerabilities->get_vulnerabilities( ( new Vulnerabilities_Options() ) ->add_software( Vulnerability::T_PLUGIN, $slug ) ->set_resolutions( [ '', Vulnerability::R_PATCHED, Vulnerability::R_DEACTIVATED ] ) ); if ( ! $vulnerabilities->is_success() ) { return; } $by = null; if ( is_user_logged_in() && ! wp_doing_cron() ) { $by = wp_get_current_user(); } foreach ( $vulnerabilities->get_data() as $vulnerability ) { $vulnerability->deleted( $by ); do_action( 'itsec_vulnerability_not_seen', $vulnerability ); $this->vulnerabilities->persist( $vulnerability ); } } /** * When a theme is switched, check if there are any vulnerabilities we need to resolve. * * @param string $new_name * @param WP_Theme $new_theme * @param WP_Theme $old_theme * * @return void */ public function resolve_switched_theme( $new_name, $new_theme, $old_theme ) { $vulnerabilities = $this->vulnerabilities->get_vulnerabilities( ( new Vulnerabilities_Options() ) ->add_software( Vulnerability::T_THEME, $old_theme->get_stylesheet() ) ->set_resolutions( [ '' ] ) ); if ( ! $vulnerabilities->is_success() ) { return; } $by = null; if ( is_user_logged_in() && ! wp_doing_cron() ) { $by = wp_get_current_user(); } foreach ( $vulnerabilities->get_data() as $vulnerability ) { $vulnerability->deactivated( $by ); do_action( 'itsec_vulnerability_not_seen', $vulnerability ); $this->vulnerabilities->persist( $vulnerability ); } } /** * When a theme is deleted, check if there are any vulnerabilities we need to resolve. * * @param string $stylesheet The theme being deleted. * @param bool $deleted If the theme was deleted. * * @return void */ public function resolve_deleted_theme( $stylesheet, $deleted ) { if ( ! $deleted ) { return; } $vulnerabilities = $this->vulnerabilities->get_vulnerabilities( ( new Vulnerabilities_Options() ) ->add_software( Vulnerability::T_THEME, $stylesheet ) ->set_resolutions( [ '', Vulnerability::R_PATCHED, Vulnerability::R_DEACTIVATED ] ) ); if ( ! $vulnerabilities->is_success() ) { return; } $by = null; if ( is_user_logged_in() && ! wp_doing_cron() ) { $by = wp_get_current_user(); } foreach ( $vulnerabilities->get_data() as $vulnerability ) { $vulnerability->deleted( $by ); do_action( 'itsec_vulnerability_not_seen', $vulnerability ); $this->vulnerabilities->persist( $vulnerability ); } } public function add_vulnerabilities_section_to_digest( ITSEC_Mail $mail ): void { $vulnerabilities = $this->vulnerabilities->get_vulnerabilities( ( new Vulnerabilities_Options() ) ->set_resolutions( [ '', Vulnerability::R_PATCHED ] ) ); if ( ! $vulnerabilities->is_success() ) { return; } if ( count( $vulnerabilities->get_data() ) === 0 ) { return; } $issues = array_map( static fn( Vulnerability $vulnerability ) => $vulnerability->as_issue(), $vulnerabilities->get_data() ); $mail->add_section_heading( __('Known Vulnerabilities', 'better-wp-security') ); $mail->add_text( __('Each vulnerability is assigned a Patchstack priority score to help inform your next steps. If no virtual patch has been applied, ensure that you patch/update within the recommended timeframe.', 'better-wp-security'), 'light', 10 ); $mail->add_section(ITSEC_Site_Scanner_Mail::format_vulnerability_issues( $mail, $issues )); } /** * Add vulnerability counts to Site Health. * * @param array $info * * @return array */ public function add_site_health_info( $info ) { $active = $this->vulnerabilities->count_vulnerabilities( ( new Vulnerabilities_Options() )->set_resolutions( [ '' ] ) ); $patched = $this->vulnerabilities->count_vulnerabilities( ( new Vulnerabilities_Options() )->set_resolutions( [ Vulnerability::R_PATCHED ] ) ); if ( $active->is_success() ) { $info['solid-security']['fields']['active-vulnerabilities'] = [ 'label' => __( 'Active Vulnerabilities', 'better-wp-security' ), 'value' => $active->get_data(), 'debug' => $active->get_data(), ]; } if ( $patched->is_success() ) { $info['solid-security']['fields']['patched-vulnerabilities'] = [ 'label' => __( 'Patched Vulnerabilities', 'better-wp-security' ), 'value' => $patched->get_data(), 'debug' => $patched->get_data(), ]; } return $info; } }