' . esc_html__( 'Open scanner', 'icecodero-security-scanner' ) . '' ); return $links; } add_filter( 'plugin_action_links_' . plugin_basename( ICSEC_DIR . '/icecodero-security-scanner.php' ), 'icsec_action_links' ); /** * Loads the assets on the scanner screen only. * * @param string $hook Current screen. */ function icsec_assets( $hook ) { if ( 'toplevel_page_icecodero-security-scanner' !== $hook ) { return; } wp_enqueue_style( 'icsec-admin', ICSEC_URL . 'assets/admin.css', array(), ICSEC_VERSION ); wp_enqueue_script( 'icsec-admin', ICSEC_URL . 'assets/admin.js', array(), ICSEC_VERSION, true ); wp_localize_script( 'icsec-admin', 'ICSEC', array( 'ajax' => admin_url( 'admin-ajax.php' ), 'nonce' => wp_create_nonce( 'icsec' ), 'i18n' => array( 'error' => __( 'Communication error', 'icecodero-security-scanner' ), 'preparing' => __( 'Preparing the scan…', 'icecodero-security-scanner' ), 'saving' => __( 'Saving the report…', 'icecodero-security-scanner' ), /* translators: %d: number of files checked so far */ 'resume' => __( '(continuing, %d files checked)', 'icecodero-security-scanner' ), /* translators: %d: number of findings so far */ 'found' => __( 'findings: %d', 'icecodero-security-scanner' ), 'failed' => __( 'Steps that could not be completed:', 'icecodero-security-scanner' ), 'notStart' => __( 'The scan could not start:', 'icecodero-security-scanner' ), /* translators: 1: last modified date, 2: file size */ 'meta' => __( 'Modified: %1$s · Size: %2$s', 'icecodero-security-scanner' ), ), ) ); } add_action( 'admin_enqueue_scripts', 'icsec_assets' ); /** Allows the AJAX actions only for administrators (the nonce is checked first, in every handler). */ function icsec_check_ajax() { if ( ! current_user_can( 'manage_options' ) ) { wp_send_json_error( __( 'You are not allowed to do this.', 'icecodero-security-scanner' ), 403 ); } } /** Starts a scan: returns its id and its steps. */ function icsec_ajax_plan() { check_ajax_referer( 'icsec', 'nonce' ); icsec_check_ajax(); $id = wp_generate_password( 12, false ); set_transient( 'icsec_run_' . $id, array( 'findings' => array(), 'notes' => array(), ), 2 * HOUR_IN_SECONDS ); wp_send_json_success( array( 'id' => $id, 'steps' => ICSEC_Scanner::plan(), ) ); } add_action( 'wp_ajax_icsec_plan', 'icsec_ajax_plan' ); /** Runs one slice of one step. */ function icsec_ajax_step() { check_ajax_referer( 'icsec', 'nonce' ); icsec_check_ajax(); $id = isset( $_POST['id'] ) ? sanitize_key( wp_unslash( $_POST['id'] ) ) : ''; $step = isset( $_POST['step'] ) ? sanitize_text_field( wp_unslash( $_POST['step'] ) ) : ''; $run = get_transient( 'icsec_run_' . $id ); if ( ! is_array( $run ) ) { wp_send_json_error( __( 'The scan has expired. Please start it again.', 'icecodero-security-scanner' ) ); } $cursor = isset( $_POST['cursor'] ) ? absint( $_POST['cursor'] ) : 0; $r = ICSEC_Scanner::run_step( $step, $cursor, $id ); $run = get_transient( 'icsec_run_' . $id ); foreach ( (array) ( $r['findings'] ?? array() ) as $f ) { $run['findings'][] = $f; } if ( ! empty( $r['note'] ) ) { $run['notes'][ $step ] = $r['note']; } set_transient( 'icsec_run_' . $id, $run, 2 * HOUR_IN_SECONDS ); wp_send_json_success( array( 'found' => count( (array) ( $r['findings'] ?? array() ) ), 'files' => (int) ( $r['files'] ?? 0 ), 'next' => $r['next'] ?? null, ) ); } add_action( 'wp_ajax_icsec_step', 'icsec_ajax_step' ); /** Saves the report at the end of the scan. */ function icsec_ajax_finish() { check_ajax_referer( 'icsec', 'nonce' ); icsec_check_ajax(); $id = isset( $_POST['id'] ) ? sanitize_key( wp_unslash( $_POST['id'] ) ) : ''; $run = get_transient( 'icsec_run_' . $id ); if ( ! is_array( $run ) ) { wp_send_json_error( __( 'The scan has expired.', 'icecodero-security-scanner' ) ); } delete_transient( 'icsec_run_' . $id ); ICSEC_Scanner::save_report( $run['findings'], $run['notes'] ); wp_send_json_success(); } add_action( 'wp_ajax_icsec_finish', 'icsec_ajax_finish' ); /** Ignores (or restores) a finding. */ function icsec_ajax_ignore() { check_ajax_referer( 'icsec', 'nonce' ); icsec_check_ajax(); $key = isset( $_POST['key'] ) ? sanitize_key( wp_unslash( $_POST['key'] ) ) : ''; $on = ! empty( $_POST['on'] ); $ign = (array) get_option( ICSEC_Scanner::OPT_IGNORED, array() ); if ( $on ) { $ign[ $key ] = time(); } else { unset( $ign[ $key ] ); } update_option( ICSEC_Scanner::OPT_IGNORED, $ign, false ); $report = get_option( ICSEC_Scanner::OPT_REPORT ); if ( is_array( $report ) ) { ICSEC_Scanner::save_report( $report['findings'], $report['notes'], $report['time'] ); } wp_send_json_success(); } add_action( 'wp_ajax_icsec_ignore', 'icsec_ajax_ignore' ); /** Shows a few lines of a site file (read only, text files inside the site root, never wp-config.php). */ function icsec_ajax_view() { check_ajax_referer( 'icsec', 'nonce' ); icsec_check_ajax(); $rel = isset( $_POST['file'] ) ? wp_normalize_path( sanitize_text_field( wp_unslash( $_POST['file'] ) ) ) : ''; $line = isset( $_POST['line'] ) ? max( 1, absint( $_POST['line'] ) ) : 1; $root = ICSEC_Scanner::root(); $real = realpath( $root . '/' . ltrim( $rel, '/' ) ); if ( ! $real || 0 !== strpos( wp_normalize_path( $real ), $root . '/' ) || ! is_file( $real ) || filesize( $real ) > ICSEC_Scanner::max_file() || 'wp-config.php' === basename( $real ) ) { wp_send_json_error( __( 'This file cannot be shown.', 'icecodero-security-scanner' ) ); } $lines = file( $real ); $from = max( 0, $line - 8 ); $out = array(); foreach ( array_slice( (array) $lines, $from, 17 ) as $i => $l ) { $out[] = array( 'n' => $from + $i + 1, 't' => function_exists( 'mb_substr' ) ? mb_substr( rtrim( $l, "\r\n" ), 0, 400 ) : substr( rtrim( $l, "\r\n" ), 0, 400 ), ); } wp_send_json_success( array( 'lines' => $out, 'line' => $line, 'modified' => wp_date( 'Y-m-d H:i', filemtime( $real ) ), 'size' => size_format( filesize( $real ) ), ) ); } add_action( 'wp_ajax_icsec_view', 'icsec_ajax_view' ); /** Saves the settings. */ function icsec_save_settings() { if ( ! current_user_can( 'manage_options' ) ) { wp_die( esc_html__( 'You are not allowed to do this.', 'icecodero-security-scanner' ) ); } check_admin_referer( 'icsec_settings' ); $email = isset( $_POST['email'] ) ? sanitize_email( wp_unslash( $_POST['email'] ) ) : ''; $freq = isset( $_POST['freq'] ) ? sanitize_key( wp_unslash( $_POST['freq'] ) ) : ''; $level = isset( $_POST['level'] ) ? sanitize_key( wp_unslash( $_POST['level'] ) ) : ''; $steps = isset( $_POST['steps'] ) ? array_map( 'sanitize_key', (array) wp_unslash( $_POST['steps'] ) ) : array(); update_option( ICSEC_Scanner::OPT_SETTINGS, array( 'cron' => empty( $_POST['cron'] ) ? 0 : 1, 'freq' => in_array( $freq, array( 'twicedaily', 'daily', 'weekly' ), true ) ? $freq : 'daily', 'level' => 'critical' === $level ? 'critical' : 'warning', 'email' => $email ? $email : get_option( 'admin_email' ), 'max_mb' => isset( $_POST['max_mb'] ) ? max( 1, min( 64, absint( $_POST['max_mb'] ) ) ) : 4, 'budget' => isset( $_POST['budget'] ) ? max( 5, min( 120, absint( $_POST['budget'] ) ) ) : 18, 'skip' => isset( $_POST['skip'] ) ? sanitize_textarea_field( wp_unslash( $_POST['skip'] ) ) : '', 'steps' => array_values( array_intersect( $steps, array_keys( ICSEC_Scanner::optional_steps() ) ) ), ), false ); icsec_schedule(); wp_safe_redirect( admin_url( 'admin.php?page=icecodero-security-scanner&saved=1' ) ); exit; } add_action( 'admin_post_icsec_settings', 'icsec_save_settings' ); /** Downloads the report as a text file. */ function icsec_export() { if ( ! current_user_can( 'manage_options' ) ) { wp_die( esc_html__( 'You are not allowed to do this.', 'icecodero-security-scanner' ) ); } check_admin_referer( 'icsec_export' ); $report = get_option( ICSEC_Scanner::OPT_REPORT ); nocache_headers(); header( 'Content-Type: text/plain; charset=utf-8' ); header( 'Content-Disposition: attachment; filename="security-report-' . sanitize_file_name( (string) wp_parse_url( home_url(), PHP_URL_HOST ) ) . '-' . gmdate( 'Y-m-d' ) . '.txt"' ); // Plain text download, not HTML: escaping for HTML would corrupt the report. echo icsec_report_text( $report ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped exit; } add_action( 'admin_post_icsec_export', 'icsec_export' ); /** * Translated name of a severity. * * @param string $sev Severity code. * @return string */ function icsec_severity_label( $sev ) { $labels = array( 'critical' => __( 'Critical', 'icecodero-security-scanner' ), 'warning' => __( 'Warning', 'icecodero-security-scanner' ), 'info' => __( 'Info', 'icecodero-security-scanner' ), ); return $labels[ $sev ] ?? $sev; } /** * The report as plain text. * * @param mixed $report Saved report. * @return string */ function icsec_report_text( $report ) { if ( ! is_array( $report ) ) { return __( 'There is no report yet.', 'icecodero-security-scanner' ) . "\n"; } /* translators: %s: site address */ $t = sprintf( __( 'Security report – %s', 'icecodero-security-scanner' ), home_url() ) . "\n"; /* translators: %s: scan date */ $t .= sprintf( __( 'Date: %s', 'icecodero-security-scanner' ), wp_date( 'Y-m-d H:i', $report['time'] ) ) . "\n"; $t .= sprintf( '%s: %d · %s: %d · %s: %d', icsec_severity_label( 'critical' ), $report['counts']['critical'], icsec_severity_label( 'warning' ), $report['counts']['warning'], icsec_severity_label( 'info' ), $report['counts']['info'] ) . "\n\n"; foreach ( $report['findings'] as $f ) { if ( ! empty( $f['ignored'] ) ) { continue; } /* translators: %d: line number */ $t .= '[' . icsec_severity_label( $f['sev'] ) . '] ' . ICSEC_Scanner::message( $f ) . ( $f['file'] ? ' – ' . $f['file'] : '' ) . ( $f['line'] ? ' (' . sprintf( __( 'line %d', 'icecodero-security-scanner' ), $f['line'] ) . ')' : '' ) . "\n"; if ( $f['code'] ) { $t .= ' ' . $f['code'] . "\n"; } } return $t; } /** The scanner screen. */ function icsec_page() { $report = get_option( ICSEC_Scanner::OPT_REPORT ); $settings = ICSEC_Scanner::settings(); $has = is_array( $report ) && isset( $report['counts']['critical'] ); ?>

' . esc_html( wp_date( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ), $report['time'] ) ) . '' ); ?>

iceCode' ); ?>