uploads_folder = wp_upload_dir( null, false );
$this->excludedFiles = array(
'.',
'..',
'.DS_Store',
);
}
/**
* Check which files are allowed to be executed in uploads folder
* @return array
*/
function check_execution(): array {
$this->status_key;
$this->log_entry( 'Checking if File Execution is enabled in uploads folder' );
//create a php file in uploads folder and check if it can be executed
$uploads_dir = $this->uploads_folder;
$result = array();
$time = time();
$php_file = $uploads_dir['basedir'] . DIRECTORY_SEPARATOR . $time . '.php';
$php_script = '';
$this->log_entry( 'Creating a dummy php file in uploads' );
file_put_contents( $php_file, $php_script );
//check response of calling the file
$url = $uploads_dir['baseurl'] . '/' . $time . '.php';
$this->log_entry( 'Retriving headers from dummy file' );
$headers = $this->get_curl_header( $url );
$this->log_entry( 'Deleting dummy file' );
unlink( $php_file );
if ( array_key_exists( 'x-one-executable', $headers ) ) {
$guide_link = sprintf( "", onecom_generic_locale_link( '', get_locale(), 1 ) );
$result = $this->format_result( $this->flag_open, __( 'File execution in uploads', 'onecom-wp' ), sprintf( 'File execution is allowed in your uploads folder. This means that an attacker can upload malware and execute it by simply trying to access it from their browser. %sDisable file execution in the WordPress uploads folder%s', $guide_link, '' ) );
} else {
$result = $this->format_result( $this->flag_resolved, __( 'File execution is blocked in "Uploads" folder.', 'onecom-wp' ), '' );
}
$this->log_entry( 'Finished checking for File Execution' );
//@todo oc_sh_save_result( 'file_execution', $result[ $this->status_key ] );
return $result;
}
/**
* Check the index of uploads directory, if the overall count of files
* is more than specified number, warn user
* @return array
*/
public function check_index(): array {
$source = $this->uploads_folder['basedir'];
$result = $this->count_files( $source );
$count = $result['count'];
$files_array = $result['counted_files'];
$get_single_file_scan = get_site_transient( 'onecom_uploads_single_folder_scan' );
$single_file_limit = ! empty( $get_single_file_scan ) ? json_decode( $get_single_file_scan, true ) : array();
$tran_scan_file_set = false;
$directory_tree = '';
//if transient value empty set flag false
if ( empty( $single_file_limit ) ) {
$tran_scan_file_set = true;
}
//if transient empty then check by traverse file
if ( $tran_scan_file_set ) {
$directory_tree = $this->get_directory_tree( $files_array );
$single_file_limit = array();
if ( count( $directory_tree ) > 0 ) {
$max = $this->desired_single_folder_count;
$single_file_limit = array_filter(
$directory_tree,
function ( $value ) use ( $max ) {
return ( $value >= $max );
}
);
}
//Set individual folder file limit
set_site_transient( 'onecom_uploads_single_folder_scan', json_encode( $single_file_limit ), 10 * HOUR_IN_SECONDS );
}
if ( $count >= $this->desired_file_count ) {
$directory_tree = $this->get_directory_tree( $files_array );
$result = $this->format_result( $this->flag_open, 'The index of uploads directory is huge', __( sprintf( 'The total file count (%s) of uploads directory exceeds the desired limits (%s). Following are some of the directories you can review.', $count, $this->desired_file_count ), 'onecom-wp' ) );
$result['file-list'] = array_slice( $directory_tree, 0, 3 );
} elseif ( ( $count <= $this->desired_file_count ) && ( count( $single_file_limit ) ) > 0 ) {
if ( $tran_scan_file_set === false ) {
$directory_tree = $this->get_directory_tree( $files_array );
}
$result = $this->format_result( $this->flag_open, 'The index of uploads directory is huge', __( sprintf( 'The total file count (%s) of uploads directory exceeds the desired limits (%s). Following are some of the directories you can review.', $count, $this->desired_file_count ), 'onecom-wp' ) );
$result['file-list'] = array_slice( $directory_tree, 0, 3 );
} else {
$result = $this->format_result( $this->flag_resolved, 'The index of uploads directory is optimal', __( sprintf( 'The total file count (%s) of uploads directory is within desired limits (%s).', $count, $this->desired_file_count ), 'onecom-wp' ) );
}
return $result;
}
/**
* Count the files and directories in a directory
*
* @param false $dir
*
* @return array
*/
private function count_files( $source ): array {
$count = 0;
$source = str_replace( '\\', '/', realpath( $source ) );
$files_array = array();
if ( is_dir( $source ) === true ) {
//use RecursiveDirectoryIterator to loop through nested subfolders
$files = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator( $source, RecursiveDirectoryIterator::SKIP_DOTS ),
RecursiveIteratorIterator::SELF_FIRST
);
foreach ( $files as $f ) {
if ( in_array( $f->getFilename(), $this->excludedFiles ) ) {
continue;
}
$files_array[] = $f;
$count++;
//break loop traversing if count reached at desired_file_count
if ( $count >= $this->desired_file_count ) {
break;
}
}
} elseif ( is_file( $source ) === true && ( ! in_array( basename( $source ), $this->excludedFiles ) ) ) {
$count++;
$files_array[] = $source;
}
return array(
'count' => $count,
'counted_files' => $files_array,
);
}
/**
* Count the top level files and directories
*
* @param array $files array of filesystem path names.
*
* @return array
*/
private function get_directory_tree( array $files ): array {
if ( ! $files ) {
return array();
}
$tree = array();
foreach ( $files as $file ) {
$path_name = $file->getPathName();
$file_name = $file->getFilename();
if ( ( ! is_dir( $path_name ) ) || in_array( $file_name, $this->excludedFiles ) ) {
continue;
}
$tree[ $path_name ] = ( count( scandir( $path_name ) ) - 2 );
}
//count the files in uploads basedir
$tree[ $this->uploads_folder['basedir'] ] = ( count( scandir( $this->uploads_folder['basedir'] ) ) - 2 );
arsort( $tree, SORT_NUMERIC );
return $tree;
}
/**
* Check if .zip files are present in root or uploads directories.
* @return array
*/
public function check_backup_zips(): array {
$files_in_root = glob( ABSPATH . '*.zip' );
$source = $this->uploads_folder['basedir'];
// Array of directory names to be excluded
$excludedDirectories = array( 'thrive-theme' );
$directory = new \RecursiveDirectoryIterator(
$source,
\FilesystemIterator::FOLLOW_SYMLINKS
);
$filter = new \RecursiveCallbackFilterIterator(
$directory,
function ( $current, $key, $iterator ) use ( $excludedDirectories ) {
$key;
$path = $current->getPathname();
// Check if the current item is inside any of the excluded directories
foreach ( $excludedDirectories as $excludedDir ) {
if ( stripos( $path, DIRECTORY_SEPARATOR . $excludedDir . DIRECTORY_SEPARATOR ) !== false ) {
return false; // Exclude ZIP files inside the excluded directories
}
}
return $current->getExtension() === 'zip' || $iterator->hasChildren();
}
);
$iterator = new \RecursiveIteratorIterator( $filter );
$nested_files = iterator_to_array( $iterator );
$nested_file_paths = array();
foreach ( $nested_files as $file ) {
$nested_file_paths[] = $file->getPathname();
}
$backup_files = array_merge( $files_in_root, $nested_file_paths );
if ( count( $backup_files ) > 0 ) {
$result = $this->format_result( $this->flag_open, 'Some archived files are present', 'We found some archived files (.zip) present in your site. You probably created them for backup. Consider cleaning them up.' );
$result['list'] = $backup_files;
} else {
$result = $this->format_result( $this->flag_resolved, 'No archived files present', '' );
}
return $result;
}
/**
* Delete a zip file
*
* @param string $file , the file to delete
*
* @return string
*/
public function fix_backup_zips( $file ): array {
$file_path = ABSPATH . $file;
$result = $this->format_result( $this->flag_open, __( 'Failed', 'onecom-wp' ) );
if ( ! $file || ! file_exists( $file_path ) ) {
return $result;
}
if ( unlink( $file_path ) ) {
$result = $this->format_result( $this->flag_resolved, __( 'Deleted', 'onecom-wp' ) );
}
return $result;
}
/**
* Check file permissions
* @return array
*/
public function check_permission(): array {
$this->log_entry( 'Scanning for WP file permissions.' );
clearstatcache();
$bad_permission = false;
$files = array_diff( scandir( ABSPATH ), array( '.', '..', '.DS_Store', '.tmb' ) );
foreach ( $files as $file ) {
$valid_permission = 755;
if ( is_dir( ABSPATH . DIRECTORY_SEPARATOR . $file ) ) {
$valid_permission = 755;
}
if ( $valid_permission < decoct( fileperms( ABSPATH . DIRECTORY_SEPARATOR . $file ) & 0777 ) ) {
$bad_permission = true;
}
}
if ( $bad_permission ) {
$guide_link = sprintf( "", onecom_generic_locale_link( '', get_locale(), 1 ) );
$status = $this->flag_open;
$title = __( 'WP file directory and files permissions', 'onecom-wp' );
$desc = sprintf( __( 'Your file and folder permissions are not set correctly. If they are too strict you get errors on your site, if they are too loose this poses a security risk. %sChange the file permissions via an FTP client%s', 'onecom-wp' ), $guide_link, '' );
} else {
$status = $this->flag_resolved;
$title = __( 'Correct file permissions.', 'onecom-wp' );
$desc = '';
}
// @todo oc_sh_save_result( 'file_permissions', $result[ $oc_hm_status ] );
$this->log_entry( 'Finished scanning for WP file permissions.' );
return $this->format_result( $status, $title, $desc );
}
/**
* Check if file editing is allowed in admin
* @return array
*/
public function check_file_editing(): array {
$this->log_entry( 'Checking if file editing enabled from admin' );
$file_editing_enabled = true;
if ( defined( 'DISALLOW_FILE_EDIT' ) && ( DISALLOW_FILE_EDIT ) ) {
$file_editing_enabled = false;
}
if ( ! $file_editing_enabled ) {
$title = __( 'File editing from WordPress admin is disabled', 'onecom-wp' );
$desc = '';
$status = $this->flag_resolved;
} else {
$guide_link = sprintf( "", onecom_generic_locale_link( '', get_locale(), 1 ) );
$title = __( 'File editing from WordPress admin is allowed', 'onecom-wp' );
$desc = sprintf( __( 'File editing from your WordPress dashboard is allowed, meaning users with a role that has this right can edit all the core files of your site. Someone might accidentally break it, or a hacker might get access to a password. %sDisable file editing in WordPress admin%s', 'onecom-wp' ), $guide_link, '' );
$status = $this->flag_open;
}
$this->log_entry( 'Finished checking for file editing enabled from admin' );
//@todo oc_sh_save_result( 'admin_file_edit', $result[ $oc_hm_status ] );
return $this->format_result( $status, $title, $desc );
}
}