<?php /* Copyright (C) 2015-22 CERBER TECH INC., https://cerber.tech Copyright (C) 2015-22 Markov Gregory, https://wpcerber.com Licenced under the GNU GPL. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /* *========================================================================* | | | ATTENTION! Do not change or edit this file! | | | *========================================================================* */ const UIS_LOADER_HTML = '<div class="uis_loader_wrapper"><div class="uis_page_loader"></div></div>'; add_action( 'admin_init', function () { CRB_Globals::$assets_url = cerber_plugin_dir_url() . 'assets/'; CRB_Globals::$ajax_loader = CRB_Globals::$assets_url . 'ajax-loader.gif'; if ( cerber_is_wp_ajax() ) { return; } cerber_phpinfo(); cerber_admin_init(); cerber_export(); cerber_import(); cerber_delete_alert(); // @since 8.8.2.3 Workaround: if scheduled (cron) tasks are not executed on the website $last = get_site_transient( 'cerber_daily_1' ); if ( ! $last || ! is_array( $last ) || $last[0] < ( time() - DAY_IN_SECONDS ) ) { cerber_bg_task_add( 'cerber_do_hourly_2' ); cerber_bg_task_add( 'cerber_daily_run' ); } } ); function cerber_assets_dir() { return cerber_plugin_dir() . '/assets'; } // Scan dashboard =========================================================== function cerber_scanner_show_dashboard( $msg = '', $status = 0 ) { $loader = ( $status ) ? UIS_LOADER_HTML : ''; $stats = cerber_get_stats_html(); $stats = array_shift( $stats ); $note = ( $status ) ? '' : __( 'It seems this website has never been scanned. To start scanning click the button below.', 'wp-cerber' ); ?> <div id="crb-scan-display"> <div id="crb-the-table"> <div class="crb-scan-info scan-tile"> <table> <tr> <td><?php _e( 'Started', 'wp-cerber' ); ?></td> <td id="crb-started" data-init="-">-</td> </tr> <tr> <td><?php _e( 'Finished', 'wp-cerber' ); ?></td> <td id="crb-finished" data-init="-">-</td> </tr> <tr> <td><?php _e( 'Duration', 'wp-cerber' ); ?></td> <td id="crb-duration" data-init="-">-</td> </tr> <tr> <td><?php _e( 'Performance', 'wp-cerber' ); ?></td> <td id="crb-performance" data-init="-">-</td> </tr> <tr> <td>Mode</td> <td id="crb-smode" data-init="-">-</td> </tr> </table> </div> <div id="crb-scan-filter" class="crb-scan-info scan-tile"> <?php echo $stats; ?> </div> <div class="scan-tile"> <div><p><span id="crb-scanned-files" data-init="0">0</span> / <span id="crb-total-files" data-init="0">0</span> </p> <p><?php echo __( 'Scanned', 'wp-cerber' ) . ' / ' . __( 'Files to scan', 'wp-cerber' ); ?></p> </div> </div> <div class="scan-tile"> <div><p><span id="crb-critical" data-init="0">0</span> / <span id="crb-warning" data-init="0">0</span> </p> <p><?php _e( 'Critical issues', 'wp-cerber' ); ?> / <?php _e( 'Issues total', 'wp-cerber' ); ?></p> </div> </div> </div> <div id="crb-scan-progress"> <div> <div id="the-scan-bar"></div> </div> </div> <div id="crb-scan-message"><?php echo $msg; ?></div> </div> <div id="crb-scan-details"> <?php if ( $note ) { echo '<div id="crb-scan-note">' . $note . '</div>'; } ?> <table class="crb-table" id="crb-browse-files"> <?php $rows = array(); $rows[] = '<tr class="crb-scan-container" id="crb-wordpress" style=""><td colspan="6">WordPress</td></tr>'; $rows[] = '<tr class="crb-scan-container" id="crb-muplugins" style=""><td colspan="6">Must use plugins</td></tr>'; $rows[] = '<tr class="crb-scan-container" id="crb-dropins" style=""><td colspan="6">Drop-ins</td></tr>'; $rows[] = '<tr class="crb-scan-container" id="crb-plugins" style=""><td colspan="6">Plugins</td></tr>'; $rows[] = '<tr class="crb-scan-container" id="crb-themes" style=""><td colspan="6">Themes</td></tr>'; $rows[] = '<tr class="crb-scan-container" id="crb-uploads" style=""><td colspan="6">Uploads folder</td></tr>'; $rows[] = '<tr class="crb-scan-container" id="crb-unattended" style=""><td colspan="6">Unattended files</td></tr>'; echo implode( "\n", $rows ); ?> </table> <?php echo $loader; ?> </div> <?php cerber_ref_upload_form(); } function cerber_show_scanner() { // http://www.adequatelygood.com/JavaScript-Module-Pattern-In-Depth.html $msg = ''; $status = 0; if ( $scan = cerber_get_scan() ) { if ( ! $scan['finished'] ) { if ( $scan['cloud'] && cerber_is_cloud_enabled() && $scan['started'] > ( time() - 900 ) ) { $msg = __( 'Currently a scheduled scan in progress. Please wait until it is finished.', 'wp-cerber' ); $status = 1; } else { $msg = sprintf( __( 'Previous scan started %s has not been completed. Continue scanning?', 'wp-cerber' ), cerber_date( $scan['started'], false ) ); $status = 2; } } else { $status = 3; } } $start_quick = '<input data-control="start_scan" data-mode="quick" type="button" value="' . __( 'Start Quick Scan', 'wp-cerber' ) . '" class="button button-primary">'; $start_full = '<input data-control="start_scan" data-mode="full" type="button" value="' . __( 'Start Full Scan', 'wp-cerber' ) . '" class="button button-primary">'; $stop = '<input id="crb-stop-scan" style="display: none;" data-control="stop_scan" type="button" value="' . __( 'Stop Scanning', 'wp-cerber' ) . '" class="button button-primary">'; $continue = '<input id="crb-continue-scan" data-control="continue_scan" type="button" value="' . __( 'Continue Scanning', 'wp-cerber' ) . '" class="button button-primary">'; $controls = ''; switch ( $status ) { case 0: case 3: $controls = $start_quick . $start_full; break; case 1: $controls = ''; break; case 2: $controls = $start_quick . $start_full . $continue; break; } $controls .= $stop; $class = ( $status ) ? '' : 'crb-scanner-has-data'; echo '<div id="crb-scanner" class="' . $class . '">'; cerber_scanner_show_dashboard( $msg, $status ); $d = ''; if ( nexus_is_valid_request() && ! nexus_is_granted( 'submit' ) ) { $d = 'disabled="disabled"'; } ?> <div id="crb-scan-area"> <form> <table id="crb-scan-controls"> <tr> <td id="crb-file-controls"> <input data-control="delete_file" type="button" class="button button-secondary" <?php echo $d; ?> value="<?php _e( 'Delete', 'wp-cerber' ); ?>"/> <input data-control="ignore_add_file" type="button" class="button button-secondary" <?php echo $d; ?> value="<?php _e( 'Ignore', 'wp-cerber' ); ?>"/> </td> <td> <?php echo $controls; ?> </td> <!-- <td><a href="#" data-control="full-paths">Show full paths</a></td> --> <td><a href="#" class="dashicons dashicons-list-view" data-control="full-paths" title="Toggle full/relative paths"></a></td> </tr> </table> </form> </div> <?php echo '</div>'; } function cerber_ref_upload_form() { ?> <div id="crb-ref-upload-dialog" style="display: none;"> <p><?php _e( 'We have not found any integrity data to verify', 'wp-cerber' ); ?> <span id="ref-section-name"></span>.</p> <p><?php _e( "You have to upload a ZIP archive from which you've installed it. This enables the security scanner to verify the integrity of the code and detect malware.", 'wp-cerber' ); ?></p> <p><?php echo sprintf( __( 'Maximum upload file size: %s.' ), esc_html( size_format( wp_max_upload_size() ) ) ); ?></p> <form enctype="multipart/form-data"> <input type="file" name="refile" id="refile" required="required" accept=".zip"> <input type="submit" name="submit" value="<?php _e( 'Upload file', 'wp-cerber' ); ?>" class="button button-primary"> <ul style="list-style: none;"> <li style="display:none;" class="crb-status-msg">Uploading the file, please wait&#8230;</li> <li style="display:none;" class="crb-status-msg">Processing the file, please wait&#8230;</li> </ul> </form> </div> <?php } add_action( 'wp_ajax_cerber_scan_control', 'cerber_manual_scan' ); function cerber_manual_scan() { cerber_check_ajax_permissions(); ob_start(); // Collecting possible junk warnings and notices cause we need clean JSON to be sent $scanner = array(); $console_log = array(); $scan_do = ''; if ( cerber_is_http_post() && $scan_do = crb_get_post_fields( 'cerber_scan_do' ) ) { $scan_do = preg_replace( '/[^a-z_\-\d]/i', '', $scan_do ); $mode = ( $mode = crb_get_post_fields( 'cerber_scan_mode' ) ) ? preg_replace( '/[^a-z_\-\d]/i', '', $mode ) : 'quick'; $scanner = cerber_scanner( $scan_do, $mode ); } else { $console_log[] = 'Unknown HTTP request'; } $next_do = ( ! empty( $scanner['cerber_scan_do'] ) ) ? $scanner['cerber_scan_do'] : 'stop'; $console_log = array_merge( $console_log, cerber_db_get_errors() ); $console_log[] = 'PHP MEMORY ' . @ini_get( 'memory_limit' ); $ret = array( 'console_log' => $console_log, 'cerber_scan_do' => $next_do, 'cerber_scanner' => $scanner, //'scan' => cerber_get_scan(), // debug only ); if ( $scan_do != 'continue_scan' ) { $ret['strings'] = cerber_get_strings(); } ob_end_clean(); echo json_encode( $ret ); crb_admin_stop_ajax(); } /** * File viewer, server side AJAX * */ add_action( 'wp_ajax_cerber_view_file', function () { cerber_check_ajax_permissions(); $get = crb_get_query_params(); if ( ! $file_name = $get['file'] ) { crb_admin_stop_ajax( 'Error: Filename not specified. Please run a new malware scan.' ); } if ( ! @file_exists( $file_name ) ) { crb_admin_stop_ajax( 'Error: The requested file doesn\'t exist or has been deleted: ' . htmlspecialchars( $file_name ). '. Please run a new malware scan.' ); return; } if ( ! @is_file( $file_name ) ) { crb_admin_stop_ajax( 'Error: The requested file is not an ordinary file: ' . htmlspecialchars( $file_name ) ); return; } $file_size = filesize( $file_name ); if ( $file_size > 8000000 ) { crb_admin_stop_ajax( 'Error: The requested is too large to display: ' . crb_size_format( $file_size ) ); return; } if ( $file_size <= 0 ) { crb_admin_stop_ajax( 'The requested file is empty.' ); return; } $scan_id = absint( $get['scan_id'] ); $the_file = cerber_db_get_row( 'SELECT * FROM ' . cerber_get_db_prefix() . CERBER_SCAN_TABLE . ' WHERE scan_id = ' . $scan_id . ' AND file_name = "' . $file_name . '"' ); if ( ! $the_file ) { crb_admin_stop_ajax( __( 'File access error. Possibly scan results are outdated. Please run Quick or Full Scan.', 'wp-cerber' ) ); return; } if ( ! $source = file_get_contents( $file_name ) ) { crb_admin_stop_ajax( 'Error: Unable to load file.' ); return; } $source = htmlspecialchars( $source, ENT_SUBSTITUTE ); if ( ! $source ) { $source = 'Unable to display the contents of the file. This file contains non-printable characters.'; } if ( cerber_detect_exec_extension( $file_name ) || cerber_check_extension( $file_name, array( 'js', 'css', 'inc' ) ) || cerber_is_htaccess( $file_name ) ) { $paint = true; } else { $paint = false; } $overlay = ''; if ( $paint ) { $overlay = '<div id="crb-overlay">Loading, please wait...</div>'; } //$sh_url = plugin_dir_url( __FILE__ ) . 'assets/sh/'; $sh_url = CRB_Globals::$assets_url . 'sh/'; $sheight = absint( $get['sheight'] ) - 100; // highlighter is un-responsible, so we need tell him the real height ?> <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <script type="text/javascript" src="<?php echo $sh_url ?>scripts/shCore.js"></script> <script type="text/javascript" src="<?php echo $sh_url; ?>scripts/shBrushPhp.js"></script> <link href="<?php echo $sh_url; ?>styles/shCore.css" rel="stylesheet" type="text/css"/> <link href="<?php echo $sh_url; ?>styles/shThemeDefault.css" rel="stylesheet" type="text/css"/> <style> body { overflow: hidden; font-family: 'Roboto', sans-serif; font-size: 14px; } #crb-overlay { display: flex; justify-content: center; align-items: center; text-align: center; background-color: #fff; position: fixed; width: 100%; height: 100% z-index: 2; top: 0; left: 0; right: 0; bottom: 0; } #crb-issue { border-left: 3px solid crimson; background-color: #eee; padding: 1em; overflow: auto; } #crb-file-content { <?php if (!$paint) { echo ' max-height: '.$sheight .'px; overflow: auto; padding: 15px; '; } else { echo 'overflow: hidden;'; } ?> } .syntaxhighlighter { max-height: <?php echo $sheight; ?>px; } .syntaxhighlighter code { font-family: Menlo, Consolas, Monaco, monospace !important; font-size: 13px !important; } .syntaxhighlighter .gutter .line { border-right: 3px solid #c7c7c7 !important; } </style> </head> <body> <?php echo $overlay; echo '<pre id="crb-file-content" class="brush: php; toolbar: false;">' . $source . '</pre>'; if ( $the_file ) { echo '<div id="crb-issue">Issue: ' . cerber_get_issue_label( $the_file['scan_status'] ) . '</div>'; } if ( $paint ) : ?> <script type="text/javascript"> SyntaxHighlighter.defaults["highlight"]; SyntaxHighlighter.all(); function crb_waitUntilRender() { let overlay = document.getElementById("crb-overlay").style.visibility = "hidden"; } let intervalID = setInterval(crb_waitUntilRender, 200); </script> <?php endif; ?> </body> </html> <?php crb_admin_stop_ajax(); } ); /** * Upload a reference ZIP archive for a theme or a plugin * */ add_action( 'wp_ajax_cerber_ref_upload', function () { cerber_check_ajax_permissions(); //ob_start(); // Collecting possible junk warnings and notices cause we need clean JSON to be sent $error = ''; $folder = cerber_get_tmp_file_folder(); if ( crb_is_wp_error( $folder ) ) { cerber_end_ajax( array( 'error' => $folder->get_error_message() ) ); } if ( isset( $_FILES['refile'] ) ) { // Step 1, saving file if ( ! is_uploaded_file( $_FILES['refile']['tmp_name'] ) ) { $error = 'Unable to read uploaded file'; } if ( ! cerber_check_extension( $_FILES['refile']['name'], array( 'zip' ) ) ) { $error = __( 'This type of file is not supported. Please upload a ZIP archive.', 'wp-cerber' ); } if ( cerber_detect_exec_extension( $_FILES['refile']['name'] ) ) { $error = __( 'Executable files are not supported. Please upload a ZIP archive.', 'wp-cerber' ); } if ( false !== strpos( $_FILES['refile']['name'], '/' ) ) { $error = 'Incorrect filename'; } if ( $error ) { cerber_end_ajax( array( 'error' => $error ) ); } if ( false === @move_uploaded_file( $_FILES['refile']['tmp_name'], $folder . $_FILES['refile']['name'] ) ) { cerber_end_ajax( array( 'error' => 'Unable to copy file to ' . $folder ) ); } } else { // Step 2, creating hash $result = cerber_need_for_hash(); if ( crb_is_wp_error( $result ) ) { cerber_end_ajax( array( 'error' => $result->get_error_message() ) ); } } cerber_end_ajax(); } ); /** * Deleting files, server side AJAX * */ add_action( 'wp_ajax_cerber_scan_bulk_files', function () { cerber_check_ajax_permissions(); $post = crb_get_post_fields(); if ( empty( $post['files'] ) || empty( $post['scan_id'] ) ) { crb_admin_stop_ajax( 'Error!' ); return; } $scan_id = absint( $post['scan_id'] ); if ( ! cerber_get_scan( $scan_id ) ) { crb_admin_stop_ajax( 'Error!' ); return; } $operation = $post['scan_file_operation']; if ( ( ! $ignore = cerber_get_set( 'ignore-list' ) ) || ! is_array( $ignore ) ) { $ignore = array(); } global $crb_list; $crb_list = array(); $i = 0; $errors = array(); $time = time(); $user_id = get_current_user_id(); foreach ( $post['files'] as $file_name ) { if ( ! is_file( $file_name ) ) { continue; } $the_file = cerber_db_get_row( 'SELECT * FROM ' . cerber_get_db_prefix() . CERBER_SCAN_TABLE . ' WHERE scan_id = ' . $scan_id . ' AND file_name = "' . $file_name . '"', MYSQL_FETCH_OBJECT ); if ( ! $the_file || ! is_file( $the_file->file_name ) ) { $errors[] = 'Unknown file: ' . $file_name; continue; } switch ( $operation ) { case 'delete_file': $result = cerber_quarantine_file( $file_name, $scan_id ); break; case 'ignore_add_file': $ignore[ $the_file->file_name_hash ] = array( $the_file->file_name, @hash_file( 'sha256', $the_file->file_name ), $user_id, $time, ); $result = true; break; } if ( crb_is_wp_error( $result ) ) { $errors[] = $result->get_error_message(); } elseif ( ! $result ) { $errors[] = 'Unknown error 55'; } else { $i ++; $crb_list[] = $file_name; } } if ( $operation == 'ignore_add_file' ) { // Update the last scan results to keep it up to date and avoid user confusing if ( $scan = cerber_get_scan() ) { crb_file_filter( $scan['issues'], function ( $file_name ) { global $crb_list; if ( in_array( $file_name, $crb_list ) ) { return false; } return true; } ); cerber_update_scan( $scan ); } if ( ! cerber_update_set( 'ignore-list', $ignore ) ) { $errors [] = 'Unable to update the ignore list'; } } crb_scan_debug( $errors ); cerber_end_ajax( array( 'errors' => $errors, 'number' => $i, 'processed' => $crb_list ) ); } ); /** * Finalizes current AJAX request and sends data to the client * * @param $data array */ function cerber_end_ajax( $data = array() ) { if ( ! $data ) { $data = array(); } $data['cerber_db_errors'] = cerber_db_get_errors(); if ( ! $data['cerber_db_errors'] ) { $data['OK'] = 'OK!'; } echo json_encode( $data ); if ( ! nexus_is_valid_request() ) { wp_die(); } } function crb_admin_stop_ajax( $msg = '' ) { if ( $msg ) { echo $msg; } if ( ! nexus_is_valid_request() ) { exit; } } function cerber_show_quarantine() { $folder = cerber_get_the_folder( true ); if ( crb_is_wp_error( $folder ) ) { echo $folder->get_error_message(); return; } $no_files = '<p>' . __( 'There are no files in the quarantine at the moment.', 'wp-cerber' ) . '</p>'; $per_page = crb_admin_get_per_page(); $first = ( cerber_get_pn() - 1 ) * $per_page; $last = $first + $per_page; $list = array(); $filter_scan = crb_get_query_params( 'scan', '\d+' ); list ( $list, $count, $scan_list ) = cerber_quarantine_get_files( $first, $last, $filter_scan ); _crb_qr_total_sync( $count ); if ( ! $list ) { if ( ! $filter_scan ) { echo $no_files; } else { echo __( 'No files match the specified filter.', 'wp-cerber' ) . ' <a href="' . cerber_admin_link( 'scan_quarantine' ) . '">' . __( 'Click here to see the full list of files', 'wp-cerber' ) . '</a>.'; } return; } $rows = array(); $ofs = get_option( 'gmt_offset' ) * 3600; $confirm = ' onclick="return confirm(\'' . __( 'Are you sure?', 'wp-cerber' ) . '\');"'; foreach ( $list as $file ) { $p = array( 'cerber_admin_do' => 'scan_tegrity', 'crb_scan_id' => $file['scan_id'], 'crb_file_id' => $file['qfile'] ); $p['crb_scan_adm'] = 'delete'; $delete = '<a ' . $confirm . ' href="' . cerber_admin_link_add( $p ) . '">' . __( 'Delete permanently', 'wp-cerber' ) . '</a>'; $p['crb_scan_adm'] = 'restore'; $restore = ( ! $file['can'] ) ? '' : ' | <a ' . $confirm . ' href="' . cerber_admin_link_add( $p ) . '">' . __( 'Restore', 'wp-cerber' ) . '</a>'; $moved = strtotime( $file['date'] ) - $ofs; $will = cerber_auto_date( $file['scan_id'] + DAY_IN_SECONDS * crb_get_settings( 'scan_qcleanup' ) ); $file_name = str_replace( DIRECTORY_SEPARATOR, '<wbr>' . DIRECTORY_SEPARATOR, $file['source'] ); $rows[] = array( '<span title="' . cerber_date( $file['scan_id'] ) . '">' . cerber_auto_date( $file['scan_id'] ) . '</span>', '<span title="' . cerber_date( $moved ) . '">' . cerber_auto_date( $moved ) . '</span>', $will, $file['size'], $file_name, '<span style="white-space: pre;">' . $delete . $restore . '</span>' ); } $heading = array( __( 'Scanned', 'wp-cerber' ), __( 'Quarantined', 'wp-cerber' ), __( 'Automatic deletion', 'wp-cerber' ), __( 'Size', 'wp-cerber' ), __( 'File', 'wp-cerber' ), __( 'Action', 'wp-cerber' ), ); $table = cerber_make_table( $rows, $heading, 'crb-quarantine' ); $table .= cerber_page_navi( $count, $per_page ); $filter = ''; if ( count( $scan_list ) > 1 ) { krsort( $scan_list ); $list = array( 0 => __( 'All scans', 'wp-cerber' ) ); foreach ( $scan_list as $s ) { $list[ $s ] = cerber_date( $s, false ); } $filter = '<div style="text-align: right; margin-bottom: 1em;"><form style="width: auto;" action="">' . cerber_select( 'scan', $list, $filter_scan ) . ' <input value="Filter" class="button" type="submit"><input name="page" value="cerber-integrity" type="hidden"><input name="tab" value="scan_quarantine" type="hidden"></form></div>'; } echo $filter . $table; } function cerber_quarantine_do( $what, $scan_id, $qfile ) { $scan_id = absint( $scan_id ); if ( ! $scan_id ) { cerber_admin_notice( 'Error: Wrong scan parameters.' ); return; } //$dir = cerber_get_the_folder() . 'quarantine' . DIRECTORY_SEPARATOR . $scan_id; $dir = cerber_get_the_folder( true ); if ( crb_is_wp_error( $dir ) ) { cerber_admin_notice( $dir->get_error_message() ); return; } $dir .= 'quarantine' . DIRECTORY_SEPARATOR . $scan_id; $file = $dir . DIRECTORY_SEPARATOR . $qfile; if ( ! @is_file( $file ) || is_link( $file ) ) { cerber_admin_notice( 'Error: No file to process' ); return; } $rst = $dir . '/.restore'; if ( ! file_exists( $rst ) || ! $handle = @fopen( $rst, 'r' ) ) { cerber_admin_notice( 'Error: A restore registry file is corrupt or missing.' ); return; } $data = null; while ( ( $line = fgets( $handle ) ) !== false ) { if ( $p = crb_parse_qline( $dir, $line ) ) { if ( $p['qfile'] == $qfile ) { $data = $p; break; } } } if ( ! $data ) { cerber_admin_notice( 'Error: No information about this file. Unable to proceed.' ); return; } $err = null; $msg = null; switch ( $what ) { case 'delete': if ( unlink( $file ) ) { $msg = __( 'The file has been deleted permanently.', 'wp-cerber' ); crb_qr_total_update( -1 ); } else { $err = 'Unable to delete the file: ' . $file; } break; case 'restore': if ( $data['can'] ) { $target_dir = dirname( $data['source'] ); if ( ! file_exists( $target_dir ) && ! mkdir( $target_dir, 0755, true ) ) { $err = 'Unable to create the folder <b>' . $target_dir . '</b>. Check permissions of parent folders.'; } if ( ! $err ) { if ( @rename( $file, $data['source'] ) ) { $msg = __( 'The file has been restored to its original location.', 'wp-cerber' ); crb_qr_total_update( -1 ); } else { $err = 'A file error occurred while restoring the file. Check permissions of folders.'; } } } else { $err = 'This file cannot be restored and needs to be manually copied. <p>See instructions in this file: ' . $rst . '</p>'; } break; } if ( $err ) { cerber_admin_notice( __( 'ERROR:', 'wp-cerber' ) . ' ' . $err ); } if ( $msg ) { cerber_admin_message( $msg ); } } function cerber_show_ignore() { // For translators __( 'Apply', 'wp-cerber' ); __( 'Remove from the list', 'wp-cerber' ); __( 'User Insights', 'wp-cerber' ); __( 'Traffic Insights', 'wp-cerber' ); __( 'Activity Insights', 'wp-cerber' ); $no_files = __( 'The list is empty.', 'wp-cerber' ); $per_page = crb_admin_get_per_page(); $first = ( cerber_get_pn() - 1 ) * $per_page; if ( ! $list = cerber_get_set( 'ignore-list' ) ) { echo '<p>' . $no_files . '</p>'; return; } $count = count( $list ); $list = array_slice( $list, $first, $per_page ); $rows = array(); $confirm = ' onclick="return confirm(\'' . __( 'Are you sure?', 'wp-cerber' ) . '\');"'; foreach ( $list as $key => $file ) { $delete = '<a ' . $confirm . ' href="' . cerber_admin_link_add( array( 'cerber_admin_do' => 'scan_tegrity', 'crb_scan_adm' => 'remove_ignore', 'crb_file_id' => $key ) ) . '">' . __( 'Remove from the list', 'wp-cerber' ) . '</a>'; $rows[] = array( cerber_date( $file[3] ), cerber_date( cerber_get_date( $file[0] ) ), crb_size_format( cerber_get_size( $file[0] ) ), $file[0], '<span style="white-space: pre;">' . $delete . '</span>' ); } $heading = array( __( 'Added', 'wp-cerber' ), __( 'Modified', 'wp-cerber' ), __( 'Size', 'wp-cerber' ), __( 'File', 'wp-cerber' ), __( 'Action', 'wp-cerber' ), ); $table = cerber_make_table( $rows, $heading ); $table .= cerber_page_navi( $count, $per_page ); echo $table; } function crb_remove_ignore( $id ) { if ( ! $list = cerber_get_set( 'ignore-list' ) ) { return false; } if ( ! isset( $list[ $id ] ) ) { return false; } unset( $list[ $id ] ); return cerber_update_set( 'ignore-list', $list ); } // Scan analytics =========================================================== function cerber_scan_insights() { if ( ! $scan_id = crb_scan_last_full() ) { echo 'No data for generating reports. Please run the Full Scan. After the scan is completed, the report will be generated.'; return; } if ( $ext = crb_get_query_params( 'fext' ) ) { cerber_show_files( $ext, $scan_id ); return; } ?> <div id="crb_scan_insights"> <div class="crb_async_content" data-ajax_route="scanner_analytics" data-scan_id="<?php echo $scan_id; ?>" data-itype="1"> </div> <div class="crb_async_content" data-ajax_route="scanner_analytics" data-scan_id="<?php echo $scan_id; ?>" data-itype="2"> </div> <div id="crb_ins_ext_list" class="crb_async_content" data-ajax_route="scanner_analytics" data-scan_id="<?php echo $scan_id; ?>" data-itype="3"> </div> </div> <?php } /** * @param string $ext * @param int $scan_id */ function cerber_show_files( $ext, $scan_id ) { $ext = cerber_real_escape( $ext ); $per_page = 25; $limit = cerber_get_sql_limit( $per_page ); $files = cerber_db_get_results( 'SELECT SQL_CALC_FOUND_ROWS file_name, file_mtime, file_size FROM ' . cerber_get_db_prefix() . CERBER_SCAN_TABLE . ' WHERE scan_id = ' . $scan_id . ' AND file_ext = "' . $ext . '" ' . $limit ); $total = cerber_db_get_var( 'SELECT FOUND_ROWS()' ); if ( ! $files ) { echo '<p>No files found. Please run the Full Scan.</p>'; return; } $title = ( $ext == '.' ) ? __( 'Files without extension', 'wp-cerber' ) : 'Files with the "' . $ext . '" extension'; echo '<div style="display: table-cell"><h3>' . $title . '</h3></div><div style="display: table-cell; vertical-align: middle;"> &nbsp; <a href="' . cerber_admin_link( 'scan_insights' ) . '#crb_ins_ext_list" class="page-title-action">' . __( 'Back to list', 'wp-cerber' ) . '</a></div>'; echo crb_make_file_table( $files ) . cerber_page_navi( $total, $per_page ); } /** * @param string $type * * @return string[] * * @since 8.6.4 */ function cerber_generate_insights( $type ) { if ( ! $scan_id = crb_scan_last_full() ) { return array( 'html' => __( 'No data for generating reports. Please run the Full Scan. After the scan is completed, the reports will be generated.', 'wp-cerber' ) ); } $key = 'scan_insights_' . $type; // Cache if ( $report = cerber_get_set( $key, $scan_id, false ) ) { return array( 'html' => $report ); } switch ( $type ) { case 1: $report = crb_scan_insights_brief( $scan_id ); break; case 2: $report = crb_scan_insights_lrgst( $scan_id ); break; case 3: $report = crb_scan_insights_exts( $scan_id ); break; } // Cache if ( $report ) { cerber_update_set( $key, $report, $scan_id, false, time() + 24 * 3600 ); } else { $report = 'ERROR: Unknown report'; } $response = array( 'html' => $report, 'error' => '' ); /*if ( $_REQUEST['request'] < 2 ) { $response['continue'] = 1; }*/ return $response; } function crb_scan_insights_brief( $scan_id ) { $scan_id = absint( $scan_id ); $table = cerber_get_db_prefix() . CERBER_SCAN_TABLE; $result = '<h3>' . __( 'Brief summary', 'wp-cerber' ) . '</h3>'; $list = array( array( 'WordPress root folder (ABSPATH) ', ABSPATH ), array( 'WordPress uploads folder', cerber_get_upload_dir() ), array( 'WordPress content folder', dirname( cerber_get_plugins_dir() ) ), array( 'WordPress plugins folder', cerber_get_plugins_dir() ), array( 'WordPress themes folder', cerber_get_themes_dir() ), array( 'WordPress must-use plugin folder (WPMU_PLUGIN_DIR) ', WPMU_PLUGIN_DIR ), array( 'PHP folder for uploading files', ini_get( 'upload_tmp_dir' ) ), array( 'Server folder for temporary files', sys_get_temp_dir() ), array( 'Server folder for users\' session data', session_save_path() ), ); /* It's not scanned by the scanner $cerber_folder = cerber_get_my_folder(); if ( ! crb_is_wp_error( $cerber_folder ) ) { $list[] = array( 'WP Cerber\'s folder', $cerber_folder ); }*/ $folders = array(); foreach ( $list as $item ) { if ( ! $item[1] || ! @file_exists( $item[1] ) ) { continue; } $item[2] = 0; $item[3] = 0; $item[1] = rtrim( $item[1], DIRECTORY_SEPARATOR ) . DIRECTORY_SEPARATOR; if ( $files = cerber_db_get_col( 'SELECT file_size FROM ' . $table . ' WHERE scan_id = ' . $scan_id . ' AND file_name LIKE "' . $item[1] . '%"' ) ) { $item[2] = count( $files ); if ( $sum = array_sum( $files ) ) { $item[3] = crb_size_format( $sum ); } } $folders[] = $item; } $sql = 'SELECT file_size FROM ' . $table . ' WHERE scan_id = ' . $scan_id . ' AND file_name NOT LIKE "' . ABSPATH . '%"'; $files = cerber_db_get_col( $sql ); if ( $files ) { if ( $sum = array_sum( $files ) ) { $sum = crb_size_format( $sum ); } $folders[] = array( 'Above the WordPress installation folder', '', count( $files ), $sum ); } $column = array_column( $folders, 2 ); array_multisort( $column, SORT_DESC, $folders ); return $result . cerber_make_table( $folders, array( __( 'Folder', 'wp-cerber' ), __( 'Path', 'wp-cerber' ), __( 'Files', 'wp-cerber' ), __( 'Space Occupied', 'wp-cerber' ) ), '', 'crb_align_right crb_align_left_2', 'crb-monospace' ); } function crb_scan_insights_exts( $scan_id ) { $scan_id = absint( $scan_id ); $table = cerber_get_db_prefix() . CERBER_SCAN_TABLE; $list = array(); $offset = 0; $total = 0; $done = false; //cerber_exec_timer( 15 ); while ( true ) { if ( ! $files = cerber_db_get_results( 'SELECT file_name, file_name_hash, file_mtime, file_size, file_ext FROM ' . $table . ' WHERE scan_id = ' . $scan_id . ' LIMIT ' . CRB_SQL_CHUNK . ' OFFSET ' . $offset ) ) { $done = true; break; } $offset += CRB_SQL_CHUNK; foreach ( $files as $file ) { if ( ! $ext = crb_get_extension( $file['file_name'] ) ) { $ext = '.'; } if ( empty( $file['file_ext'] ) ) { cerber_db_query( 'UPDATE ' . $table . ' SET file_ext = "' . $ext . '" WHERE scan_id = ' . $scan_id . ' AND file_name_hash = "' . $file['file_name_hash'] . '"' ); } if ( ! isset( $list[ $ext ] ) ) { $list[ $ext ] = array( 0, 0, array(), array() ); } $list[ $ext ][0] ++; $list[ $ext ][1] += $file['file_size']; $list[ $ext ][2][] = $file['file_size']; $list[ $ext ][3][] = $file['file_mtime']; $total ++; } } if ( ! $done && ( count( $files ) < CRB_SQL_CHUNK ) ) { $done = true; } if ( ! $done ) { return array( 'continue' => 1 ); } if ( ! $list ) { return 'No data for generating reports. Please run the Full Scan. After the scan is completed, the report will be generated.'; } $base = cerber_admin_link( 'scan_insights' ); $none = __( 'No extension', 'wp-cerber' ); $result = array(); foreach ( $list as $ext => $data ) { $text = ( $ext == '.' ) ? $none : $ext; $result[] = array( '<a href="' . $base . esc_attr( '&fext=' . $ext ) . '">' . $text . '</a>', $data[0], crb_size_format( $data[1] ), crb_size_format( min( $data[2] ) ), crb_size_format( max( $data[2] ) ), crb_size_format( round( $data[1] / $data[0], 0 ) ), cerber_date( min( $data[3] ) ), cerber_date( max( $data[3] ) ), ); } // Sorting by a column in a multidimensional array $column = array_column( $result, 1 ); array_multisort( $column, SORT_DESC, $result ); // Create table //$report = '<h3>The file system report is based on the last full scan.</h3>'; $report = '<h3>' . __( 'File extensions statistics', 'wp-cerber' ) . '</h3>'; $report .= cerber_make_table( $result, array( __( 'Extension', 'wp-cerber' ), __( 'Files', 'wp-cerber' ), __( 'Space Occupied', 'wp-cerber' ), __( 'Smallest', 'wp-cerber' ), __( 'Largest', 'wp-cerber' ), __( 'Average Size', 'wp-cerber' ), __( 'Oldest', 'wp-cerber' ), __( 'Newest', 'wp-cerber' ), ), 'crb-ext-statistics', 'crb_align_right', 'crb-monospace crb-anchor-decorated' ); return $report; } function crb_scan_insights_lrgst( $scan_id ) { $scan_id = absint( $scan_id ); $table = cerber_get_db_prefix() . CERBER_SCAN_TABLE; if ( ! $files = cerber_db_get_results( 'SELECT file_name, file_mtime, file_size FROM ' . $table . ' WHERE scan_id = ' . $scan_id . ' ORDER BY file_size DESC LIMIT 10' ) ) { return 'No data for generating reports. Please run the Full Scan. After the scan is completed, the report will be generated.'; } $title = '<h3>' . __( 'Top 10 largest files', 'wp-cerber' ) . '</h3>'; return $title . crb_make_file_table( $files ); } function crb_scan_insights_duplicates( $scan_id ) { $scan_id = absint( $scan_id ); // SQL Doesn't work /* ( ! $files = cerber_db_get_results( 'SELECT file_name, file_mtime, file_size, file_hash FROM ' . cerber_get_db_prefix() . CERBER_SCAN_TABLE . ' WHERE scan_id = ' . $scan_id . ' GROUP BY file_hash HAVING COUNT(*) > 1 LIMIT 100' ) ) { return ''; }*/ if ( ! $files = cerber_db_get_col( 'SELECT file_hash FROM ' . cerber_get_db_prefix() . CERBER_SCAN_TABLE . ' WHERE scan_id = ' . $scan_id . ' AND file_size !=0 AND file_hash !="" ' ) ) { return 'No data for generating reports. Please run the Full Scan. After the scan is completed, the report will be generated.'; } $dup = array_unique( array_diff_assoc( $files, array_unique( $files ) ) ); rsort( $dup ); $list = array(); foreach ( $dup as $hash ) { if ( $fl = cerber_db_get_results( 'SELECT file_name, file_mtime, file_size FROM ' . cerber_get_db_prefix() . CERBER_SCAN_TABLE . ' WHERE scan_id = ' . $scan_id . ' AND file_hash = "' . $hash . '"' ) ) { $list = array_merge( $list, $fl ); } } $result = '<h3>Duplicate files</h3>'; return $result . crb_make_file_table( $list ); } function crb_scan_last_full() { $scans = cerber_db_get_col( 'SELECT DISTINCT scan_id FROM ' . cerber_get_db_prefix() . CERBER_SCAN_TABLE . ' WHERE scan_mode = 1 ORDER BY scan_id DESC LIMIT 1' ); if ( $scans ) { $scan_id = $scans[0]; $scan = cerber_get_scan( $scan_id ); if ( $scan['finished'] || ( $scan['aborted'] && $scan['next_step'] > 5 ) ) { // Step 5 is enough for analysis return $scan_id; } } return false; } /** * @param int $user_id * @param string $tab * @param bool $cache_only * * @return string */ function crb_generate_user_insights( $user_id, $tab, $cache_only = false ) { if ( $tab != 'activity' ) { $tab = 'traffic'; } $result = ''; $links = array(); if ( $user = get_userdata( $user_id ) ) { $labels = cerber_get_labels(); $list = array(); if ( $data = crb_q_cache_get( 'SELECT COUNT(activity) as cnt, activity FROM ' . CERBER_LOG_TABLE . ' WHERE user_id = ' . $user_id . ' GROUP BY activity', CERBER_LOG_TABLE, $cache_only ) ) { foreach ( $data as $item ) { $list[ $item[1] ] = 1; $links[] = array( array( 'filter_activity' => $item[1], 'filter_user' => $user_id ), crb_array_get( $labels, $item[1], 'Unknown' ) . ' - ' . $item[0] ); } } if ( $tab == 'activity' ) { if ( $data = crb_q_cache_get( 'SELECT COUNT(activity) as cnt, activity FROM ' . CERBER_LOG_TABLE . ' WHERE user_login = "' . $user->user_login . '" OR user_login = "' . $user->user_email . '" GROUP BY activity', CERBER_LOG_TABLE, $cache_only ) ) { foreach ( $data as $item ) { if ( isset( $list[ $item[1] ] ) ) { continue; } $links[] = array( array( 'filter_activity' => $item[1], 'filter_login' => $user->user_login . '|' . $user->user_email ), crb_array_get( $labels, $item[1], 'Unknown' ) . ' - ' . $item[0] ); } } } if ( $links ) { array_unshift( $links, array( array( 'filter_user' => $user_id ), __( 'All' ) ) ); $result = '<div id="crb-quick-insights">' . crb_make_nav_links( $links, $tab ) . '</div>'; } } if ( ! $result ) { if ( $cache_only ) { return ''; } $result = __( 'No activity has been logged yet.', 'wp-cerber' ); } return $result; } // Miscellaneous admin routines =========================================================== /** * Detects file extension * * @param string $file_name * * @return string */ function crb_get_extension( $file_name ) { $name = basename( $file_name ); if ( $name == '.htaccess' ) { return ''; } if ( false === ( $last_dot = strrpos( $name, '.' ) ) ) { return ''; } /*$first_dot = strpos( $name, '.' ); if ( $last_dot !== $first_dot && cerber_detect_exec_extension( $name ) ) { $ext = substr( $name, $first_dot + 1 ); } else { $ext = substr( $name, $last_dot + 1 ); }*/ $ext = substr( $name, $last_dot + 1 ); if ( ! $ext ) { $ext = ''; } return $ext; } function crb_make_file_table( $files ) { $result = array(); foreach ( $files as $file ) { $result[] = array( $file['file_name'], crb_size_format( $file['file_size'] ), cerber_date( $file['file_mtime'] ), ); } return cerber_make_table( $result, array( __( 'File Name', 'wp-cerber' ), __( 'Size', 'wp-cerber' ), __( 'Modified', 'wp-cerber' ), ), '', 'crb_align_right', 'crb-monospace crb-anchor-decorated' ); } /** * Generates an HTML table * * @param $heading * @param $rows * @param string $id * @param $class * @param string $body_class * @param string $head_class * @param bool $show_footer * * @return string * @since 8.6.4 * */ function cerber_make_table( $rows, $heading = array(), $id = '', $class = '', $body_class = '', $head_class = '', $show_footer = true ) { $tr = array(); foreach ( $rows as $row ) { $tr[] = '<td>' . implode( '</td><td>', $row ) . '</td>'; } $tfoot = ''; $thead = ''; if ( $heading ) { $titles = '<tr><th>' . implode( '</th><th>', $heading ) . '</th></tr>'; $thead = '<thead class="' . $head_class . '">' . $titles . '</thead>'; if ( $show_footer ) { $tfoot = '<tfoot class="' . $head_class . '">' . $titles . '</tfoot>'; } } $id = ( $id ) ? 'id="' . $id . '"' : ''; return '<table ' . $id . ' class="widefat crb-table cerber-margin ' . $class . '">' . $thead . $tfoot . '<tbody class="' . $body_class . '"><tr>' . implode( '</tr><tr>', $tr ) . '</tr></tbody></table>'; } function cerber_get_chmod( $file ) { return substr( sprintf( '%o', @fileperms( $file ) ), - 4 ); } function cerber_get_size( $file ) { $size = @filesize( $file ); return ( is_numeric( $size ) ) ? $size : 0; } function cerber_get_date( $file ) { $mtime = @filemtime( $file ); return ( is_numeric( $mtime ) ) ? $mtime : 0; } function crb_show_admin_announcement( $text = '', $big = true ) { $class = ( $big ) ? 'crb-cerber-logo-big' : 'crb-cerber-logo-small'; echo '<div class="cerber-msg crb-announcement ' . $class . '"><table><tr><td id="crb-ann-logo"></td>' . '<td id="crb-ann-text"><div>' . $text . '</div></td></tr></table></div>'; } /** * Returns GET (query string) params of the currently displayed page * * @return array */ function crb_get_referrer_params() { if ( cerber_is_wp_ajax() ) { $referrer = array(); if ( $ref_url = crb_get_request_field( 'referrer_page_url' ) ) { if ( $temp = parse_url( $ref_url, PHP_URL_QUERY ) ) { parse_str( $temp, $referrer ); } } } else { $referrer = crb_get_query_params(); } if ( ! is_array( $referrer ) ) { $referrer = array(); } array_walk_recursive( $referrer, function ( &$item ) { $item = preg_replace( '/[^\w\-.:*|@]/i', '', $item ); // Some sanitization } ); return $referrer; } add_filter( 'manage_application-passwords-user_columns', function ( $columns ) { end( $columns ); $key = key( $columns ); $last = array_pop( $columns ); return array_merge( $columns, array( 'app_cerber_log_ok' => __( 'Authorized', 'wp-cerber' ), 'app_cerber_log_fail' => __( 'Authorization Failed', 'wp-cerber' ), $key => $last ) ); } ); add_action( 'manage_application-passwords-user_custom_column', function ( $column_name, $item ) { global $user_id; if ( $column_name == 'app_cerber_log_ok' && ! empty( $item['last_ip'] ) ) { $link = cerber_activity_link( array( 4 ) ) . '&amp;filter_user=' . $user_id; echo '<a href="' . $link . '">View Log</a>'; } if ( $column_name == 'app_cerber_log_fail' ) { $user = get_user_by( 'ID', $user_id ); $link = cerber_activity_link( array( 8 ) ) . '&amp;filter_login=' . $user->user_email . '|' . $user->user_login; echo '<a href="' . $link . '">View Log</a>'; } }, 10, 2 ); /** * @param string $fname * @param string $ct Content-Type @since 8.6.9 */ function crb_file_headers( $fname, $ct = 'text/csv' ) { $fname = rawurlencode( $fname ); // encode non-ASCII symbols @ob_clean(); // This trick is crucial for some servers/environments (e.g. some IIS) header( "Content-Type: " . $ct ); header( "Content-Disposition: attachment; filename*=UTF-8''{$fname}" ); }