diff --git a/uniforce/css/images/ui-icons_444444_256x240.png b/uniforce/css/images/ui-icons_444444_256x240.png new file mode 100644 index 0000000..ecfe4eb Binary files /dev/null and b/uniforce/css/images/ui-icons_444444_256x240.png differ diff --git a/uniforce/css/images/ui-icons_555555_256x240.png b/uniforce/css/images/ui-icons_555555_256x240.png new file mode 100644 index 0000000..580fcd1 Binary files /dev/null and b/uniforce/css/images/ui-icons_555555_256x240.png differ diff --git a/uniforce/css/images/ui-icons_777620_256x240.png b/uniforce/css/images/ui-icons_777620_256x240.png new file mode 100644 index 0000000..4c889d2 Binary files /dev/null and b/uniforce/css/images/ui-icons_777620_256x240.png differ diff --git a/uniforce/css/images/ui-icons_777777_256x240.png b/uniforce/css/images/ui-icons_777777_256x240.png new file mode 100644 index 0000000..b952d12 Binary files /dev/null and b/uniforce/css/images/ui-icons_777777_256x240.png differ diff --git a/uniforce/css/images/ui-icons_cc0000_256x240.png b/uniforce/css/images/ui-icons_cc0000_256x240.png new file mode 100644 index 0000000..9e23bc6 Binary files /dev/null and b/uniforce/css/images/ui-icons_cc0000_256x240.png differ diff --git a/uniforce/css/images/ui-icons_ffffff_256x240.png b/uniforce/css/images/ui-icons_ffffff_256x240.png new file mode 100644 index 0000000..ad42b0a Binary files /dev/null and b/uniforce/css/images/ui-icons_ffffff_256x240.png differ diff --git a/uniforce/css/settings-scanner.css b/uniforce/css/settings-scanner.css index 9c1a867..7f136dc 100644 --- a/uniforce/css/settings-scanner.css +++ b/uniforce/css/settings-scanner.css @@ -18,4 +18,5 @@ .spbc_view_file_row_wrapper:nth-child(odd) { background: #ccc; } .spbc_view_file_row_wrapper:nth-child(even) { background: #ddd; } .spbc_view_file_row_num { display: inline-block; width: 30px; margin: 0 0 0 5px; border-right: 1px solid black; } -.spbc_view_file_row { display: inline; margin: 0 0 0 5px; } \ No newline at end of file +.spbc_view_file_row { display: inline; margin: 0 0 0 5px; } +.spbc_view_file_row_wrapper_weak_spots { background-color: #f00; } \ No newline at end of file diff --git a/uniforce/css/settings.css b/uniforce/css/settings.css index a2972a1..ef53a9e 100644 --- a/uniforce/css/settings.css +++ b/uniforce/css/settings.css @@ -90,7 +90,9 @@ label.checkbox { vertical-align: middle; margin: 0 5px 3px 0; } - .ctusp_field input[type=text]{ + .ctusp_field input[type=text], + .ctusp_field input[type=password] + { padding: 6px 12px; border-radius: 4px; border: 1px #999 solid; diff --git a/uniforce/inc/actions.php b/uniforce/inc/actions.php index 4131589..8443ae3 100644 --- a/uniforce/inc/actions.php +++ b/uniforce/inc/actions.php @@ -48,6 +48,10 @@ usp_do_uninstall(); break; + case 'change_admin_password' : + usp_do_change_admin_password(); + break; + case 'spbc_tbl-action--row': call_user_func( '\Cleantalk\USP\Layout\ListTable::ajax__row_action_handler' ); break; diff --git a/uniforce/inc/admin.php b/uniforce/inc/admin.php index 0e34c55..c73593a 100644 --- a/uniforce/inc/admin.php +++ b/uniforce/inc/admin.php @@ -156,9 +156,9 @@ function usp_do_install() { * @param $exclusions */ function usp_install($files, $api_key, $cms, $exclusions ){ - + foreach ($files as $file){ - + $file_content = file_get_contents( $file ); // Check if short PHP tags used if( preg_match( "/<\?[^(php)]/", $file_content ) ) { @@ -172,13 +172,13 @@ function usp_install($files, $api_key, $cms, $exclusions ){ // Adding \n", 'start'); - + if( ! Err::check() ){ - + // Adding ? > to the end if it's not there if($php_open_tags <= $php_close_tags) File::inject__code($file, "\n$open_php_tag\n" . PHP_EOL, 'end'); - + if( ! Err::check() ){ // Addition to the top of the script @@ -188,9 +188,9 @@ function usp_install($files, $api_key, $cms, $exclusions ){ '(<\?php)|(<\?)', 'top_code' ); - + if( ! Err::check() ){ - + // Addition to index.php Bottom (JavaScript test) File::inject__code( $file, @@ -201,7 +201,7 @@ function usp_install($files, $api_key, $cms, $exclusions ){ 'end', 'bottom_code' ); - + } } } @@ -210,7 +210,7 @@ function usp_install($files, $api_key, $cms, $exclusions ){ // Install settings in cofig if everything is ok if( ! Err::check() ) usp_install_config( $files, $api_key, $cms, $exclusions ); - + // Set cron tasks if( ! Err::check() ) usp_install_cron(); @@ -247,31 +247,7 @@ function usp_install_config($modified_files, $api_key, $cms, $exclusions ){ if( Post::get( 'user_token' ) ) $usp->data->user_token = trim( Post::get( 'user_token' ) ); - $host = $_SERVER['HTTP_HOST'] ?: 'Your Site'; - $to = trim( Post::get( 'email' ) ); - $subject = 'UniForce settings password for ' . $host; - $message = "Hi,

- Your credentials to get access to settings of Uniforce (Universal security plugin by CleanTalk) are bellow,

- Login: $login
- Password: $pass
- Settings URL: https://$host/uniforce/
- Dashboard: https://cleantalk.org/my/?cp_mode=security

- --
- With regards,
- CleanTalk team."; - - $headers = 'MIME-Version: 1.0' . "\r\n"; - $headers .= 'Content-type: text/html; charset=iso-8859-1' . "\r\n"; - - // Sending password - if( trim( Post::get( 'email' ) ) && Post::get( 'admin_password' ) ){ - mail( - $to, - $subject, - $message, - $headers - ); - } + usp_send_pass_to_email(trim(Post::get('email')), $login, $pass); if( Post::get( 'account_name_ob' ) ) $usp->data->account_name_ob = trim( Post::get( 'account_name_ob' ) ); @@ -289,9 +265,40 @@ function usp_install_config($modified_files, $api_key, $cms, $exclusions ){ $usp->plugin_meta->is_installed = true; $usp->plugin_meta->version = SPBCT_VERSION; + if ( empty($usp->plugin_meta->latest_version) ) { + $updater = new \Cleantalk\USP\Updater\Updater(CT_USP_ROOT); + $usp->plugin_meta->latest_version = $updater->getLatestVersion(); + } $usp->plugin_meta->save(); } +function usp_send_pass_to_email($to, $login, $pass) +{ + $host = $_SERVER['HTTP_HOST'] ?: 'Your Site'; + //$to = trim( Post::get( 'email' ) ); + $subject = 'UniForce settings password for ' . $host; + $message = "Hi,

+ Your credentials to get access to settings of Uniforce (Universal security plugin by CleanTalk) are bellow,

+ Login: $login
+ Password: $pass
+ Settings URL: https://$host/uniforce/
+ Dashboard: https://cleantalk.org/my/?cp_mode=security

+ --
+ With regards,
+ CleanTalk team."; + + $headers = 'MIME-Version: 1.0' . "\r\n"; + $headers .= 'Content-type: text/html; charset=iso-8859-1' . "\r\n"; + + // Sending password + mail( + $to, + $subject, + $message, + $headers + ); +} + /** * Modify cron */ @@ -312,7 +319,7 @@ function usp_install_cron(){ * @return bool */ function usp_uninstall(){ - + $usp = State::getInstance(); foreach ( $usp->data->modified_files as $file ){ @@ -323,6 +330,7 @@ function usp_uninstall(){ // Deleting FW data $db = new \Cleantalk\USP\File\FileDB( 'fw_nets' ); $db->delete(); + $db->deleteTemp(); // Deleting options and their files $usp->delete( 'data' ); @@ -332,7 +340,7 @@ function usp_uninstall(){ $usp->delete( 'signatures' ); $usp->delete( 'fw_stats' ); $usp->delete( 'plugin_meta' ); - + $usp->delete( 'bfp_blacklist' ); $usp->delete( 'bfp_blacklist_fast' ); @@ -342,7 +350,7 @@ function usp_uninstall(){ // Deleting any logs usp_uninstall_logs(); - setcookie('authentificated', 0, time()-86400, '/', null, false, true); + setcookie('authentificated', 0, time()-86400, '/', '', false, true); return ! Err::check(); @@ -375,12 +383,12 @@ function usp_uninstall_logs() { * @return array */ function usp_detect_cms($path_to_index, $out = array( 'name' => 'Unknown', 'admin_page' => '' ) ){ - + if( is_file($path_to_index) ){ - + // Detecting CMS $index_file = file_get_contents( $path_to_index ); - + //X-Cart 4 if (preg_match('/(xcart_4_.*?)/', $index_file)) $out = array( 'name' => 'X-Cart 4', 'admin_page' => '' ); @@ -422,7 +430,7 @@ function usp_detect_cms($path_to_index, $out = array( 'name' => 'Unknown', 'admi $out = array( 'name' => 'phpBB', 'admin_page' => '/' ); } - + return $out; } @@ -444,10 +452,10 @@ function usp_do_login($apikey, $password, $email ) { if( $password ){ if( ( Post::get( 'login' ) == $apikey || Post::get( 'login' ) === $email ) && hash( 'sha256', trim( Post::get( 'password' ) ) ) == $password ) - setcookie('authentificated', State::getInstance()->data->security_key, 0, '/', null, false, true); + setcookie('authentificated', State::getInstance()->data->security_key, 0, '/', '', false, true); else Err::add('Incorrect login or password'); - + // No match }else Err::add('Incorrect login'); @@ -463,7 +471,7 @@ function usp_do_login($apikey, $password, $email ) { */ function usp_do_logout() { - setcookie('authentificated', 0, time()-86400, '/', null, false, true); + setcookie('authentificated', 0, time()-86400, '/', '', false, true); die( json_encode( array( 'success' => true ) ) ); } @@ -484,10 +492,10 @@ function usp_do_save_settings() { : $value; settype($settings[$setting], gettype($value)); } unset($setting, $value); - + // Recognizing new key $new_key_is_set = $usp->settings->key !== $settings['key']; - + // Set values foreach ( $settings as $setting => $value) { $usp->settings->$setting = $value; @@ -495,7 +503,7 @@ function usp_do_save_settings() { // validate the new key $usp->data->key_is_ok = usp_check_account_status(); - + // BFP actions if( $usp->settings->key ){ @@ -507,27 +515,27 @@ function usp_do_save_settings() { $usp->data->stat->bfp->count = 0; } } - + if( $new_key_is_set ){ $scanner_controller = new \Cleantalk\USP\ScannerController( CT_USP_SITE_ROOT ); $scanner_controller->action__scanner__create_db(); } - + // Update signatures if( $usp->settings->scanner_signature_analysis ){ $scanner_controller = new \Cleantalk\USP\ScannerController( CT_USP_SITE_ROOT ); $scanner_controller->action__scanner__get_signatures(); } - + $usp->data->save(); $usp->settings->save(); - + // FireWall actions // Last in the list because it can overwrite the data in the the remote call it makes if( ( $usp->settings->fw || $usp->settings->waf ) && $usp->settings->key ){ - + // Update SFW Helper::http__request( Server::get('HTTP_HOST') . CT_USP_AJAX_URI, @@ -539,21 +547,22 @@ function usp_do_save_settings() { ), 'get async' ); - + // Send FW logs $result = \Cleantalk\USP\Uniforce\Firewall\FW::send_log( $usp->settings->key ); - + if( empty( $result['error'] ) && ! Err::check() ) { $usp->fw_stats->logs_sent_time = time(); $usp->fw_stats->logs_sent_amount = $result['rows']; $usp->fw_stats->save(); } - + // Cleaning up Firewall data } else { // Deleting FW data $db = new \Cleantalk\USP\File\FileDB( 'fw_nets' ); $db->delete(); + $db->deleteTemp(); State::getInstance()->data->save(); Cron::removeTask( 'sfw_update' ); Cron::removeTask( 'fw_send_logs' ); @@ -614,7 +623,7 @@ function usp_check_account_status( $key = null ){ $usp->data->save(); $usp->settings->save(); - return $usp->valid; + return $usp->data->valid; } /** @@ -623,10 +632,55 @@ function usp_check_account_status( $key = null ){ */ function usp_do_uninstall() { - setcookie('authentificated', 0, time()-86400, '/', null, false, true); + setcookie('authentificated', 0, time()-86400, '/', '', false, true); usp_uninstall(); Err::check() or die(json_encode(array('success' => true))); die(Err::check_and_output( 'as_json' )); -} \ No newline at end of file +} + +/** + * AJAX handler for the changing admin password logic + * + * @return string json + */ +function usp_do_change_admin_password() +{ + $usp = State::getInstance(); + + // Changing password logic + // 1 if the fields not empty + if ( Post::get('old_password') && Post::get('new_password') && Post::get('new_password_confirm') ) { + + // 2 if the old password is right + if ( $usp->data->password !== hash( 'sha256', trim(Post::get('old_password'))) ) { + Err::add('Changing admin password', 'The old password is wrong'); + die(Err::check_and_output( 'as_json' )); + } + + // 3 if the password is too short + if ( strlen(Post::get('new_password')) < 8 ) { + Err::add('Changing admin password', 'Password must be more than 8 characters'); + die(Err::check_and_output( 'as_json' )); + } + + // 4 if the new password confirmed + if ( Post::get('new_password') !== Post::get('new_password_confirm') ) { + Err::add('Changing admin password', 'New password is not confirmed'); + die(Err::check_and_output( 'as_json' )); + } + + // 5 save the new password + $usp->data->password = hash('sha256', trim(Post::get('new_password'))); + $usp->data->save(); + + usp_send_pass_to_email($usp->data->email, $usp->data->email, Post::get('new_password')); + + } else { + Err::add('Changing admin password', 'All fields are required'); + } + + Err::check() or die(json_encode(array('success' => true))); + die(Err::check_and_output( 'as_json' )); +} diff --git a/uniforce/inc/common.php b/uniforce/inc/common.php index 42f03ff..7f6bfc7 100644 --- a/uniforce/inc/common.php +++ b/uniforce/inc/common.php @@ -5,15 +5,16 @@ * * Sets all main constants * - * Version: 3.7.0 + * Version: 3.8.0 */ +use Cleantalk\USP\Common\Err; use Cleantalk\USP\Common\State; use Cleantalk\USP\Variables\Server; use Cleantalk\USP\Common\RemoteCalls; if( ! defined( 'SPBCT_PLUGIN' ) ) define( 'SPBCT_PLUGIN', 'uniforce' ); -if( ! defined( 'SPBCT_VERSION' ) ) define( 'SPBCT_VERSION', '3.7.0' ); +if( ! defined( 'SPBCT_VERSION' ) ) define( 'SPBCT_VERSION', '3.8.0' ); if( ! defined( 'SPBCT_AGENT' ) ) define( 'SPBCT_AGENT', SPBCT_PLUGIN . '-' . str_replace( '.', '', SPBCT_VERSION ) ); if( ! defined( 'SPBCT_USER_AGENT' ) ) define( 'SPBCT_USER_AGENT', 'Cleantalk-Security-Universal-Plugin/' . SPBCT_VERSION ); @@ -66,4 +67,9 @@ unset( $cron ); // Accept remote calls -RemoteCalls::check() && RemoteCalls::perform(); +try { + RemoteCalls::check() && RemoteCalls::perform(); +} catch (\Exception $e) { + Err::add('Failed to perform test remote call: ' . $e->getMessage()); + exit; +} diff --git a/uniforce/inc/cron_functions.php b/uniforce/inc/cron_functions.php index ce0fa9f..98133e6 100644 --- a/uniforce/inc/cron_functions.php +++ b/uniforce/inc/cron_functions.php @@ -13,6 +13,9 @@ function uniforce_fw_update( $immediate = false ){ // SFW actions if( $usp->key && $usp->settings->fw ){ + State::getInstance()->fw_stats->updating = false; + State::getInstance()->fw_stats->save(); + // Update SFW Helper::http__request( Server::get('HTTP_HOST') . CT_USP_AJAX_URI, diff --git a/uniforce/inc/functions.php b/uniforce/inc/functions.php index 03ff60e..bbd2ac0 100644 --- a/uniforce/inc/functions.php +++ b/uniforce/inc/functions.php @@ -1,6 +1,7 @@ /**/"; -} \ No newline at end of file +} + +function usp__is_admin() +{ + return Cookie::get( 'authentificated' ) === State::getInstance()->data->security_key; +} diff --git a/uniforce/inc/scanner.php b/uniforce/inc/scanner.php index 00a8087..3cb5988 100644 --- a/uniforce/inc/scanner.php +++ b/uniforce/inc/scanner.php @@ -17,28 +17,28 @@ * @return array|bool|mixed|string[] */ function spbc_scanner_file_send( $file_id = null ){ - + $file_id = $file_id ?: Post::get('file_id', 'hash'); - + if($file_id){ - + if( State::getInstance()->data->no_sql ) return spbc_scanner_file_send___no_sql( $file_id ); - + $usp = State::getInstance(); $db = DB::getInstance( $usp->data->db_request_string, $usp->data->db_user, $usp->data->db_password ); - + $root_path = substr(CT_USP_SITE_ROOT, 0 ,-1); - + // Getting file info. $file_info = $db->fetch_all('SELECT *' .' FROM scanner_files' .' WHERE fast_hash = "' . $file_id . '"')[0]; - + // Scan file before send it // Heuristic $result_heur = Scanner::file__scan__heuristic($root_path, $file_info); @@ -46,7 +46,7 @@ function spbc_scanner_file_send( $file_id = null ){ $output = array('error' =>'RESCACNNING_FAILED'); die(json_encode($output)); } - + // Signature $signatures = new Storage('signatures', null, '', 'csv', array( 'id', @@ -58,7 +58,12 @@ function spbc_scanner_file_send( $file_id = null ){ 'cci' ) ); $signatures = $signatures->convertToArray(); - $result_sign = Scanner::file__scan__for_signatures($root_path, $file_info, $signatures); + $decoded_signatures = array(); + foreach ($signatures as $signature => $value){ + $decoded_signatures[$signature] = $value; + $decoded_signatures[$signature]['body'] = base64_decode($signature['body']); + } + $result_sign = Scanner::file__scan__for_signatures($root_path, $file_info, $decoded_signatures); if(!empty($result['error'])){ $output = array('error' =>'RESCACNNING_FAILED'); die(json_encode($output)); @@ -130,50 +135,50 @@ function spbc_scanner_file_send( $file_id = null ){ * @return array|bool[]|mixed|string|string[] */ function spbc_scanner_file_delete( $file_id = false ){ - + $file_id = $file_id ?: Post::get('file_id', 'hash'); - + if($file_id){ - + if( State::getInstance()->data->no_sql ) return spbc_scanner_file_delete___no_sql( $file_id ); - + $usp = State::getInstance(); $db = DB::getInstance( $usp->data->db_request_string, $usp->data->db_user, $usp->data->db_password ); - + $root_path = substr(CT_USP_SITE_ROOT, 0 ,-1); - + // Getting file info. $file_info = $db->fetch_all('SELECT *' .' FROM scanner_files' .' WHERE fast_hash = "' . $file_id . '"')[0]; if(!empty($file_info)){ - + $file_path = $file_info['status'] == 'QUARANTINED' ? $file_info['q_path'] : $root_path.$file_info['path']; - + if(file_exists($root_path.$file_info['path'])){ if(is_writable($root_path.$file_info['path'])){ - + // Getting file && API call $remeber = file_get_contents($file_path); $result = unlink($file_path); if($result){ - + $response = Helper::http__request( CT_USP_URI, array(), 'dont_split_to_array get', array( CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_2_0, ) ); - + if( empty( $response['error'] ) ){ - + if( Helper::http__request__get_response_code( CT_USP_URI ) && ! Helper::search_page_errors( $response ) ){ // Deleting row from DB $db->execute('DELETE FROM scanner_files WHERE fast_hash = "'.$file_id.'"'); @@ -210,7 +215,7 @@ function spbc_scanner_file_delete( $file_id = false ){ function spbc_scanner_file_view( $file_id = false ){ $file_id = $file_id ?: Post::get('file_id', 'hash'); - + if($file_id){ $usp = State::getInstance(); @@ -227,11 +232,11 @@ function spbc_scanner_file_view( $file_id = false ){ $file_info = $db->fetch_all('SELECT *' .' FROM scanner_files' .' WHERE fast_hash = "' . $file_id . '"')[0]; - + if ( ! empty( $file_info ) ) { - + $file_path = $file_info['status'] == 'QUARANTINED' ? $file_info['q_path'] : $root_path.$file_info['path']; - + if ( file_exists( $file_path ) ) { if ( is_readable( $file_path ) ) { @@ -284,7 +289,7 @@ function spbc_scanner__display__prepare_data__files( &$table ){ 'cci' ) ); $signatures = $signatures->convertToArray(); - + if($table->items_count){ $root = substr(CT_USP_SITE_ROOT, 0, -1); @@ -298,7 +303,7 @@ function spbc_scanner__display__prepare_data__files( &$table ){ unset($row->actions['view_bad']); if( isset( $row->status ) && $row->status === 'quarantined' ) unset($row->actions['quarantine']); - + $table->items[] = array( 'cb' => $row->fast_hash, 'uid' => $row->fast_hash, @@ -310,7 +315,7 @@ function spbc_scanner__display__prepare_data__files( &$table ){ : $root . $row->path, 'actions' => $row->actions, ); - + if(isset($row->weak_spots)){ $weak_spots = json_decode($row->weak_spots, true); if($weak_spots){ @@ -463,17 +468,17 @@ function usp_scanner__display(){ // Progressbar echo '
'; - + if( $usp->data->stat->scanner->last_scan ){ - + $db = DB::getInstance( $usp->data->db_request_string, $usp->data->db_user, $usp->data->db_password ); - + if( $db ){ - + $table = new ListTable( $db, array( @@ -518,7 +523,7 @@ function usp_scanner__display(){ 'order_by' => array( 'path' => 'asc' ), ) ); - + $table->get_data() ->display(); } @@ -538,7 +543,7 @@ function usp_scanner__display__get_data__files___no_sql( $offset = 0, $limit = 2 } function usp_scanner__display__prepare_data__files___no_sql( &$table ){ - + $usp = State::getInstance(); $signatures = new Storage('signatures', null, '', 'csv', array( 'id', @@ -550,13 +555,13 @@ function usp_scanner__display__prepare_data__files___no_sql( &$table ){ 'cci' ) ); $signatures = $signatures->convertToArray(); - + if($table->items_count){ - + $root = substr(CT_USP_SITE_ROOT, 0, -1); - + foreach($table->rows as $key => $row){ - + // Filtering row actions if( ( isset( $row->last_sent ) && $row->last_sent > $row->mtime ) || $row->size == 0 || $row->size > 1048570) unset($row->actions['send']); @@ -564,7 +569,7 @@ function usp_scanner__display__prepare_data__files___no_sql( &$table ){ unset($row->actions['view_bad']); if( isset( $row->status ) && $row->status === 'quarantined' ) unset($row->actions['quarantine']); - + $table->items[] = array( 'cb' => $row->fast_hash, 'uid' => $row->fast_hash, @@ -576,14 +581,14 @@ function usp_scanner__display__prepare_data__files___no_sql( &$table ){ : $root . $row->path, 'actions' => $row->actions, ); - + if(isset($row->weak_spots)){ $weak_spots = json_decode($row->weak_spots, true); if($weak_spots){ if(!empty($weak_spots['SIGNATURES'])){ foreach ($weak_spots['SIGNATURES'] as $string => $weak_spot_in_string) { foreach ($weak_spot_in_string as $weak_spot) { - + $index = array_search( $weak_spot, array_column($signatures, 'id') @@ -627,7 +632,7 @@ function usp_scanner__display__prepare_data__files___no_sql( &$table ){ } }else $ws_string = ''; - + $table->items[$key]['weak_spots'] = $ws_string; } } @@ -635,45 +640,45 @@ function usp_scanner__display__prepare_data__files___no_sql( &$table ){ } function usp_scanner__display___no_sql(){ - + $usp = State::getInstance(); - + // Key is bad if(!$usp->valid) { - + $button = ''; $link = sprintf( '%s', $button ); echo '

' . __( 'Please, enter valid API key.', 'security-malware-firewall' ) . '

' . $link . '
'; - + return; } - + // Key is ok if ( $usp->valid && ! $usp->moderate ) { - + $button = ''; $link = sprintf( '%s', $usp->user_token, $button ); echo '

' . __( 'Please renew your security license.', 'security-malware-firewall' ) . '

' . $link . '
'; - + return; } - + // Key is ok if ( ! $usp->settings->scanner_heuristic_analysis && ! $usp->settings->scanner_signature_analysis ) { - + $button = ''; $link = sprintf( '%s', $button ); echo '

' . __( 'All types of scannig is switched off, please, enable at least one.', 'security-malware-firewall' ) . '

' . $link . '
'; - + return; } - + // Info about last scanning echo '

'; if( !$usp->data->stat->scanner->last_scan ) @@ -686,10 +691,10 @@ function usp_scanner__display___no_sql(){ date( 'M d Y H:i:s', $usp->data->stat->scanner->last_scan ), $usp->data->stat->scanner->last_scan_amount ); - + } echo '

'; - + // Statistics link echo '

'; echo sprintf( @@ -698,7 +703,7 @@ function usp_scanner__display___no_sql(){ '' ); echo '

'; - + // Start scan button echo '
' .'
'; echo '
'; - + echo ''; // Stages echo '
'; - + echo '' .__('Preparing', 'security-malware-firewall') .' -> ' .'' .__('Counting files', 'security-malware-firewall') .' -> '; if ( $usp->settings->scanner_signature_analysis ) @@ -721,14 +726,14 @@ function usp_scanner__display___no_sql(){ if ( $usp->settings->scanner_heuristic_analysis ) echo ''.__('Heuristic analysis', 'security-malware-firewall').' -> '; echo ''.__('Sending results', 'security-malware-firewall').''; - + echo '
'; - + echo '
'; - + // Progressbar echo '
'; - + $table = new ListTable( NULL, array( @@ -766,7 +771,7 @@ function usp_scanner__display___no_sql(){ 'order_by' => array('path' => 'asc'), ) ); - + $table->get_data(); $table->display(); } @@ -779,15 +784,15 @@ function usp_scanner__display___no_sql(){ * @return array|bool|mixed|string[] */ function spbc_scanner_file_send___no_sql( $file_id = false ){ - + $usp = State::getInstance(); - + $root_path = substr(CT_USP_SITE_ROOT, 0 ,-1); - + $file_id = $file_id ?: Post::get('file_id', 'hash'); - + if($file_id){ - + // Getting file info. $index = array_search( $file_id, @@ -829,17 +834,17 @@ function spbc_scanner_file_send___no_sql( $file_id = false ){ // ); // $file_info['weak_spots'] = $result['weak_spots']; // $file_info['full_hash'] = md5_file($root_path.$file_info['path']); - + if(!empty($file_info)){ if(file_exists($root_path.$file_info['path'])){ if(is_readable($root_path.$file_info['path'])){ if(filesize($root_path.$file_info['path']) > 0){ if(filesize($root_path.$file_info['path']) < 1048570){ - + // Getting file && API call $file = file_get_contents($root_path.$file_info['path']); $result = API::method__security_mscan_files($usp->settings->key, $file_info['path'], $file, $file_info['full_hash'], $file_info['weak_spots']); - + if(empty($result['error'])){ if($result['result']){ @@ -866,7 +871,7 @@ function spbc_scanner_file_send___no_sql( $file_id = false ){ $output = array('error' =>'FILE_NOT_FOUND'); }else $output = array('error' =>'WRONG_FILE_ID'); - + return $output; } @@ -878,37 +883,37 @@ function spbc_scanner_file_send___no_sql( $file_id = false ){ * @return bool[]|string[] */ function spbc_scanner_file_delete___no_sql( $file_id = false ){ - + $usp = State::getInstance(); - + $root_path = substr(CT_USP_SITE_ROOT, 0 ,-1); - + $file_id = $file_id ?: Post::get('file_id', 'hash'); - + if($file_id){ - + // Getting file info. $index = array_search( $file_id, array_column($usp->scan_result->convertToArray(), 'fast_hash') ); $file_info = $usp->scan_result->$index; - + if(!empty($file_info)){ if(file_exists($root_path.$file_info['path'])){ if(is_writable($root_path.$file_info['path'])){ - + // Getting file && API call $result = unlink($root_path.$file_info['path']); - + if($result){ - + // Deleting row from DB unset($usp->scan_result->$index); $usp->scan_result->save(); - + $output = array('success' => true); - + }else $output = array('error' =>'FILE_COULDNT_DELETE'); }else @@ -919,7 +924,7 @@ function spbc_scanner_file_delete___no_sql( $file_id = false ){ $output = array('error' =>'FILE_NOT_FOUND'); }else $output = array('error' =>'WRONG_FILE_ID'); - + return $output; } @@ -929,36 +934,36 @@ function spbc_scanner_file_delete___no_sql( $file_id = false ){ * @param bool|string $file_id */ function spbc_scanner_file_view___no_sql( $file_id = false ){ - + $file_id = $file_id ?: Post::get('file_id', 'hash'); - + if($file_id){ - + $root_path = substr(CT_USP_SITE_ROOT, 0 ,-1); $usp = State::getInstance(); - + // Getting file info. $index = array_search( $file_id, array_column($usp->scan_result->convertToArray(), 'fast_hash') ); $file_info = $usp->scan_result->$index; - + if ( ! empty( $file_info ) ) { if ( file_exists( $root_path . $file_info['path'] ) ) { if ( is_readable( $root_path . $file_info['path'] ) ) { - + // Getting file && API call $file = file( $root_path . $file_info['path'] ); - + if($file !== false && count($file)){ - + $file_text = array(); for($i=0; isset($file[$i]); $i++){ $file_text[$i+1] = htmlspecialchars($file[$i]); $file_text[$i+1] = preg_replace("/[^\S]{4}/", " ", $file_text[$i+1]); } - + if(!empty($file_text)){ $output = array( 'success' => true, @@ -967,7 +972,7 @@ function spbc_scanner_file_view___no_sql( $file_id = false ){ 'difference' => $file_info['difference'], 'weak_spots' => $file_info['weak_spots'] ); - + }else $output = array('error' =>'FILE_TEXT_EMPTY'); }else @@ -980,6 +985,6 @@ function spbc_scanner_file_view___no_sql( $file_id = false ){ $output = array('error' =>'FILE_NOT_FOUND'); }else $output = array('error' =>'WRONG_FILE_ID'); - + die(json_encode( $output, true )); -} \ No newline at end of file +} diff --git a/uniforce/inc/settings.php b/uniforce/inc/settings.php index e07ea29..6b727f7 100644 --- a/uniforce/inc/settings.php +++ b/uniforce/inc/settings.php @@ -13,16 +13,22 @@ function ctusp_settings__show_modified_files(){ } } -function usp_settings__plugin_state(){ - $usp = State::getInstance(); - if( version_compare( $usp->plugin_meta->version, $usp->plugin_meta->latest_version ) === -1 ){ - echo '

There is a newer version. Update to the latest ' . $usp->plugin_meta->latest_version . '

'; - echo '

'; - }elseif( version_compare( $usp->plugin_meta->version, $usp->plugin_meta->latest_version ) === 1 ){ - echo '

You are using more than the latest version '. $usp->plugin_meta->version . '

'; - }else{ - echo '

You are using the latest version '. $usp->plugin_meta->version . '

'; - } +function usp_settings__plugin_state() +{ + $usp = State::getInstance(); + $usp->plugin_meta->latest_version; + if (! empty($usp->plugin_meta->latest_version) ) { + if ( version_compare($usp->plugin_meta->version, $usp->plugin_meta->latest_version) === -1 ) { + echo '

There is a newer version. Update to the latest ' . $usp->plugin_meta->latest_version . '

'; + echo '

'; + } elseif ( version_compare($usp->plugin_meta->version, $usp->plugin_meta->latest_version) === 1 ) { + echo '

You are using more than the latest version ' . $usp->plugin_meta->version . ' < ' . $usp->plugin_meta->latest_version . '

'; + } else { + echo '

You are using the latest version ' . $usp->plugin_meta->version . '

'; + } + } else { + echo '

Can\'t check module version updates. You use version ' . $usp->plugin_meta->version. '

'; + } } function usp_settings__show_fw_statistics( $out = '' ) @@ -56,24 +62,24 @@ function usp_settings__show_fw_statistics( $out = '' ) } function usp_settings__show_scanner_statistics(){ - + $usp = State::getInstance(); $stat = State::getInstance()->data->stat; - + if( State::getInstance()->data->no_sql ) echo ''; - + echo 'Last scan: ' . ( $stat->scanner->last_scan ? date('M d Y H:i:s', $stat->scanner->last_scan) : 'never' ) . '
'; echo 'Number of scanned files at the last scan: ' . $stat->scanner->last_scan_amount . '
'; - + echo '
'; - + echo 'Signature last update: ' . ( $stat->scanner->signature_last_update ? date('M d Y H:i:s', $stat->scanner->signature_last_update) : 'never.' ) . '
'; echo 'Signatures in local base: ' . $stat->scanner->signature_entries . '.
'; - -} \ No newline at end of file + +} diff --git a/uniforce/index.php b/uniforce/index.php index 8561766..1bba9fb 100644 --- a/uniforce/index.php +++ b/uniforce/index.php @@ -1,6 +1,6 @@ x[1]) + var window_height = window.innerHeight; var row_template = '
%s

%s


'; + var row_template_weak_spots = '
%s

%s


'; jQuery('#spbc_dialog').empty(); for(row in result.file){ - jQuery('#spbc_dialog').append(row_template.printf(row, result.file[row])); + if (weak_spots.includes(row)) { + jQuery('#spbc_dialog').append(row_template_weak_spots.printf(row, result.file[row])); + } else { + jQuery('#spbc_dialog').append(row_template.printf(row, result.file[row])); + } } - var content_height = Object.keys(result.file).length * 19 + 19, - visible_height = (document.documentElement.clientHeight) / 10 * 75; - content_height = content_height < 76 ? 76 : content_height; - var overflow = content_height < visible_height ? 'no_scroll' : 'scroll'; + let content_height = Object.keys(result.file).length * 19 < 76 ? 76 : Object.keys(result.file).length * 19 + 19, + visible_height = (window.screen.availHeight/100) * 75, + overflow = content_height < visible_height ? 'hidden' : 'scroll', + height = overflow === 'scroll' ? visible_height : content_height; + + jQuery('#spbc_dialog').css({ + height: height, + overflow: overflow, + }) - jQuery('#spbc_dialog').data('overflow', overflow); jQuery('#spbc_dialog').dialog({ modal:true, - title: result.file_path, - position: { my: "center", at: "center" , of: window }, + title: ('Loaded: ' + result.file_path), + position: { my: "center top", at: "center top+100px" , of: window }, width: +(jQuery('body').width() / 100 * 70), - height: overflow === 'scroll' ? visible_height : content_height, - // minHeight: 300, show: { effect: "blind", duration: 500 }, + maxHeight: visible_height, draggable: true, + resizable: false, closeText: "Close", open: function(event, ui) { - console.log(jQuery(event.target).data('overflow')); - document.body.style.overflow = 'hidden'; - if(jQuery(event.target).data('overflow') == 'scroll') event.target.style.overflow = 'scroll'; + event.target.style.overflow = overflow; + jQuery('#spbc_dialog').height(height); + jQuery('.ui-widget-overlay').on('click', function() { + jQuery("#spbc_dialog").dialog('close'); + }); + }, + beforeClose: function(event, ui) { + document.body.style.overflow = 'auto'; + jQuery('#spbc_dialog').empty(); }, - beforeClose: function(event, ui) { document.body.style.overflow = 'auto'; }, }); } @@ -100,4 +116,4 @@ jQuery(document).ready(function(){ spbc_scanner.start(); }); -}); \ No newline at end of file +}); diff --git a/uniforce/js/settings.js b/uniforce/js/settings.js index 2793ca8..d415bac 100644 --- a/uniforce/js/settings.js +++ b/uniforce/js/settings.js @@ -23,6 +23,11 @@ jQuery(document).ready(function() { uninstall(); }); + // Change admin password + $("#ctusp_field---change_admin_password").on('click', function(event){ + changeAdminPassword(); + }); + // Update // Logout $("#btn-update").on('click', function(event){ @@ -169,6 +174,55 @@ function uninstall(){ } +function changeAdminPassword() { + const newPassword = $('#ctusp_field---new_password').val(); + if ( newPassword.length < 8 ) { + $("body").overhang({ + type: "error", + message: 'Error: Password must be more than 8 characters', + duration: 43200, + overlay: true, + closeConfirm: true, + easing: 'linear' + }); + return; + } + ctAJAX( + { + data: { + action: 'change_admin_password', + old_password: $('#ctusp_field---old_password').val(), + new_password: newPassword, + new_password_confirm: $('#ctusp_field---new_password_confirm').val(), + }, + successCallback: function(result, data, params, obj) { + if (result.success) { + $("body").overhang({ + type: "success", + message: "New password saved!", + duration: 3, + overlay: true, + // closeConfirm: true, + easing: 'linear' + }); + } + }, + spinner: $('#ctusp_field---change_admin_password+div>.preloader'), + button: $('#ctusp_field---change_admin_password'), + errorOutput: function( msg ){ + $("body").overhang({ + type: "error", + message: 'Error: ' + msg, + duration: 43200, + overlay: true, + closeConfirm: true, + easing: 'linear' + }); + } + } + ); +} + function update(){ ctAJAX({ data: { action: 'update'}, diff --git a/uniforce/lib/Cleantalk/USP/Common/API.php b/uniforce/lib/Cleantalk/USP/Common/API.php index 6e98fba..5408e7c 100644 --- a/uniforce/lib/Cleantalk/USP/Common/API.php +++ b/uniforce/lib/Cleantalk/USP/Common/API.php @@ -380,6 +380,7 @@ static public function method__security_firewall_data_file($api_key, $out = null $request = array( 'auth_key' => $api_key, 'method_name' => 'security_firewall_data_file', + 'version' => 2 ); if( $out ) @@ -652,15 +653,22 @@ static public function send_request($data, $url = self::URL, $ssl = false) $data['agent'] = static::get_agent(); // Add ssl to 'presets' if enabled - if( $ssl ) - array_push( $presets, 'ssl' ); - + if( $ssl ){ + array_push( $presets, 'ssl' ); + } + $result = Helper::http__request( $url, $data, $presets ); // Retry with SSL enabled if failed - if( ! empty ( $result['error'] ) && $ssl === false ) - $result = Helper::http__request( $url, $data, 'api ssl' ); - + if( ! empty ( $result['error'] ) && $ssl === false ) { + $result = Helper::http__request( $url, $data, 'api ssl' ); + } + + //Retry with HTTP 20 if failed + if( ! empty ( $result['error'] )) { + $result = Helper::http__request( $url, $data, 'api http_20' ); + } + return $result; } diff --git a/uniforce/lib/Cleantalk/USP/Common/Cron.php b/uniforce/lib/Cleantalk/USP/Common/Cron.php index b69c516..39f99a2 100644 --- a/uniforce/lib/Cleantalk/USP/Common/Cron.php +++ b/uniforce/lib/Cleantalk/USP/Common/Cron.php @@ -148,6 +148,8 @@ public function runTasks() $error = Err::get_last('string')['error']; } + Err::add('CRON: can not execute task: ' . $error); + $this->tasks_completed[$task] = false; } @@ -180,7 +182,7 @@ public function runTasks() if(isset($this->tasks[$task], $this->tasks_completed[$task])){ $this->tasks[$task]['next_call'] = $this->tasks_completed[$task] ? time() + $this->tasks[$task]['period'] - : time() + round($this->tasks[$task]['period']/4); + : time() + (int)round($this->tasks[$task]['period']/4); } if(empty($this->tasks[$task]['next_call']) || $this->tasks[$task]['next_call'] < time()){ diff --git a/uniforce/lib/Cleantalk/USP/Common/Helper.php b/uniforce/lib/Cleantalk/USP/Common/Helper.php index 527c34a..ea6308b 100644 --- a/uniforce/lib/Cleantalk/USP/Common/Helper.php +++ b/uniforce/lib/Cleantalk/USP/Common/Helper.php @@ -159,7 +159,7 @@ static public function ip__get($ip_types = array('real', 'remote_addr', 'x_forwa } // Is private network - if($ip_type === false || ($ip_type && (self::ip__is_private_network($ips['real'], $ip_type) || self::ip__mask_match($ips['real'], filter_input(INPUT_SERVER, 'SERVER_ADDR') . '/24', $ip_type)))){ + if($ip_type === false || ($ip_type && (self::ip__is_private_network($ips['real'], $ip_type) || self::ip__mask_match($ips['real'], $_SERVER['SERVER_ADDR'] . '/24', $ip_type)))){ // X-Forwarded-For if(isset($headers['X-Forwarded-For'])){ @@ -224,53 +224,76 @@ static function ip__is_private_network($ip, $ip_type = 'v4') */ static public function ip__mask_match($ip, $cidr, $ip_type = 'v4', $xtet_count = 0) { - if(is_array($cidr)){ - foreach($cidr as $curr_mask){ - if(self::ip__mask_match($ip, $curr_mask, $ip_type)){ - return true; - } - } - unset($curr_mask); - return false; - } - - $xtet_base = ($ip_type == 'v4') ? 8 : 16; - - // Calculate mask - $exploded = explode('/', $cidr); - $net_ip = $exploded[0]; - $mask = $exploded[1]; - - // Exit condition - $xtet_end = ceil($mask / $xtet_base); - if($xtet_count == $xtet_end) - return true; - - // Lenght of bits for comparsion - $mask = $mask - $xtet_base * $xtet_count >= $xtet_base ? $xtet_base : $mask - $xtet_base * $xtet_count; - - // Explode by octets/hextets from IP and Net - $net_ip_xtets = explode($ip_type == 'v4' ? '.' : ':', $net_ip); - $ip_xtets = explode($ip_type == 'v4' ? '.' : ':', $ip); - - // Standartizing. Getting current octets/hextets. Adding leading zeros. - $net_xtet = str_pad(decbin($ip_type == 'v4' ? $net_ip_xtets[$xtet_count] : hexdec($net_ip_xtets[$xtet_count])), $xtet_base, 0, STR_PAD_LEFT); - $ip_xtet = str_pad(decbin($ip_type == 'v4' ? $ip_xtets[$xtet_count] : hexdec($ip_xtets[$xtet_count])), $xtet_base, 0, STR_PAD_LEFT); - - // Comparing bit by bit - for($i = 0, $result = true; $mask != 0; $mask--, $i++){ - if($ip_xtet[$i] != $net_xtet[$i]){ - $result = false; - break; - } - } - - // Recursing. Moving to next octet/hextet. - if($result) - $result = self::ip__mask_match($ip, $cidr, $ip_type, $xtet_count + 1); - - return $result; - + if (is_array($cidr)) { + foreach ($cidr as $curr_mask) { + if (self::ip__mask_match($ip, $curr_mask, $ip_type)) { + return true; + } + } + + return false; + } + + if ( ! self::ip__validate($ip) || ! self::cidrValidate($cidr) ) { + return false; + } + + $xtet_base = ($ip_type === 'v4') ? 8 : 16; + + // Calculate mask + $exploded = explode('/', $cidr); + $net_ip = $exploded[0]; + $mask = (int)$exploded[1]; + + // Exit condition + $xtet_end = ceil($mask / $xtet_base); + if ($xtet_count == $xtet_end) { + return true; + } + + // Length of bits for comparison + $mask = $mask - $xtet_base * $xtet_count >= $xtet_base ? $xtet_base : $mask - $xtet_base * $xtet_count; + + // Explode by octets/hextets from IP and Net + $net_ip_xtets = explode($ip_type === 'v4' ? '.' : ':', $net_ip); + $ip_xtets = explode($ip_type === 'v4' ? '.' : ':', $ip); + + // Standartizing. Getting current octets/hextets. Adding leading zeros. + $net_xtet = str_pad( + decbin( + ($ip_type === 'v4' && (int)$net_ip_xtets[$xtet_count]) ? $net_ip_xtets[$xtet_count] : @hexdec( + $net_ip_xtets[$xtet_count] + ) + ), + $xtet_base, + 0, + STR_PAD_LEFT + ); + $ip_xtet = str_pad( + decbin( + ($ip_type === 'v4' && (int)$ip_xtets[$xtet_count]) ? $ip_xtets[$xtet_count] : @hexdec( + $ip_xtets[$xtet_count] + ) + ), + $xtet_base, + 0, + STR_PAD_LEFT + ); + + // Comparing bit by bit + for ($i = 0, $result = true; $mask != 0; $mask--, $i++) { + if ($ip_xtet[$i] != $net_xtet[$i]) { + $result = false; + break; + } + } + + // Recursing. Moving to next octet/hextet. + if ($result) { + $result = self::ip__mask_match($ip, $cidr, $ip_type, $xtet_count + 1); + } + + return $result; } /** @@ -300,6 +323,20 @@ static public function ip__validate($ip) if(filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) && self::ip__v6_reduce($ip) != '0::0') return 'v6'; // IPv6 return false; // Unknown } + + /** + * Validate CIDR + * + * @param string $cidr expects string like 1.1.1.1/32 + * + * @return bool + */ + public static function cidrValidate($cidr) + { + $cidr = explode('/', $cidr); + + return isset($cidr[0], $cidr[1]) && self::ip__validate($cidr[0]) && preg_match('@\d{1,2}@', $cidr[1]); + } /** * Expand IPv6 @@ -541,6 +578,9 @@ static public function http__request($url, $data = array(), $presets = null, $op $opts[CURLOPT_POSTFIELDS] = null; $opts[CURLOPT_HEADER] = false; break; + case 'http_20': + $opts[CURLOPT_HTTP_VERSION] = CURL_HTTP_VERSION_2_0; + break; default: @@ -822,14 +862,21 @@ public static function getFilenameFromUrl( $url ) * * returns array */ - static public function http__get_headers(){ + public static function http__get_headers() + { $headers = array(); - foreach($_SERVER as $key => $val){ - if(preg_match('/\AHTTP_/', $key)){ + + if ( !is_array($_SERVER) ) { + return $headers; + } + + foreach($_SERVER as $key => $val){ + + if(preg_match('/\AHTTP_/', $key)){ $server_key = preg_replace('/\AHTTP_/', '', $key); $key_parts = explode('_', $server_key); - if(count($key_parts) > 0 and strlen($server_key) > 2){ + if(count($key_parts) > 0 && strlen($server_key) > 2){ foreach($key_parts as $part_index => $part){ $key_parts[$part_index] = function_exists('mb_strtolower') ? mb_strtolower($part) : strtolower($part); $key_parts[$part_index][0] = strtoupper($key_parts[$part_index][0]); @@ -1171,5 +1218,28 @@ static function search_page_errors($string_page){ || stripos($string_page, 'there has been a critical error on your website') !== false ); } + + /** + * Try to return int value of timestamp converted from arg, false if fails. + * @param $arg + * + * @return bool|int + */ + static function arg_to_timestamp($arg) + { + if ( ! is_int($arg) ) { + if ( $arg === (string)(int)$arg ) { + $arg = (int)$arg; + } else { + return false; + } + } + + if ( $arg > time() || 0 > $arg ) { + return false; + } + + return $arg; + } } \ No newline at end of file diff --git a/uniforce/lib/Cleantalk/USP/Common/RemoteCalls.php b/uniforce/lib/Cleantalk/USP/Common/RemoteCalls.php index 7a607bd..7ae9df5 100644 --- a/uniforce/lib/Cleantalk/USP/Common/RemoteCalls.php +++ b/uniforce/lib/Cleantalk/USP/Common/RemoteCalls.php @@ -28,7 +28,7 @@ public static function perform(){ $cooldown = isset($usp->remote_calls->$action->cooldown) ? $usp->remote_calls->$action->cooldown : self::COOLDOWN; - $pass_cooldown = Helper::ip__get(array('real')) === filter_input(INPUT_SERVER, 'SERVER_ADDR'); + $pass_cooldown = Helper::ip__get(array('real')) === $_SERVER['SERVER_ADDR']; if(time() - $usp->remote_calls->$action->last_call >= $cooldown || $pass_cooldown diff --git a/uniforce/lib/Cleantalk/USP/DB.php b/uniforce/lib/Cleantalk/USP/DB.php index f22ef30..c5c0b4c 100644 --- a/uniforce/lib/Cleantalk/USP/DB.php +++ b/uniforce/lib/Cleantalk/USP/DB.php @@ -3,51 +3,53 @@ namespace Cleantalk\USP; class DB extends \PDO implements Common\DB { - + use Templates\Singleton; - + private static $instance; - + /** * @var string */ public $query = ''; - + /** * @var \PDOStatement */ public $query_result = ''; - + /** * @var int */ public $rows_affected; - + /** * @param mixed ...$params */ public function init( ...$params ){ - - if( $params[0] ){ - $dsn = $params[0]; - $username = $params[1]; - $password = $params[2]; - $options = isset( $params[3] ) ? $params[3] : array( - \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION, // Handle errors as an exceptions - \PDO::ATTR_DEFAULT_FETCH_MODE => \PDO::FETCH_ASSOC, // Set default fetch mode as associative array - \PDO::MYSQL_ATTR_SSL_CA => CT_USP_DATA_SSL_CERT . 'ca.pem', - \PDO::MYSQL_ATTR_SSL_CERT => CT_USP_DATA_SSL_CERT . 'client-cert.pem', - \PDO::MYSQL_ATTR_SSL_KEY => CT_USP_DATA_SSL_CERT . 'client-key.pem', - ); - - parent::__construct( $dsn, $username, $password, $options ); - - }else{ - self::$instance = null; - } - + try { + if( $params[0] ){ + $dsn = $params[0]; + $username = $params[1]; + $password = $params[2]; + $options = isset( $params[3] ) ? $params[3] : array( + \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION, // Handle errors as an exceptions + \PDO::ATTR_DEFAULT_FETCH_MODE => \PDO::FETCH_ASSOC, // Set default fetch mode as associative array + \PDO::MYSQL_ATTR_SSL_CA => CT_USP_DATA_SSL_CERT . 'ca.pem', + \PDO::MYSQL_ATTR_SSL_CERT => CT_USP_DATA_SSL_CERT . 'client-cert.pem', + \PDO::MYSQL_ATTR_SSL_KEY => CT_USP_DATA_SSL_CERT . 'client-key.pem', + ); + + parent::__construct( $dsn, $username, $password, $options ); + + }else{ + self::$instance = null; + } + } catch (\Exception $e) { + throw new \Exception("Can not init DB connect, error:" . $e->getMessage()); + } } - + /** * Safely replace place holders * @@ -59,7 +61,7 @@ public function init( ...$params ){ public function prepare( $query, $param = array() ) { return parent::prepare( $query, $param ); } - + /** * Executes a query to DB * @@ -76,7 +78,7 @@ function q( $query, $mode = \PDO::ATTR_DEFAULT_FETCH_MODE ){ $this->rows_affected = $this->query_result->rowCount(); return $this->query_result; } - + /** * @param $query * @@ -87,8 +89,8 @@ public function execute( $query ) { $this->rows_affected = parent::exec( $query ); return $this->rows_affected; } - - + + /** * Fetch first column from query. * May receive raw or prepared query. @@ -99,10 +101,10 @@ public function execute( $query ) { * @return array|object|void|null */ public function fetch( $query = '', $response_type = 'array' ) { - + if( $this->query !== $query) $this->q( $query ); - + switch( $response_type ){ case 'array': $response_type = \PDO::FETCH_ASSOC; @@ -114,12 +116,12 @@ public function fetch( $query = '', $response_type = 'array' ) { $response_type = \PDO::FETCH_NUM; break; } - + return $this->query_result ->fetch( $response_type ); - + } - + /** * Fetch all result from query. * May receive raw or prepared query. @@ -130,7 +132,7 @@ public function fetch( $query = '', $response_type = 'array' ) { * @return array|object|null */ public function fetch_all( $query = '', $response_type = 'array' ) { - + switch($response_type){ case 'array': $response_type = \PDO::FETCH_ASSOC; @@ -142,8 +144,8 @@ public function fetch_all( $query = '', $response_type = 'array' ) { $response_type = \PDO::FETCH_NUM; break; } - + return parent::query( $query ) ->fetchAll( $response_type ); } -} \ No newline at end of file +} diff --git a/uniforce/lib/Cleantalk/USP/File/FileDB.php b/uniforce/lib/Cleantalk/USP/File/FileDB.php index f174821..4463fe1 100644 --- a/uniforce/lib/Cleantalk/USP/File/FileDB.php +++ b/uniforce/lib/Cleantalk/USP/File/FileDB.php @@ -26,8 +26,14 @@ class FileDB { */ private $meta; + /** + * @var \Cleantalk\USP\Common\Storage + */ + private $meta_temp; + private $indexes_description; private $indexes; + private $indexes_temp; private $indexed_column; private $index_type; @@ -59,18 +65,27 @@ public function __construct( $db_name ) { $this->getIndexes(); } } + + $this->getMetaDataTemp(); // @todo handle error + + if (! $this->meta_temp->is_empty()) { + $this->storage = new \Cleantalk\USP\File\Storage( $db_name, $this->meta_temp->cols ); + if ($this->meta_temp->indexes) { + $this->getIndexesTemp(); + } + } } - - public function insert( $data ){ + + public function insertTemp( $data ){ $inserted = 0; for( $number = 0; isset( $data[ $number ] ); $number++ ){ - switch ( $this->addIndex( $number + 1, $data[ $number ] ) ){ + switch ( $this->addIndexTemp( $number + 1, $data[ $number ] ) ){ case true: - if( $this->storage->put( $data[ $number ] ) ){ + if( $this->storage->putTemp( $data[ $number ] ) ){ $inserted++; } break; @@ -82,12 +97,12 @@ public function insert( $data ){ } - $this->meta->rows += $inserted; - $this->meta->save(); + $this->meta_temp->rows += $inserted; + $this->meta_temp->save(); return $inserted; } - + public function delete() { // Clear indexes @@ -118,6 +133,37 @@ public function delete() { // Clear and delete a storage $this->storage->delete(); } + + public function deleteTemp() { + + // Clear indexes + if( $this->meta->indexes ){ + + foreach( $this->meta->indexes as &$index ){ + + // @todo make multiple indexes support + $column_to_index = $index['columns'][0]; + + switch( $index['type'] ){ + case 'bintree': + $this->indexes_temp[ $column_to_index ]->clear_tree(); + break; + case 'btree': + $this->indexes_temp[ $column_to_index ]->clear(); + break; + } + $index['status'] = false; + } unset( $index ); + + } + + // Reset rows amount + $this->meta_temp->rows = 0; + $this->meta_temp->save(); + + // Clear and delete a storage + $this->storage->deleteTemp(); + } /** * Set what columns to select @@ -341,6 +387,19 @@ private function getMetaData(){ $this->meta->cols_num = count( $this->meta->cols ); } } + + /** + * Getting metadata for temp data and creates new file if not exists + */ + private function getMetaDataTemp(){ + + $this->meta_temp = new Storage( $this->name . '_meta', null ); + + if( ! $this->meta_temp->is_empty() ){ + $this->meta_temp->line_length = array_sum( array_column( $this->meta_temp->cols, 'length' ) ); + $this->meta_temp->cols_num = count( $this->meta_temp->cols ); + } + } private function getIndexes() { @@ -369,10 +428,38 @@ function($result, $item){ return $result . ucfirst( $item ); } } } + + private function getIndexesTemp() { + + foreach( $this->meta_temp->indexes as $index ){ + // Index file name = databaseName_allColumnsNames.indexType + $index_name = + $this->name + . '_' . lcfirst( array_reduce( + $index['columns'], + function($result, $item){ return $result . ucfirst( $item ); } + ) ) + . '_temp.' . $index['type']; + + // @todo extend indexes on a few columns + switch( $index['type'] ){ + + case 'bintree': + $this->indexes_temp[ $index['columns'][0] ] = new BinaryTree( self::FS_PATH . $index_name ); + break; + + case 'btree': + $this->indexes_temp[ $index['columns'][0] ] = new BTree( self::FS_PATH . $index_name ); + break; + } + + } + + } - private function addIndex( $number, $data ) { + private function addIndexTemp( $number, $data ) { - foreach ( $this->meta->indexes as $key => &$index ){ + foreach ( $this->meta_temp->indexes as $key => &$index ){ // @todo this is a crunch $column_to_index = $index['columns'][0]; @@ -380,10 +467,10 @@ private function addIndex( $number, $data ) { switch ( $index['type'] ){ case 'bintree': - $result = $this->indexes[ $column_to_index ]->add_key( $value_to_index, $this->meta->rows + $number ); + $result = $this->indexes_temp[ $column_to_index ]->add_key( $value_to_index, $this->meta_temp->rows + $number ); break; case 'btree': - $result = $this->indexes[ $column_to_index ]->put( $value_to_index, $this->meta->rows + $number ); + $result = $this->indexes_temp[ $column_to_index ]->put( $value_to_index, $this->meta_temp->rows + $number ); break; default: $result = false; @@ -394,10 +481,8 @@ private function addIndex( $number, $data ) { $index['status'] = 'ready'; $out = true; }elseif( $result === true ){ -// Err::add('Insertion', 'Duplicate key for column "' . $index . '": ' . $data[ array_search( $index, $columns_name ) ] ); $out = false; }elseif( $result === false ){ -// Err::add('Insertion', 'No index added for column "' . $index . '": ' . array_search( $index, $columns_name ) ); $out = false; }else{ $out = false; diff --git a/uniforce/lib/Cleantalk/USP/File/Storage.php b/uniforce/lib/Cleantalk/USP/File/Storage.php index 1a53443..d2cbfa0 100644 --- a/uniforce/lib/Cleantalk/USP/File/Storage.php +++ b/uniforce/lib/Cleantalk/USP/File/Storage.php @@ -12,11 +12,13 @@ class Storage { private $name; private $path; + private $path_temp; /** * @var false|resource */ private $stream; + private $stream_temp; private $cols; private $line_length; @@ -35,10 +37,12 @@ public function __construct( $name, $cols, $folder = null ){ $this->folder = $folder ?: CT_USP_ROOT . 'data' . DIRECTORY_SEPARATOR; $this->name = $name; $this->path = $this->folder . $name . '.storage'; + $this->path_temp = $this->folder . $name . '_temp.storage'; $this->cols = $cols; $this->line_length = array_sum( array_column( $this->cols, 'length' ) ); $this->stream = fopen( $this->path, 'a+b' ); + $this->stream_temp = fopen( $this->path_temp, 'a+b' ); } /** @@ -65,6 +69,31 @@ public function put( $row ) { return (bool) $res; } + + /** + * @param $row + * + * @return bool|int + */ + public function putTemp( $row ) { + + $res = false; + + if( + $this->checkRowFormat( $row ) && + $this->covertRowToRaw( $row ) + ){ + fseek( $this->stream, 0, SEEK_END ); + $res = fwrite( $this->stream_temp, $this->input_buffer . $this->row_separator ); + } + + if ( ! $res ){ + $err = error_get_last(); + Err::add( $err['message'] ); + } + + return (bool) $res; + } /** * @return bool @@ -72,6 +101,13 @@ public function put( $row ) { public function delete(){ return ftruncate( $this->stream, 0 ) && unlink( $this->path ); } + + /** + * @return bool + */ + public function deleteTemp(){ + return ftruncate( $this->stream_temp, 0 ) && unlink( $this->path_temp ); + } private function checkRowFormat( $data ){ diff --git a/uniforce/lib/Cleantalk/USP/Layout/Field.php b/uniforce/lib/Cleantalk/USP/Layout/Field.php index b041e2a..7157e03 100644 --- a/uniforce/lib/Cleantalk/USP/Layout/Field.php +++ b/uniforce/lib/Cleantalk/USP/Layout/Field.php @@ -121,6 +121,28 @@ public function draw_element__text() { : ''; } + public function draw_element__password() { + + $name = $this->getName(); + + if($this->title_first) + echo ' '; + + echo 'parent_field && !$this->state->settings->{$this->parent_field} ? ' disabled="disabled"' : '') + .($this->child_fields ? ' onchange="uspSettingsDependencies([\''.implode("','",$this->child_fields).'\'])"' : '') + .' />'; + + if(!$this->title_first) + echo ' '; + + echo $this->description + ?'
'. $this->description .'
' + : ''; + } + public function draw_element__checkbox() { $name = $this->getName(); diff --git a/uniforce/lib/Cleantalk/USP/Scanner/Helper.php b/uniforce/lib/Cleantalk/USP/Scanner/Helper.php index 03a235d..15aed7c 100644 --- a/uniforce/lib/Cleantalk/USP/Scanner/Helper.php +++ b/uniforce/lib/Cleantalk/USP/Scanner/Helper.php @@ -3,12 +3,12 @@ namespace Cleantalk\USP\Scanner; class Helper { - - const signatures_version_file_url = 'https://s3-us-west-2.amazonaws.com/cleantalk-security/security_signatures/version.txt'; - const signatures_file_url = 'https://s3-us-west-2.amazonaws.com/cleantalk-security/security_signatures/security_signatures_v2.csv.gz'; - + + const signatures_version_file_url = 'https://cleantalk-security.s3.amazonaws.com/security_signatures/version.txt'; + const signatures_file_url = 'https://cleantalk-security.s3.amazonaws.com/security_signatures/security_signatures_v2.csv.gz'; + public static function get_files( $offset = 0, $amount = 1500, $path = CT_USP_SITE_ROOT ) { - + $path_to_scan = realpath($path); $root_path = realpath( substr( CT_USP_SITE_ROOT, 0, - 1 ) ); $init_params = array( @@ -22,14 +22,14 @@ public static function get_files( $offset = 0, $amount = 1500, $path = CT_USP_SI 'files_mandatory' => array(), 'dir_exceptions' => array() ); - + $scanner = new Scanner($path_to_scan, $root_path, $init_params); - + return $scanner->files_count ? $scanner->files : false; } - + /** * Static. * Gets and parses signatures from the Cloud @@ -41,35 +41,38 @@ public static function get_files( $offset = 0, $amount = 1500, $path = CT_USP_SI static public function get_hashes__signature( $last_signature_update = 0 ) { if( \Cleantalk\USP\Uniforce\Helper::http__request__get_response_code(self::signatures_version_file_url) == 200) { - + $latest_signatures = \Cleantalk\USP\Uniforce\Helper::http__request__get_content(self::signatures_version_file_url); - + if(strtotime($latest_signatures)){ - + if(strtotime($last_signature_update) < strtotime($latest_signatures)){ - + if(\Cleantalk\USP\Uniforce\Helper::http__request__get_response_code(self::signatures_file_url) == 200) { - + $gz_data = \Cleantalk\USP\Uniforce\Helper::http__request__get_content(self::signatures_file_url); - + if(empty($gz_data['error'])){ - + if(function_exists('gzdecode')){ - + $data = gzdecode($gz_data); - + if($data !== false){ - + // Set map for file $map = strpos( self::signatures_file_url, '_mapped' ) !== false ? \Cleantalk\USP\Uniforce\Helper::buffer__csv__get_map( $data ) // Map from file : array( 'id', 'name', 'body', 'type', 'attack_type', 'submitted', 'cci' ); // Default map - + $out = array(); while( $data ){ - $out[] = \Cleantalk\USP\Uniforce\Helper::buffer__csv__pop_line_to_array( $data, $map, true ); + $row = \Cleantalk\USP\Uniforce\Helper::buffer__csv__pop_line_to_array( $data, $map, true ); + if ( isset($row['body']) ) { + $row['body'] = base64_encode($row['body']); + } + $out[] = $row; } - return $out; }else return array('error' => 'COULDNT_UNPACK'); @@ -86,7 +89,7 @@ static public function get_hashes__signature( $last_signature_update = 0 ) }else return array('error' =>'NO_VERSION_FILE'); } - + /** * Getting real hashs of approved files * @@ -97,48 +100,46 @@ static public function get_hashes__signature( $last_signature_update = 0 ) * @return array Array with all files hashes or Error Array */ static public function get_hashes__approved_files($cms, $type, $version) { - - $file_path = 'https://cleantalk-security.s3-us-west-2.amazonaws.com/extensions_checksums/'.$cms.'/'.$type.'/'.$version.'.csv.gz'; - + + $file_path = 'https://cleantalk-security.s3.amazonaws.com/extensions_checksums/' . $cms . '/' . $type . '/' . $version . '.csv.gz'; + if( \Cleantalk\USP\Uniforce\Helper::http__request($file_path, array(), 'get_code') == 200) { - + $gz_data = \Cleantalk\USP\Uniforce\Helper::http__request__get_content($file_path); - + if(empty($gz_data['error'])) { - + if ( function_exists( 'gzdecode' ) ) { - + $data = gzdecode( $gz_data ); - + if ( $data !== false ) { - + $lines = \Cleantalk\USP\Uniforce\Helper::buffer__parse__csv($data); - + if( count( $lines ) > 0 ) { - + $result = array(); - + foreach( $lines as $hash_info ) { - + if(empty($hash_info)) continue; - + preg_match('/.*\.(\S*)$/', $hash_info[0], $matches); $ext = isset($matches[1]) ? $matches[1] : ''; if(!in_array($ext, array('php','html'))) continue; - + $result[] = $hash_info; - + } - + if(count($result)){ return $result; }else return array('error' =>'BAD_HASHES_FILE'); - } else { return array('error' => 'Empty hashes file'); } - } else { return array( 'error' => 'COULDNT_UNPACK' ); } @@ -146,10 +147,10 @@ static public function get_hashes__approved_files($cms, $type, $version) { return array( 'error' => 'Function gzdecode not exists. Please update your PHP to version 5.4' ); } } - }else - return array('error' =>'REMOTE_FILE_NOT_FOUND'); + } + return array('error' =>'REMOTE_FILE_NOT_FOUND'); } - + /** * Scanning file * @@ -160,19 +161,19 @@ static public function get_hashes__approved_files($cms, $type, $version) { static public function file__get__differences($root_path, $file_info) { if(file_exists($root_path.$file_info['path'])){ - + if(is_readable($root_path.$file_info['path'])){ - + /** @todo Add proper comparing mechanism // Comparing with original file (if it's exists) and getting difference if(!empty($file_info['real_full_hash']) && $file_info['real_full_hash'] !== $file_info['full_hash']){ - + $file_original = \Cleantalk\USP\Scanner\Helper::file__get_original($file_info, $cms); - + if(!empty($file_original['error'])){ - + $file = file($root_path.$file_info['path']); - + // Comparing files strings for($row = 0; !empty($file[$row]); $row++){ if(isset($file[$row]) || isset($file_original[$row])){ @@ -186,16 +187,16 @@ static public function file__get__differences($root_path, $file_info) } } */ - + }else $output = array('error' => 'NOT_READABLE'); }else $output = array('error' => 'NOT_EXISTS'); - + return !empty($output) ? $output : false; - + } - + /** * Get original file's content * @@ -206,7 +207,7 @@ static public function file__get__differences($root_path, $file_info) static public function file__get_original($file_info) { $file_info['path'] = str_replace('\\', '/', $file_info['path']); // Replacing win slashes to Orthodox slashes =) in case of Windows - + switch( $file_info['source_type'] ){ case 'PLUGIN': $file_info['path'] = preg_replace('@/wp-content/plugins/.*?/(.*)$@i', '$1',$file_info['path']); @@ -217,18 +218,18 @@ static public function file__get_original($file_info) $url_path = 'https://themes.svn.wordpress.org/'.$file_info['source'].'/'.$file_info['version'].'/'.$file_info['path']; break; default: - $url_path = 'http://cleantalk-security.s3.amazonaws.com/cms_sources/'.$file_info['source'].'/'.$file_info['version'].$file_info['path']; + $url_path = 'https://cleantalk-security.s3.amazonaws.com/cms_sources/'.$file_info['source'].'/'.$file_info['version'].$file_info['path']; break; } - + if( \Cleantalk\USP\Uniforce\Helper::http__request__get_response_code($url_path) == 200 ){ $out = \Cleantalk\USP\Uniforce\Helper::http__request__get_content($url_path); }else $out = array('error' => 'Couldn\'t get original file'); - + return $out; } - + /** * Checks if the current system is Windows or not * @@ -237,7 +238,7 @@ static public function file__get_original($file_info) static function is_windows(){ return strpos(strtolower(php_uname('s')), 'windows') !== false ? true : false; } - + /** * Returns number of string with a given char position * @@ -250,7 +251,7 @@ static function is_windows(){ public static function file__get_string_number_with_needle($file_path, $signature_body, $is_regexp = false){ $file = file( $file_path ); $out = 0; - + foreach( $file as $number => $line ){ if( ( $is_regexp && preg_match( $signature_body, $line ) ) || @@ -259,7 +260,7 @@ public static function file__get_string_number_with_needle($file_path, $signatur $out = $number; } } - + return $out; } -} \ No newline at end of file +} diff --git a/uniforce/lib/Cleantalk/USP/Scanner/Scanner.php b/uniforce/lib/Cleantalk/USP/Scanner/Scanner.php index bae5da6..59e9848 100644 --- a/uniforce/lib/Cleantalk/USP/Scanner/Scanner.php +++ b/uniforce/lib/Cleantalk/USP/Scanner/Scanner.php @@ -7,63 +7,65 @@ class Scanner { + const FILE_MAX_SIZE = 2621440; // 2.5 MB + public $path = ''; // Main path public $path_lenght = 0; - + /** @var array Description Extensions to check */ public $ext = array(); - + /** @var array Exception for extensions */ public $ext_except = array(); - + /** @var array Exception for files paths */ public $files_except = array(); - + /** @var array Exception for directories */ - public $dirs_except = array(); - + public $dirs_except = array(); + /** @var array Mandatory check for files paths */ - public $files_mandatory = array(); - + public $files_mandatory = array(); + /** @var array Mandatory check for directories */ public $dirs_mandatory = array(); - + public $files = array(); public $dirs = array(); - + public $files_count = 0; public $dirs_count = 0; - + private $file_start = 0; private $file_curr = 0; private $file_max = 1000000; - + function __construct($path, $rootpath, $params = array('count' => true)) { // INITIALING PARAMS - + // Main directory $path = realpath($path); if(!is_dir($path)) die("Scan '$path' isn't directory"); if(!is_dir($rootpath)) die("Root '$rootpath' isn't directory"); $this->path_lenght = strlen($rootpath); - - // Processing filters + + // Processing filters $this->ext = !empty($params['extensions']) ? $this->filter_params($params['extensions']) : array(); $this->ext_except = !empty($params['extensions_exceptions']) ? $this->filter_params($params['extensions_exceptions']) : array(); $this->files_except = !empty($params['file_exceptions']) ? $this->filter_params($params['file_exceptions']) : array(); $this->dirs_except = !empty($params['dir_exceptions']) ? $this->filter_params($params['dir_exceptions']) : array(); - + // Mandatory files and dirs $this->files_mandatory = !empty($params['files_mandatory']) ? $this->filter_params($params['files_mandatory']) : array(); $this->dirs_mandatory = !empty($params['dirs_mandatory']) ? $this->filter_params($params['dirs_mandatory']) : array(); - + // Initialing counters $this->file_start = isset($params['offset']) ? $params['offset'] : 0; $this->file_max = isset($params['offset']) && isset($params['amount']) ? $params['offset'] + $params['amount'] : 1000000; - + // DO STUFF - + // Only count files if(!empty($params['count'])){ $this->count_files__mandatory($this->files_mandatory); @@ -76,15 +78,15 @@ function __construct($path, $rootpath, $params = array('count' => true)) // Files $this->files_count = count($this->files); $this->file__details($this->files, $this->path_lenght); - + // Directories // $this->dirs[]['path'] = $path; // $this->dirs_count = count($this->dirs); // $this->dir__details($this->dirs, $this->path_lenght); - + } - + /** * * Function coverting icoming parametrs to array even if it is a string like 'some, example, string' * @@ -108,10 +110,10 @@ public function filter_params($filter) return null; } } - + /** * Counts given mandatory files - * + * * @param array $files Files to count */ public function count_files__mandatory($files){ @@ -120,24 +122,24 @@ public function count_files__mandatory($files){ $this->files_count++; } } - + /** * Count files in directory - * + * * @param string $main_path Path to count files in */ public function count_files_in_dir($main_path) - { + { $paths = array_merge(glob($main_path.'/.*', GLOB_NOSORT), glob($main_path.'/*', GLOB_NOSORT)); - + foreach($paths as $path){ - + // Excluding $path/. and $path/.. directories from the set if(preg_match('/\.$/', $path)) continue; - + if(is_file($path)){ - + // Extensions filter if(!empty($this->ext_except)){ $tmp = explode('.', $path); @@ -155,18 +157,18 @@ public function count_files_in_dir($main_path) if(in_array(basename($path), $this->files_except)) continue; } - + // Dirnames filter foreach($this->dirs_except as $dir_except){ if(preg_match('/'.$dir_except.'/', $path)){ continue(2); } } - + $this->files_count++; - + }elseif(is_dir($path)){ - + // Dirnames filter foreach($this->dirs_except as $dir_except){ if(preg_match('/'.$dir_except.'/', $path)){ @@ -177,10 +179,10 @@ public function count_files_in_dir($main_path) } } } - + /** * Getting mandatory files - * + * * @param array $files Files to get */ public function get_files__mandatory($files){ @@ -191,76 +193,76 @@ public function get_files__mandatory($files){ } } } - + /** * Get all files from directory - * + * * @param string $main_path Path to get files from * @return void */ public function get_file_structure($main_path) { $paths = array_merge(glob($main_path.'/.*', GLOB_NOSORT), glob($main_path.'/*', GLOB_NOSORT)); - + foreach($paths as $path){ - + // Excluding $path/. and $path/.. directories from the set if(preg_match('/\.$/', $path)) continue; - + // Return if file limit is reached if($this->file_curr >= $this->file_max) return; - + if(is_file($path)){ - + // Extensions filter if(!empty($this->ext)){ $tmp = explode('.', $path); if(!in_array($tmp[count($tmp)-1], $this->ext)) continue; } - + // Extensions exception filter if(!empty($this->ext_except)){ $tmp = explode('.', $path); if(in_array($tmp[count($tmp)-1], $this->ext_except)) continue; } - + // Filenames exception filter if(!empty($this->files_except)){ if(in_array(basename($path), $this->files_except)) continue; } - + // Dirnames filter foreach($this->dirs_except as $dir_except){ if(preg_match('/'.$dir_except.'/', $path)){ continue(2); } } - + $this->file_curr++; - + // Skip if start is not reached if($this->file_curr-1 < $this->file_start) continue; - + $this->files[]['path'] = $path; - + }elseif(is_dir($path)){ - + // Dirnames filter foreach($this->dirs_except as $dir_except){ if(preg_match('/'.$dir_except.'/', $path)) continue(2); } - + $this->get_file_structure($path); if($this->file_curr > $this->file_start) $this->dirs[]['path'] = $path; - + }elseif(is_link($path)){ error_log('LINK FOUND: ' . $path); } @@ -269,7 +271,7 @@ public function get_file_structure($main_path) /** * Getting file details like last modified time, size, permissions - * + * * @param array $file_list Array of abolute paths to files * @param int $path_offset Length of CMS root path */ @@ -286,10 +288,10 @@ public function file__details($file_list, $path_offset) // Fast hash $this->files[$key]['fast_hash'] = md5($this->files[$key]['path']); - + // Full hash $this->files[$key]['full_hash'] = is_readable($val['path']) - ? md5_file($val['path']) + ? $this->files[$key]['size'] > self::FILE_MAX_SIZE ? 'file_is_too_big' : md5_file($val['path']) : 'unknown'; } @@ -298,7 +300,7 @@ public function file__details($file_list, $path_offset) /** * Getting dir details - * + * * @param array $dir_list Array of abolute paths to directories * @param int $path_offset Length of CMS root path */ @@ -310,48 +312,51 @@ public function dir__details($dir_list, $path_offset) $this->dirs[$key]['perms'] = substr(decoct(fileperms($val['path'])), 2); } } - + /** * Scan file thru malware sinatures - * + * * @param string $root_path Path to CMS's root folder * @param array $file_info Array with files data (path, real_full_hash, source_type, source, version), other is optional * @param array $signatures Set of signatures - * + * * @return array|false False or Array of found bad sigantures */ static public function file__scan__for_signatures($root_path, $file_info, $signatures) { if(file_exists($root_path.$file_info['path'])){ - + if(is_readable($root_path.$file_info['path'])){ - + $verdict = array(); - foreach ((array)$signatures as $signature){ - + foreach ($signatures as $signature){ if( $signature['type'] === 'FILE' ){ - if( $file_info['full_hash'] === $signature['body'] ){ + if( $file_info['full_hash'] === $signature ){ $verdict['SIGNATURES'][1][] = $signature['id']; } } - + if( in_array( $signature['type'], array('CODE_PHP', 'CODE_JS', 'CODE_HTML' ) ) ) { + if ( filesize($root_path . $file_info['path']) > self::FILE_MAX_SIZE ) { + // File is too big to getting the content + continue; + } $file_content = file_get_contents( $root_path . $file_info['path'] ); $is_regexp = preg_match( '/^\/.*\/$/', $signature['body'] ); if( - ( $is_regexp && preg_match( $signature['body'], $file_content ) ) || + ( $is_regexp && preg_match( $signature, $file_content ) ) || ( ! $is_regexp && strripos( $file_content, stripslashes( $signature['body'] ) ) !== false ) ){ - $line_number = ScannerHelper::file__get_string_number_with_needle( $file_info['path'], $signature['body'], $is_regexp ); + $line_number = ScannerHelper::file__get_string_number_with_needle( $root_path . $file_info['path'], $signature['body'], $is_regexp ); $verdict['SIGNATURES'][ $line_number ][] = $signature['id']; } } } - + $file_info['weak_spots'] = !empty($file_info['weak_spots']) ? json_decode($file_info['weak_spots'], true) : array(); $verdict = Helper::array_merge__save_numeric_keys__recursive($file_info['weak_spots'], $verdict); - + // Processing results if(!empty($verdict)){ $output['weak_spots'] = $verdict; @@ -362,38 +367,38 @@ static public function file__scan__for_signatures($root_path, $file_info, $signa $output['severity'] = null; $output['status'] = 'OK'; } - + }else $output = array('error' => 'NOT_READABLE'); }else $output = array('error' => 'NOT_EXISTS'); - + return $output; } - + /** * Scan file thru heuristic - * + * * @param string $root_path Path to CMS's root folder * @param array $file_info Array with files data (path, real_full_hash, source_type, source, version), other is optional - * + * * @return array|false False or Array of found bad constructs sorted by severity */ static public function file__scan__heuristic($root_path, $file_info) { if(file_exists($root_path.$file_info['path'])){ - + if(is_readable($root_path.$file_info['path'])){ - + $scanner = new ScannerH( $root_path . $file_info['path']); if ( !empty( $scanner -> errors ) ) return $scanner -> errors; $scanner -> process_file(); - + $file_info['weak_spots'] = !empty($file_info['weak_spots']) ? json_decode($file_info['weak_spots'], true) : array(); - + $verdict = Helper::array_merge__save_numeric_keys__recursive($file_info['weak_spots'], $scanner->verdict); - + // Processing results if(!empty($verdict)){ $output['weak_spots'] = $verdict; @@ -404,14 +409,14 @@ static public function file__scan__heuristic($root_path, $file_info) $output['severity'] = null; $output['status'] = 'OK'; } - + }else $output = array('error' => 'NOT_READABLE'); }else $output = array('error' => 'NOT_EXISTS'); - + return $output; } - - + + } diff --git a/uniforce/lib/Cleantalk/USP/ScannerController.php b/uniforce/lib/Cleantalk/USP/ScannerController.php index 979168b..a585540 100644 --- a/uniforce/lib/Cleantalk/USP/ScannerController.php +++ b/uniforce/lib/Cleantalk/USP/ScannerController.php @@ -12,44 +12,44 @@ use Cleantalk\USP\Variables\Get; class ScannerController { - + const table__scanner___files = 'scanner_files'; const table__scanner___links = 'scanner_links'; const table__scanner___backups = 'scanner_backups'; const table__scanner___backup_files = 'scanner_backup_files'; - + private static $instance; - + /** * DB handler * * @var DB */ public $db = null; - + /** * Site root directory * * @var string */ private $root = ''; - + /** * Current action offset * * @var int */ private $offset = 0; - + /** * Current action * * @var string */ private $state = ''; - + function __construct( $root_dir, $db_params = null ){ - + if( $db_params ){ @$this->db = DB::getInstance( $db_params[0], @@ -57,13 +57,13 @@ function __construct( $root_dir, $db_params = null ){ $db_params[2] ); } - + $this->root = $root_dir; $this->offset = intval( Get::get( 'offset' ) ) ?: $this->offset; $this->offset = intval( Get::get( 'amount' ) ) ?: $this->offset; $this->state = strval( Get::get( 'state' ) ) ?: $this->state; } - + private static $states = array( 'create_db', 'clear_table', @@ -76,20 +76,20 @@ function __construct( $root_dir, $db_params = null ){ 'frontend_analysis', 'outbound_links', ); - + public function action__scanner__controller(){ - + $usp = State::getInstance(); sleep(5); - + switch( $this->state ){ - + // Creating DB case 'create_db': $result = $this->action__scanner__create_db(); break; - + // Cleaning table case 'clear_table': $result = $this->action__scanner__clear_table( @@ -97,14 +97,14 @@ public function action__scanner__controller(){ 10000 ); break; - + //Signatures case 'get_signatures': - + $result = $this->action__scanner__get_signatures(); - + break; - + // Searching for new files case 'surface_analysis': $result = $this->action__scanner__surface_analysis( @@ -113,12 +113,12 @@ public function action__scanner__controller(){ $this->root ); break; - + // Searching for new files case 'get_approved': $result = $this->action__scanner__get_approved(); break; - + // Signatures case 'signature_analysis': @@ -127,18 +127,18 @@ public function action__scanner__controller(){ 10, $this->root ); - + break; // Heuristic case 'analysis_heuristic': - + $result = $this->action__scanner__heuristic_analysis( $this->offset, 10, $this->root ); - + break; // Send result @@ -152,7 +152,7 @@ public function action__scanner__controller(){ // Make next call if everything is ok if( ! isset( $end ) && empty( $result['error'] ) ){ - + $remote_call_params = array( 'plugin_name' => 'security', 'spbc_remote_call_token' => md5( $usp->settings->key ), @@ -160,13 +160,13 @@ public function action__scanner__controller(){ 'state' => $result['end'] ? $this->next_state( $this->state ) : $this->state, 'offset' => $result['end'] ? 0 : $this->offset + $result['processed'], ); - + Helper::http__request( CT_USP_AJAX_URI, $remote_call_params, 'get async' ); - + } // Delete or add an error @@ -176,34 +176,34 @@ public function action__scanner__controller(){ return true; } - + /** * Creates remote DB and get DB params * * @return array|bool[] */ public function action__scanner__create_db(){ - + $usp = State::getInstance(); - + $result = API::method__dbc2c_get_info( $usp->key ); - + if( empty( $result['error'] ) ){ $usp->data->db_request_string = 'mysql:host=' . $result['db_host'] . ';dbname=' . $result['db_name'] . ';charset=utf8'; $usp->data->db_user = $result['db_user']; $usp->data->db_password = $result['db_password']; $usp->data->db_created = $result['created']; $usp->data->save(); - + $out = array('success' => true, 'end' => true); - + }else $out = $result; - + return $out; - + } - + /** * Clears all data about scanned files * @@ -213,33 +213,33 @@ public function action__scanner__create_db(){ * @return array */ public function action__scanner__clear_table( $offset = null, $amount = null ){ - + if( ! $this->db ) return array('error' => 'DB_NOT_PROVIDED'); if( $this->db instanceof Cleantalk\USP\DB ) return array('error' => 'DB_BAD_CONNECTION'); - + $offset = $offset ?: (int) Get::get('offset'); $amount = $amount ?: (int) Get::get('amount'); - + $result = $this->db->fetch_all( 'SELECT count(fast_hash) as cnt' . ' FROM ' . self::table__scanner___files ); $total = (int)$result[0]['cnt']; - + $result = $this->db->fetch_all( 'SELECT path, fast_hash, status' . ' FROM ' . self::table__scanner___files . " LIMIT $offset, $amount;" ); $checked = count($result); - + $to_delete = array(); foreach($result as $value){ if( ! file_exists( $this->root . $value['path'] ) && $value['status'] != 'QUARANTINED' ){ $to_delete[] = "'{$value['fast_hash']}'"; } } unset($value); - + $deleted = 0; if( ! empty( $to_delete ) ){ $deleted = $this->db->exec( @@ -248,69 +248,74 @@ public function action__scanner__clear_table( $offset = null, $amount = null ){ . ' WHERE fast_hash IN (' . implode( ',', $to_delete ) . ');' ); } - + $out = array( 'checked' => (int) $checked, 'deleted' => (int) $deleted, 'processed' => (int) $checked - (int) $deleted, 'end' => $total <= $offset + $amount, ); - + // Count if needed if( $offset == 0 ) $out['total'] = $total; - + if($deleted === false) $out['error'] = 'COULDNT_DELETE'; return $out; } - + public function action__scanner__get_signatures(){ - + $usp = State::getInstance(); - + $out = array( 'success' => true, ); - + if ( $usp->settings->scanner_signature_analysis ) { - + $result = ScannerHelper::get_hashes__signature($usp->data->stat->scanner->signature_last_update); - + if(empty($result['error'])){ - + $signatures = new \Cleantalk\USP\Common\Storage( 'signatures', $result, '', 'csv' ); $signatures->save(); - + $usp->data->stat->scanner->signature_last_update = time(); $usp->data->stat->scanner->signature_entries = count( $result ); $usp->data->save(); - - }elseif($result['error'] === 'UP_TO_DATE'){ - $out['success'] = 'UP_TO_DATE'; - }else - $out['updated'] = count($result); - + + $out['updated'] = count($result); + + } elseif ( $result['error'] === 'UP_TO_DATE' ) { + $out['success'] = 'UP_TO_DATE'; + } else { + Err::add($result['error']); + } + $out['end'] = 1; - - }else - Err::add('Signatures scan is disabled'); - + + }else{ + Err::add('Signatures scan is disabled'); + } + + return Err::check() ? Err::check_and_output() : $out; } - + public function action__scanner__surface_analysis( $offset = null, $amount = null, $path = CT_USP_SITE_ROOT ){ - + if( ! $this->db ) return array('error' => 'DB_NOT_PROVIDED'); if( $this->db instanceof Cleantalk\USP\DB ) return array('error' => 'DB_BAD_CONNECTION'); - + $offset = $offset ?: (int) Get::get('offset'); $amount = $amount ?: (int) Get::get('amount'); $time_start = microtime(true); - + $path_to_scan = $path ?: realpath($this->root); $root_path = realpath($this->root); $init_params = array( @@ -324,23 +329,23 @@ public function action__scanner__surface_analysis( $offset = null, $amount = nul 'files_mandatory' => array(), 'dir_exceptions' => array() ); - + $scanner = new Scanner($path_to_scan, $root_path, $init_params); - + if( $scanner->files_count ){ - + $sql_query = 'INSERT INTO ' . self::table__scanner___files . ' (`path`, `size`, `perms`, `mtime`,`status`,`fast_hash`, `full_hash`) VALUES '; - + $sql_query__params = array(); foreach($scanner->files as $key => $file){ - + $file['path'] = addslashes($file['path']); $sql_query__params[] = '("' . implode( '", "', $file ) .'")'; - + } unset($key, $file); - + $sql_query .= implode( ',', $sql_query__params ); $sql_query .= " ON DUPLICATE KEY UPDATE @@ -400,12 +405,12 @@ public function action__scanner__surface_analysis( $offset = null, $amount = nul weak_spots, NULL );"; - + $success = $this->db->execute($sql_query); - + }else $output = array('error' => __FUNCTION__ . ' No files to scan',); - + if(isset($success)){ $output = array( 'processed' => $scanner->files_count, @@ -414,7 +419,7 @@ public function action__scanner__surface_analysis( $offset = null, $amount = nul 'exec_time' => round(microtime(true) - $time_start, 3), ); } - + if( $offset == 0 ){ $scanner = new Scanner( realpath( $this->root ), @@ -429,21 +434,21 @@ public function action__scanner__surface_analysis( $offset = null, $amount = nul ); $output['total'] = (int) $scanner->files_count; } - + return $output; } - + /** * Getting remote hashes of approved files * * @return array */ public function action__scanner__get_approved() { - - $result = ScannerHelper::get_hashes__approved_files('usp','approved', '1.0.0'); - + + $result = ScannerHelper::get_hashes__approved_files('usp','approved', SPBCT_VERSION); + if (empty($result['error'])) { - + $prepared_sql = $this->db->prepare('UPDATE '. self::table__scanner___files .' SET checked_signature = 1, @@ -452,7 +457,7 @@ public function action__scanner__get_approved() { severity = NULL WHERE path = :path AND full_hash = :full_hash;' ); - + foreach ($result as $key => $value) { $prepared_sql->execute(array( ':path' => $value[0], @@ -460,19 +465,19 @@ public function action__scanner__get_approved() { )); } } - + return array( 'end' => 1, 'processed' => empty($result['error']) ? count($result) : 0, ); - + } - + public function action__scanner__signature_analysis( $offset = null, $amount = null, $status = "'UNKNOWN','MODIFIED','OK','INFECTED'" ){ - + if( ! $this->db ) return array('error' => 'DB_NOT_PROVIDED'); if( $this->db instanceof Cleantalk\USP\DB ) return array('error' => 'DB_BAD_CONNECTION'); - + $status = Get::get( 'status' ) ? stripslashes( Get::get( 'status' ) ) : $status; $offset = $offset ?: (int) Get::get('offset'); $amount = $amount ?: (int) Get::get('amount'); @@ -482,7 +487,7 @@ public function action__scanner__signature_analysis( $offset = null, $amount = n 'processed' => 0, 'scanned' => 0, ); - + if( $offset == 0 ){ $result = $this->db->fetch_all( 'SELECT COUNT(fast_hash) as cnt' @@ -491,7 +496,7 @@ public function action__scanner__signature_analysis( $offset = null, $amount = n ); $out['total'] = (int) $result[0]['cnt']; } - + $files_to_check = $this->db->fetch_all( 'SELECT path, source_type, source_name, source_version, status, checked_signature, fast_hash, real_full_hash, full_hash, weak_spots, difference, severity' .' FROM ' . self::table__scanner___files @@ -502,7 +507,7 @@ public function action__scanner__signature_analysis( $offset = null, $amount = n if ( $files_to_check ) { if ( ! empty( $files_to_check ) ) { - + $prepared_query = $this ->db ->prepare( @@ -514,62 +519,68 @@ public function action__scanner__signature_analysis( $offset = null, $amount = n .' weak_spots = :weak_spots' .' WHERE fast_hash = :fast_hash' ); - + + $signatures = new Storage('signatures', null, '', 'csv', array( + 'id', + 'name', + 'body', + 'type', + 'attack_type', + 'submitted', + 'cci' + ) ); + $signatures = $signatures->convertToArray(); + + $decoded_signatures = array(); + foreach ($signatures as $signature => $value){ + $decoded_signatures[$signature] = $value; + $decoded_signatures[$signature]['body'] = base64_decode($signature['body']); + } + // Initialing results foreach ( $files_to_check as $file ) { - - $signatures = new Storage('signatures', null, '', 'csv', array( - 'id', - 'name', - 'body', - 'type', - 'attack_type', - 'submitted', - 'cci' - ) ); - $signatures = $signatures->convertToArray(); - - $result = Scanner::file__scan__for_signatures( $this->root, $file, $signatures ); - + + $result = Scanner::file__scan__for_signatures( $this->root, $file, $decoded_signatures ); + if ( empty( $result['error'] ) ) { - - $status = ! empty( $file['status'] ) && $file['status'] === 'MODIFIED' ? 'MODIFIED' : $result['status']; - $weak_spots = ! empty( $result['weak_spots'] ) ? json_encode( $result['weak_spots'] ) : NULL; - $severity = ! empty( $file['severity'] ) - ? $file['severity'] - : ( $result['severity'] ? $result['severity'] : null ); - - $result_db = $prepared_query - ->execute( - array( - ':status' => $status, - ':severity' => $severity, - ':weak_spots' => $weak_spots, - ':fast_hash' => $file['fast_hash'], - ) - ); - - $result['status'] !== 'OK' ? $out['found']++ : $out['found']; - $result_db !== false ? $out['scanned']++ : $out['scanned']; - + + $status = ! empty( $file['status'] ) && $file['status'] === 'MODIFIED' ? 'MODIFIED' : $result['status']; + $weak_spots = ! empty( $result['weak_spots'] ) ? json_encode( $result['weak_spots'] ) : NULL; + $severity = ! empty( $file['severity'] ) + ? $file['severity'] + : ( $result['severity'] ? $result['severity'] : null ); + + $result_db = $prepared_query + ->execute( + array( + ':status' => $status, + ':severity' => $severity, + ':weak_spots' => $weak_spots, + ':fast_hash' => $file['fast_hash'], + ) + ); + + $result['status'] !== 'OK' ? $out['found']++ : $out['found']; + $result_db !== false ? $out['scanned']++ : $out['scanned']; + }else return array( 'error' => 'Signature scan: ' . $result['error']); - + $out['processed']++; } } } - + $out['end'] = $out['processed'] < $amount; - + return $out; } - + public function action__scanner__heuristic_analysis( $offset = null, $amount = null, $path = '', $status = "'MODIFIED','UNKNOWN'" ) { - + if( ! $this->db ) return array('error' => 'DB_NOT_PROVIDED'); if( $this->db instanceof Cleantalk\USP\DB ) return array('error' => 'DB_BAD_CONNECTION'); - + $status = Get::get( 'status' ) ? stripslashes( Get::get( 'status' ) ) : $status; $offset = $offset ?: (int) Get::get('offset'); $amount = $amount ?: (int) Get::get('amount'); @@ -580,7 +591,7 @@ public function action__scanner__heuristic_analysis( $offset = null, $amount = n 'processed' => 0, 'scanned' => 0, ); - + if( $offset == 0 ){ $result = $this->db->fetch_all( 'SELECT COUNT(fast_hash) as cnt' @@ -589,16 +600,16 @@ public function action__scanner__heuristic_analysis( $offset = null, $amount = n ); $out['total'] = (int) $result[0]['cnt']; } - + $files_to_check = $this->db->fetch_all( 'SELECT path, source_type, source_name, source_version, status, checked_heuristic, fast_hash, real_full_hash, full_hash, weak_spots, difference, severity' .' FROM ' . self::table__scanner___files ." WHERE checked_heuristic = 0 AND (source_status <> 'OUTDATED' OR source_status IS NULL)" ." LIMIT $amount" ); - + if ( $files_to_check && count( $files_to_check )) { - + $prepared_query = $this->db->prepare('UPDATE '. self::table__scanner___files .' SET ' .' checked_heuristic = 1,' @@ -607,19 +618,19 @@ public function action__scanner__heuristic_analysis( $offset = null, $amount = n .' weak_spots = ?' .' WHERE fast_hash = ?' ); - + foreach ( $files_to_check as $file ) { $result = Scanner::file__scan__heuristic( $this->root, $file ); - + if(empty($result['error'])){ - + $status = $file['status'] === 'MODIFIED' ? 'MODIFIED' : $result['status']; $weak_spots = $result['weak_spots'] ? json_encode( $result['weak_spots'] ) : NULL; $severity = $file['severity'] ? $file['severity'] : ( $result['severity'] ? $result['severity'] : NULL ); - + $result_db = $prepared_query->execute( array( $status, @@ -628,10 +639,10 @@ public function action__scanner__heuristic_analysis( $offset = null, $amount = n $file['fast_hash'], ) ); - + $result['status'] !== 'OK' ? $out['found']++ : $out['found']; $result_db !== false ? $out['scanned']++ : $out['scanned']; - + }else return array( 'error' => 'Heuristic scan: ' . $result['error']); @@ -639,26 +650,26 @@ public function action__scanner__heuristic_analysis( $offset = null, $amount = n } } - + $out['end'] = $out['processed'] < $amount; - + return $out; } public function action__scanner__send_results( ) { - + if( ! $this->db ) return array('error' => 'DB_NOT_PROVIDED'); if( $this->db instanceof Cleantalk\USP\DB ) return array('error' => 'DB_BAD_CONNECTION'); - + $usp = State::getInstance(); - + $total_scanned = $this->count_files_by_status( "'UNKNOWN','OK','APPROVED','MODIFIED','INFECTED','QUARANTINED'" ); $bad_files = $this->get_files_by_status( "'UNKNOWN', 'MODIFIED'", array( 'path', 'full_hash', 'mtime', 'size', 'status') ); - + $unknown = array(); $modified = array(); - + if( count( $bad_files ) ){ foreach( $bad_files as $file ){ $file['path'] = Helper::is_windows() ? str_replace( '\\', '/', $file['path'] ) : $file['path']; @@ -691,7 +702,7 @@ public function action__scanner__send_results( ) { $modified, $unknown ); - + if( empty( $result['error'] ) ){ $usp->data->stat->scanner->last_sent = time(); @@ -707,7 +718,7 @@ public function action__scanner__send_results( ) { return $result; } - + public function get_files_by_status( $status, $data = '*' ) { $data = is_array( $data ) ? implode( ', ', $data ) : $data; return $this->db @@ -716,59 +727,59 @@ public function get_files_by_status( $status, $data = '*' ) { .' FROM ' . self::table__scanner___files ." WHERE status IN ( $status )"); } - + public function count_files_by_status( $status ) { return $this->db->fetch_all( 'SELECT COUNT(fast_hash) as cnt' .' FROM ' . self::table__scanner___files ." WHERE status IN ( $status )")[0]['cnt']; } - + public function next_state( $state ){ - + $state = self::$states[ array_search( $state, self::$states ) + 1 ]; $usp = State::getInstance(); $setting = 'scanner_' . $state; - + // Recursion if( isset( $usp->settings->$setting ) && $usp->settings->$setting === 0 ){ $state = $this->next_state( $state ); $this->offset = 0; } - + // Recursion. Base case return $state; } - + public static function action__scanner__controller___no_sql(){ - + $usp = State::getInstance(); - + sleep(5); - + $state = Get::get('state') ? Get::get('state') : 'clear_table'; - + $prev_state = $state; $additional_params = array(); - + switch($state){ - + // Cleaning table case 'clear_table': self::action__scanner__clear_table___no_sql(); $state = array_search( $state, self::$states ); break; - + // Signatures case 'signature_scan': if(empty($usp->settings->scanner_signature_analysis)){ $state = array_search( $state, self::$states ); break; } - - $result = self::action__scanner__signature_analysis___no_sql( + + $result = self::action__scanner__signature_analysis___no_sql( (int) Get::get( 'offset' ), 10, substr( CT_USP_SITE_ROOT, 0, - 1 ) @@ -778,34 +789,34 @@ public static function action__scanner__controller___no_sql(){ $state = 'heuristic_scan'; } break; - + // Heuristic case 'heuristic_scan': if(empty($usp->settings->scanner_heuristic_analysis)){ $state = 'cure_backup'; break; } - + $result = self::action__scanner__scan_heuristic___no_sql( (int) Get::get('offset'), 10 ); - + if(empty($result['error'])){ if($result['processed'] != 10) $state = 'send_results'; } break; - + // Send result case 'send_results': - + $result = self::action__scanner__send_results___no_sql(); $end = true; - + break; } - + // Make next call if everything is ok if(!isset($end) && empty($result['error'])){ $def_params = array( @@ -820,51 +831,51 @@ public static function action__scanner__controller___no_sql(){ 'get async' ); } - + // Delete or add an error empty($result['error']) ? $usp->error_delete($prev_state, 'and_save_data', 'cron_scan') : $usp->error_add($prev_state, $result, 'cron_scan'); - + return true; } - - + + /** * Clears all data about scanned files * * @return array */ public static function action__scanner__clear_table___no_sql(){ - + State::getInstance()->scan_result->count() ? State::getInstance()->scan_result->delete() : null; - + return array( 'processed' => 1, 'success' => 1, 'end' => true, ); } - + public function action__scanner__get_signatures___no_sql() { return $this->action__scanner__get_signatures(); } - + public static function action__scanner__signature_analysis___no_sql( $offset = 0, $amount = 10, $path = CT_USP_SITE_ROOT ){ - + $offset = Get::get( 'offset' ) ? Get::get( 'offset' ) : $offset; $amount = Get::get( 'amount' ) ? Get::get( 'amount' ) : $amount; $path = Get::get( 'path' ) ? realpath( Get::get( 'path' ) ) : realpath( $path ); - + $usp = State::getInstance(); - + $out = array( 'found' => 0, 'processed' => 0, ); - + // Count files on the first call with offset if( $offset == 0 ){ $path_to_scan = realpath( $path ); @@ -879,18 +890,18 @@ public static function action__scanner__signature_analysis___no_sql( $offset = 0 $scanner = new \Cleantalk\USP\Scanner\Scanner($path_to_scan, $root_path, $init_params); $out['total'] = $scanner->files_count; } - + $files_to_check = self::get_files( $offset, $amount ); - + if ( $files_to_check ) { - + $scanned = 0; $found = 0; - + if ( ! empty( $files_to_check ) ) { - + // Initialing results - + $signatures = new Storage('signatures', null, '', 'csv', array( 'id', 'name', @@ -901,15 +912,22 @@ public static function action__scanner__signature_analysis___no_sql( $offset = 0 'cci' ) ); $signatures = $signatures->convertToArray(); - + + $decoded_signatures = array(); + foreach ($signatures as $signature => $value){ + $decoded_signatures[$signature] = $value; + $decoded_signatures[$signature]['body'] = base64_decode($signature['body']); + } + + foreach ( $files_to_check as $file ) { - - $result = Scanner::file__scan__for_signatures( CT_USP_SITE_ROOT, $file, $signatures ); - + + $result = Scanner::file__scan__for_signatures( CT_USP_SITE_ROOT, $file, $decoded_signatures ); + if ( empty( $result['error'] ) ) { - + if ( $result['status'] !== 'OK' ) { - + $usp->scan_result[] = array( 'path' => $file['path'], 'size' => $file['size'], @@ -919,41 +937,41 @@ public static function action__scanner__signature_analysis___no_sql( $offset = 0 'fast_hash' => $file['fast_hash'], 'full_hash' => $file['full_hash'], ); - + $out['found']++; - + } - + }else return array( 'error' => 'Signature scan: ' . $result['error']); - + $out['processed']++; } - + $usp->scan_result->save(); - + } - + } - + $out['end'] = $out['processed'] < $amount; - + return $out; } - + public static function action__scanner__heuristic_analysis___no_sql( $offset = 0, $amount = 10, $path = CT_USP_SITE_ROOT ) { - + $offset = Get::get( 'offset' ) ? Get::get( 'offset' ) : $offset; $amount = Get::get( 'amount' ) ? Get::get( 'amount' ) : $amount; $path = Get::get( 'path' ) ? realpath( Get::get( 'path' ) ) : realpath( $path ); - + $usp = State::getInstance(); - + $out = array( 'found' => 0, 'processed' => 0, ); - + // Count files on the first call with offset if( $offset == 0 ){ $path_to_scan = realpath( $path ); @@ -968,20 +986,20 @@ public static function action__scanner__heuristic_analysis___no_sql( $offset = 0 $scanner = new \Cleantalk\USP\Scanner\Scanner($path_to_scan, $root_path, $init_params); $out['total'] = $scanner->files_count; } - - + + $files_to_check = self::get_files( $offset, $amount ); - + if ( $files_to_check ) { if ( count( $files_to_check ) ) { foreach ( $files_to_check as $file ) { - + $result = Scanner::file__scan__heuristic( CT_USP_SITE_ROOT, $file ); - + if ( empty( $result['error'] ) ) { - + if ( $result['status'] !== 'OK' ) { - + $usp->scan_result[] = array( 'path' => $file['path'], 'size' => $file['size'], @@ -991,38 +1009,38 @@ public static function action__scanner__heuristic_analysis___no_sql( $offset = 0 'fast_hash' => $file['fast_hash'], 'full_hash' => $file['full_hash'], ); - + $out['found'] ++; - + } - + }else return array( 'error' => 'Heuristic scan: ' . $result['error']); - + $out['processed']++; - + } - + $usp->scan_result->save(); } } - + $out['end'] = $out['processed'] < $amount; - + return $out; - + } - + public static function action__scanner__send_results___no_sql( $total_scanned = 0 ) { - + $usp = State::getInstance(); - + $total_scanned = $total_scanned ? $total_scanned : Get::get( 'total_scanned' ); - + $files = $usp->scan_result->convertToArray(); - + $files_count = count( $files ); - + $unknown = array(); $modified = array(); if($files_count){ @@ -1038,7 +1056,7 @@ public static function action__scanner__send_results___no_sql( $total_scanned = ); } } - + // API. Sending files scan result $result = API::method__security_mscan_logs( $usp->key, @@ -1049,26 +1067,26 @@ public static function action__scanner__send_results___no_sql( $total_scanned = $modified, $unknown ); - + if(empty($result['error'])){ - + $usp->data->stat->scanner->last_sent = time(); $usp->data->stat->scanner->last_scan = time(); $usp->data->stat->scanner->last_scan_amount = isset($_GET['total_scanned']) ? $_GET['total_scanned'] : $total_scanned; - + }else Err::add('scanner_result_send', $result['error']); - + $usp->data->save(); - + $result['end'] = 1; return $result; - + } - - + + public static function get_files( $offset = 0, $amount = 1500, $path = CT_USP_SITE_ROOT ) { - + $path_to_scan = realpath($path); $root_path = realpath( substr( CT_USP_SITE_ROOT, 0, - 1 ) ); $init_params = array( @@ -1082,11 +1100,11 @@ public static function get_files( $offset = 0, $amount = 1500, $path = CT_USP_SI 'files_mandatory' => array(), 'dir_exceptions' => array() ); - + $scanner = new Scanner($path_to_scan, $root_path, $init_params); - + return $scanner->files_count ? $scanner->files : false; } -} \ No newline at end of file +} diff --git a/uniforce/lib/Cleantalk/USP/Security/Firewall.php b/uniforce/lib/Cleantalk/USP/Security/Firewall.php index 9a60159..87071c2 100644 --- a/uniforce/lib/Cleantalk/USP/Security/Firewall.php +++ b/uniforce/lib/Cleantalk/USP/Security/Firewall.php @@ -30,6 +30,8 @@ class Firewall private $statuses_priority = array( 'PASS', 'DENY', + 'DENY_BY_SEC_FW', + 'DENY_BY_SPAM_FW', 'DENY_BY_NETWORK', 'DENY_BY_BFP', 'DENY_BY_DOS', @@ -166,43 +168,63 @@ public function run() { * @return array Single element array of result */ private function prioritize( $results ){ - - $current_fw_result_priority = 0; - $result = array( - 'status' => 'PASS', - 'ip' => '', - ); - - if( is_array( $results ) ) { - foreach ( $results as $fw_result ) { - $priority = array_search( $fw_result['status'], $this->statuses_priority ) + ( isset($fw_result['is_personal']) && $fw_result['is_personal'] ? count ( $this->statuses_priority ) : 0 ); - if( $priority >= $current_fw_result_priority ){ - $current_fw_result_priority = $priority; + + $final_priority = 0; + $result = array( + 'status' => 'PASS', + 'ip' => '', + ); + + if ( is_array($results) ) { + foreach ( $results as $fw_result ) { + /** + * Calculating priority. Records that have status PASS_BY_TRUSTED_NETWORK gain hardcoded highest priority. + * Personal records are next by priority in accordance of this->statuses_priority. + */ + $status_priority_from_table = array_search($fw_result['status'], $this->statuses_priority); + $is_personal_flag = isset($fw_result['is_personal']) && $fw_result['is_personal']; + $is_trusted_network_flag = isset($fw_result['status']) && $fw_result['status'] == 'PASS_BY_TRUSTED_NETWORK'; + $is_skipped_network_flag = isset($fw_result['status']) && $fw_result['status'] == 'PASS_AS_SKIPPED_NETWORK'; + //used to gain maximum priority + $total_count_of_statuses = count($this->statuses_priority); + $current_record_priority = $status_priority_from_table; + if ( $is_personal_flag || $is_trusted_network_flag ) { + // set maximum priority + $current_record_priority += $total_count_of_statuses; + // set maximum priority if skipped network (status 99 - PASS_AS_SKIPPED_NETWORK) + if ( $is_skipped_network_flag ) { + $current_record_priority++; + } + } + $final_status = $fw_result['status'] === 'PASS_AS_SKIPPED_NETWORK' ? 'PASS' : $fw_result['status']; + //set new final priority if it is less than current record priority + if ( $current_record_priority >= $final_priority ) { + $final_priority = $current_record_priority; + //proceed result array $result = array( - + // Necessary params 'module' => $fw_result['module'], 'ip' => $fw_result['ip'], - 'status' => $fw_result['status'], - + 'status' => $final_status, + // FW 'is_personal' => !empty( $fw_result['is_personal'] ) ? (int)$fw_result['is_personal'] : 0, 'country_code' => !empty( $fw_result['country_code'] ) ? $fw_result['country_code'] : '', 'network' => !empty( $fw_result['network'] ) ? $fw_result['network'] : 0, 'mask' => !empty( $fw_result['mask'] ) ? $fw_result['mask'] : 0, - + // WAF 'pattern' => !empty( $fw_result['pattern'] ) ? $fw_result['pattern'] : array(), - + // Security - 'event' => !empty( $fw_result['event'] ) ? $fw_result['event'] : 0, + 'event' => !empty( $fw_result['event'] ) ? $fw_result['event'] : 0, ); } } } - return $result; - + } /** diff --git a/uniforce/lib/Cleantalk/USP/Uniforce/Cron.php b/uniforce/lib/Cleantalk/USP/Uniforce/Cron.php index 4542c63..8d17969 100644 --- a/uniforce/lib/Cleantalk/USP/Uniforce/Cron.php +++ b/uniforce/lib/Cleantalk/USP/Uniforce/Cron.php @@ -16,10 +16,17 @@ public static function getTasks(){ if( ! file_exists( self::CRON_FILE ) ){ file_put_contents( self::CRON_FILE, - "is_login_page && ! $this->is_logged_in && $this->do_check && isset( $this->ip_array['real'] ) ){ - + $block_time = 20 * 60; // 20 minutes $allowed_count = 2; $allowed_interval = 900; // 15 min - + $bfp_blacklist = State::getInstance()->bfp_blacklist; $bfp_blacklist_fast = State::getInstance()->bfp_blacklist_fast; - + $found_ip = null; $current_ip__real = $this->ip_array['real']; - + // Check against black list foreach( $bfp_blacklist as $bad_ip => $bad_ip__details ){ if( $bad_ip === $current_ip__real ){ $found_ip = $bad_ip; $found_ip__details = $bad_ip__details; } - + } unset( $bad_ip, $bad_ip__details ); - + if( $found_ip ) { - + // Remove the IP from the blacklist and proceed the checking if( $found_ip__details->added + $block_time < time() ) { unset( $bfp_blacklist->$current_ip__real ); @@ -72,15 +72,15 @@ public function check(){ }else{ $results[] = array( 'status' => 'DENY_BY_BFP', ); } - + } - + // Check count of logins $found_ip = null; $js_on = spbct_js_test(); - + foreach( $bfp_blacklist_fast as $bad_ip => $bad_ip__details ){ - + if( $bad_ip === $current_ip__real && $bad_ip__details->added + $allowed_interval > time() ){ $found_ip = $bad_ip; $found_ip__details = array( @@ -92,33 +92,33 @@ public function check(){ unset( $bfp_blacklist_fast->$current_ip__real ); $bfp_blacklist_fast->save(); } - + } unset( $bad_ip, $bad_ip__details ); - + if( $found_ip ) { - + //increased allowed count to 20 if JS is on! if( $found_ip__details['js_on'] == 1 ) $allowed_count = $allowed_count * 2; - + // Check count of the logins and move the IP to the black list. if( $found_ip__details['count'] > $allowed_count ){ - + $bfp_blacklist->$current_ip__real['added'] = time(); $bfp_blacklist->save(); - + unset( $bfp_blacklist_fast->$current_ip__real ); $bfp_blacklist_fast->save(); - + $results[] = array( 'status' => 'DENY_BY_BFP', ); - + }else{ - + $bfp_blacklist_fast->$found_ip = $found_ip__details; $bfp_blacklist_fast->save(); - + } - + }else{ $bfp_blacklist_fast->$current_ip__real = array( 'added' => time(), @@ -126,9 +126,9 @@ public function check(){ 'count' => 1 ); $bfp_blacklist_fast->save(); - + } - + // Make the result standard foreach( $results as &$result ){ $result = array_merge( $result, array( @@ -138,13 +138,13 @@ public function check(){ 'module' => 'BFP' ) ); } - + } - + return $results; - + } - + /** * * @@ -154,48 +154,48 @@ public function actions_for_denied( $result ){ $this->state->data->stat->bfp->count++; $this->state->data->save(); } - + /** * @param array|string $fw_result */ public static function update_log( $fw_result ) { - + // Updating common firewall log if( is_array( $fw_result ) && $fw_result['status'] !== 'PASS' ){ parent::update_log( $fw_result ); return; } - + if( is_array( $fw_result ) && $fw_result['status'] !== 'DENY_BY_BFP' ){ $fw_result = 'auth_failed'; } - + global $salt; - + $params_default = array( - + // Necessary 'event' => null, 'auth_ip' => isset( $fw_result['ip'] ) ? $fw_result['ip'] : Helper::ip__get( array( 'real' ) ), 'time' => time(), - + // Unnecessary 'page_url' => substr( Server::get( 'HTTP_HOST' ) . Server::get( 'REQUEST_URI' ), 0, 1024 ), 'user_agent' => substr( Server::get( 'HTTP_USER_AGENT' ), 0, 1024 ), - + // @ToDo Unused params. Implement this logic to the next releases 'page' => null, 'page_time' => null, 'browser_sign' => null, ); $params = array_merge( $params_default, array( 'event' => $fw_result, ) ); - + // Inserting to the logs. $log_path = CT_USP_ROOT . 'data/security_logs/' . hash('sha256', $params['auth_ip'] . $salt . $params['event']) . '.log'; - + if( file_exists( $log_path ) ) $log = explode( ',', file_get_contents( $log_path ) ); - + $log = array( $params['event'], $params['auth_ip'], @@ -207,7 +207,7 @@ public static function update_log( $fw_result ) { $params['browser_sign'], isset($log[8]) ? (int) $log[8] + 1 : 1, ); - + $fd = fopen( $log_path, 'w' ); if( $fd ){ flock( $fd, LOCK_EX ); @@ -215,7 +215,7 @@ public static function update_log( $fw_result ) { fclose( $fd ); } } - + /** * Sends security log * @@ -224,82 +224,116 @@ public static function update_log( $fw_result ) { * @return array|bool|int[]|mixed|string[] */ public static function send_log( $ct_key ){ - + $log_dir_path = CT_USP_ROOT . 'data/security_logs'; - + if( is_dir( $log_dir_path ) ){ - + $log_files = array_diff( scandir( $log_dir_path ), array( '.', '..', 'index.php' ) ); - + if( ! empty( $log_files ) ){ - + //Compile logs $data = array(); - + foreach( $log_files as $log_file ){ - + $log = file_get_contents( $log_dir_path . DS . $log_file ); $log = str_getcsv( $log ); - + // Skip bad files if( ! isset( $log[0], $log[1], $log[2], $log[3], $log[4], $log[5], $log[6], $log[7], $log[8] ) ){ unlink( $log_dir_path . DS . $log_file ); continue; } - $auth_ip = $log[1] ? (string) $log[1] : '0.0.0.0'; + $_log = array( + 'event' => $log[0], + 'ip' => $log[1], + 'timestamp' => $log[2], + 'page_url' => $log[3], + 'http_user_agent' => $log[4], + //unused +// 'page' => $log[5], +// 'page_time' => $log[6], +// 'browser_sign' => $log[7], + 'hits' => $log[8], + ); - if( (string) $log[8] > 0 ){ - for( $i = 0; (string) $log[8] > $i; $i ++ ){ + //datetime legacy + if ( !empty($_log['timestamp']) && !Helper::arg_to_timestamp($_log['timestamp']) ){ + $_log['datetime'] = $_log['timestamp']; + } else { + $_log['datetime'] = !empty($_log['timestamp']) + ? gmdate('Y-m-d H:i:s', $_log['timestamp']) + : gmdate('Y-m-d H:i:s', 0); + } + + //timestamp conversion + if ( !empty($_log['timestamp']) && Helper::arg_to_timestamp($_log['timestamp']) ){ + $_log['timestamp'] = Helper::arg_to_timestamp($_log['timestamp']); + } else { + $_log['timestamp'] = 0; + } + + + $auth_ip = $_log['ip'] ? (string) $_log['ip']: '0.0.0.0'; + + if( (int) $_log['hits'] > 0 ){ //todo AG: for what this for cycle? + for( $i = 0; (int) $_log['hits'] > $i; $i ++ ){ $data[] = array( - 'datetime' => is_string($log[2]) ? $log[2] : gmdate('Y-m-d H:i:s', $log[2]), - 'datetime_gmt' => is_string($log[2]) ? strtotime($log[2]) : $log[2], + 'datetime' => $_log['datetime'], + 'datetime_gmt' => $_log['timestamp'], 'user_login' => null, - 'event' => (string) $log[0], - 'auth_ip' => strpos( ':', $auth_ip ) === false ? (int) sprintf( '%u', ip2long( $auth_ip ) ) : $auth_ip, - 'page_url' => (string) $log[3], + 'event' => (string) $_log['event'], + 'auth_ip' => strpos( ':', $auth_ip ) === false + ? (int) sprintf( '%u', ip2long( $auth_ip ) ) + : $auth_ip, + 'page_url' => (string) $_log['page_url'], 'event_runtime' => null, 'role' => null, ); } - }else{ - $data[] = array( - 'datetime' => is_string($log[2]) ? $log[2] : gmdate('Y-m-d H:i:s', $log[2]), - 'datetime_gmt' => is_string($log[2]) ? strtotime($log[2]) : $log[2], - 'user_login' => null, - 'event' => (string) $log[0], - 'auth_ip' => strpos( ':', $auth_ip ) === false ? (int) sprintf( '%u', ip2long( $auth_ip ) ) : $auth_ip, - 'page_url' => (string) $log[3], - 'event_runtime' => null, - 'role' => null, - ); + } else { + $data[] = array( + 'datetime' => $_log['datetime'], + 'datetime_gmt' => $_log['timestamp'], + 'user_login' => null, + 'event' => (string) $_log['event'], + 'auth_ip' => strpos( ':', $auth_ip ) === false + ? (int) sprintf( '%u', ip2long( $auth_ip ) ) + : $auth_ip, + 'page_url' => (string) $_log['page_url'], + 'event_runtime' => null, + 'role' => null, + ); } - + // Adding user agent if it's login event - if( in_array( (string) $log[0], array( 'login', 'login_2fa', 'login_new_device', 'logout', ) ) ){ + if( in_array( (string) $_log['event'], array( 'login', 'login_2fa', 'login_new_device', 'logout', ) ) ){ $data[] = array_merge( array_pop( $data ), array( - 'user_agent' => $log[4], + 'user_agent' => $_log['http_user_agent'], ) ); } } - - $result = API::method__security_logs( $ct_key, $data ); - + + $result = API::method__security_logs( $ct_key, $data ); + if( empty( $result['error'] ) ){ - + //Clear local table if it's ok. if( $result['rows'] == count( $data ) ){ - + foreach( $log_files as $log_file ){ if( file_exists( $log_dir_path . DS . $log_file ) ) unlink( $log_dir_path . DS . $log_file ); } - + return $result; - + }else{ return array( 'error' => 'SENT_AND_RECEIVED_LOGS_COUNT_DOESNT_MACH' ); } @@ -312,13 +346,13 @@ public static function send_log( $ct_key ){ }else{ return array( 'rows' => 0 ); } // No logs. Directory is not exists. - + } - + public static function is_logged_in( $cms ) { - + $cms = defined( 'USP_DASHBOARD' ) ? 'UniForce' : $cms; - + switch ( $cms ) { case 'Joomla' : return class_exists('JFactory') && \JFactory::getUser()->id; @@ -438,4 +472,4 @@ public static function is_login_page() { return false; } -} \ No newline at end of file +} diff --git a/uniforce/lib/Cleantalk/USP/Uniforce/Firewall/FW.php b/uniforce/lib/Cleantalk/USP/Uniforce/Firewall/FW.php index 77ddcaf..60471d1 100644 --- a/uniforce/lib/Cleantalk/USP/Uniforce/Firewall/FW.php +++ b/uniforce/lib/Cleantalk/USP/Uniforce/Firewall/FW.php @@ -12,22 +12,22 @@ use Cleantalk\USP\File\FileDB; class FW extends \Cleantalk\USP\Uniforce\Firewall\FirewallModule { - + // Domains which are skipping exclusions private static $test_domains = array( 'lc', 'loc', 'lh', 'test' ); - + public $module_name = 'FW'; - + /** * @var bool */ protected $test; - + // Additional params protected $api_key = ''; - + protected $real_ip; - + /** * FireWall_module constructor. * Use this method to prepare any data for the module working. @@ -35,18 +35,18 @@ class FW extends \Cleantalk\USP\Uniforce\Firewall\FirewallModule { * @param array $params */ public function __construct( $params = array() ){ - + parent::__construct( $params ); - + } - + /** * @param $ips */ public function ip__append_additional( &$ips ){ - + $this->real_ip = isset( $ips['real'] ) ? $ips['real'] : null; - + if( Get::get('spbct_test') == md5( $this->api_key ) ){ $ip_type = Helper::ip__validate( Get::get('spbct_test_ip') ); $test_ip = $ip_type == 'v6' ? Helper::ip__v6_normalize( Get::get('spbct_test_ip') ) : Get::get('spbct_test_ip'); @@ -57,23 +57,23 @@ public function ip__append_additional( &$ips ){ } } } - + /** * Check every IP using FireWall data table. * * @return array */ public function check() { - + $results = array(); - + foreach( $this->ip_array as $ip_origin => $current_ip ) { - + $ip_type = Helper::ip__validate($current_ip); - + // IPv4 query if( $ip_type && $ip_type === 'v4' ){ - + $current_ip_v4 = sprintf( "%u", ip2long( $current_ip ) ); // Creating IPs to search for ( $needles = array(), $m = 6; $m <= 32; $m ++ ) { @@ -82,13 +82,13 @@ public function check() { $needles[] = sprintf( "%u", bindec( $mask & base_convert( $current_ip_v4, 10, 2 ) ) ); } $needles = array_unique( $needles ); - + $db = new FileDB( 'fw_nets' ); $db_results = $db ->setWhere( array( 'network' => $needles, ) ) ->setLimit( 0, 20 ) ->select( 'network', 'mask', 'status', 'is_personal' ); - + for( $i = 0; isset( $db_results[ $i ] ); $i ++ ){ if( ! Helper::ip__mask_match( $current_ip, @@ -98,12 +98,12 @@ public function check() { } } } - + // In base if( ! empty( $db_results ) ) { - + foreach( $db_results as $entry ) { - + $result_entry = array( 'module' => $this->module_name, 'ip' => $current_ip, @@ -112,7 +112,7 @@ public function check() { 'network' => $entry['network'], 'mask' => $entry['mask'], ); - + switch ( $entry['status'] ) { case 2: $result_entry = array_merge( $result_entry, array('status' => 'PASS_BY_TRUSTED_NETWORK', ) ); break; case 1: $result_entry = array_merge( $result_entry, array('status' => 'PASS_BY_WHITELIST', ) ); break; @@ -121,14 +121,15 @@ public function check() { case -2: $result_entry = array_merge( $result_entry, array('status' => 'DENY_BY_DOS', ) ); break; case -3: $result_entry = array_merge( $result_entry, array('status' => 'DENY_BY_SEC_FW', ) ); break; case -4: $result_entry = array_merge( $result_entry, array('status' => 'DENY_BY_SPAM_FW', ) ); break; + case 99: $result_entry = array_merge( $result_entry, array('status' => 'PASS_AS_SKIPPED_NETWORK', ) ); break; + default: $result_entry = array_merge( $result_entry, array('status' => 'PASS', ) ); break; } - + $results[] = $result_entry; } - // Not in base }else { - + $results[] = array( 'module' => $this->module_name, 'ip' => $current_ip, @@ -138,15 +139,15 @@ public function check() { 'mask' => null, 'status' => 'PASS', ); - + } - + } - + return $results; - + } - + /** * Sends and wipe SFW log * @@ -155,38 +156,74 @@ public function check() { * @return array|bool array('error' => STRING) */ public static function send_log( $ct_key ){ - + $log_dir_path = CT_USP_ROOT . 'data/fw_logs'; - + if( ! is_dir( $log_dir_path ) ) return array( 'rows' => 0 ); - + $log_files = array_diff( scandir( $log_dir_path ), array( '.', '..', 'index.php' ) ); - + if( ! empty( $log_files ) ){ - + //Compile logs $data = array(); - + foreach ( $log_files as $log_file ){ - + $log = file_get_contents( $log_dir_path . DS . $log_file ); $log = str_getcsv( $log ); - + + $_log = array( + 'timestamp' => isset($log[2]) ? $log[2] : '', + 'page_url' => isset($log[6]) ? $log[6] : '', + 'ip' => isset($log[1]) ? $log[1] : '', + 'http_user_agent' => isset($log[7]) ? $log[7] : '', + 'request_method' => isset($log[8]) ? $log[8] : '', + 'x_forwarded_for' => isset($log[9]) ? $log[9] : '', + 'is_personal' => isset($log[10]) ? $log[10] : '', + 'matched_networks' => isset($log[11]) ? $log[11] : '', + 'hits' => isset($log[5]) ? $log[5] : '', + 'mask' => isset($log[12]) ? $log[12] : '' + ); + + //datetime legacy + if ( !empty($_log['timestamp']) && !Helper::arg_to_timestamp($_log['timestamp']) ){ + $_log['datetime'] = $_log['timestamp']; + } else { + $_log['datetime'] = !empty($_log['timestamp']) + ? gmdate('Y-m-d H:i:s', $_log['timestamp']) + : gmdate('Y-m-d H:i:s', 0); + } + + //timestamp conversion + if ( !empty($_log['timestamp']) && Helper::arg_to_timestamp($_log['timestamp']) ){ + $_log['timestamp'] = Helper::arg_to_timestamp($_log['timestamp']); + } else { + $_log['timestamp'] = 0; + } + //Compile log $to_data = array( - 'datetime' => isset( $log[2] ) ? gmdate('Y-m-d H:i:s', $log[2]) : 0, - 'datetime_gmt' => isset( $log[2] ) ? $log[2] : 0, - 'page_url' => isset( $log[6] ) ? $log[6] : 0, - 'visitor_ip' => isset( $log[1] ) ? ( Helper::ip__validate( $log[1] ) == 'v4' ? (int) sprintf( '%u', ip2long( $log[1] ) ) : (string) $log[1] ) : 0, - 'http_user_agent' => isset( $log[7] ) ? $log[7] : 0, - 'request_method' => isset( $log[8] ) ? $log[8] : 0, - 'x_forwarded_for' => isset( $log[9] ) ? $log[9] : 0, - 'is_personal' => isset( $log[10] ) ? $log[10] : null, - 'matched_networks' => isset( $log[11] ) ? $log[11] . '/' . $log[12] : null, - 'hits' => isset( $log[5] ) ? $log[5] : 0, + 'datetime' => $_log['datetime'], + //named because of reasons + 'datetime_gmt' => $_log['timestamp'], + 'page_url' => isset( $_log['page_url'] ) ? $_log['page_url'] : 0, + 'visitor_ip' => isset( $_log['ip'] ) + ? ( Helper::ip__validate( $_log['ip'] ) == 'v4' + ? (int) sprintf( '%u', ip2long( $_log['ip'] ) ) + : (string) $_log['ip'] ) + : 0, + 'http_user_agent' => isset( $_log['http_user_agent'] ) ? $_log['http_user_agent'] : 0, + 'request_method' => isset( $_log['request_method'] ) ? $_log['request_method'] : 0, + 'x_forwarded_for' => isset( $_log['x_forwarded_for'] ) ? $_log['x_forwarded_for'] : 0, + 'is_personal' => isset( $_log['is_personal'] ) ? $_log['is_personal'] : null, + 'matched_networks' => isset( $_log['matched_networks'] ) + ? $_log['matched_networks'] . '/' . $_log['mask'] + : null, + 'hits' => isset( $_log['hits'] ) ? $_log['hits'] : 0, ); - + // Legacy switch( $log[3] ){ case 'PASS_BY_TRUSTED_NETWORK': $to_data['status_efw'] = 3; break; @@ -203,7 +240,7 @@ public static function send_log( $ct_key ){ case 'DENY_BY_SEC_FW': $to_data['status_efw'] = -8; break; case 'DENY_BY_SPAM_FW': $to_data['status_efw'] = -9; break; } - + switch( $log[3] ){ case 'PASS_BY_TRUSTED_NETWORK': $to_data['status'] = 3; break; case 'PASS_BY_WHITELIST': $to_data['status'] = 2; break; @@ -219,23 +256,23 @@ public static function send_log( $ct_key ){ case 'DENY_BY_SEC_FW': $to_data['status'] = -8; break; case 'DENY_BY_SPAM_FW': $to_data['status'] = -9; break; } - + $data[] = $to_data; - + } unset($key, $value, $result, $to_data); - + //Sending the request $result = API::method__security_logs__sendFWData( $ct_key, $data ); - + //Checking answer and deleting all lines from the table if( empty( $result['error'] ) ){ - + if( $result['rows'] == count( $data ) ){ - + foreach ( $log_files as $log_file ){ unlink( $log_dir_path . DS . $log_file ); } - + return $result; }else return array( 'error' => 'SENT_AND_RECEIVED_LOGS_COUNT_DOESNT_MACH' ); @@ -244,47 +281,53 @@ public static function send_log( $ct_key ){ }else return array( 'rows' => 0 ); } - + public static function update( $api_key ){ - + $multifile_url = Get::get( 'multifile_url' ); $url_count = Get::get( 'url_count' ); $current_file_num = Get::get( 'current_file_num' ); - + $files = isset( State::getInstance()->fw_stats['updating_folder'] ) ? glob( State::getInstance()->fw_stats['updating_folder'] . DS . '/*csv.gz' ) : array(); - + // Get multifiles if( ! $multifile_url ){ - + + if ( State::getInstance()->fw_stats->updating ) { + return ['error' => 'Updating is under process.']; + } + + State::getInstance()->fw_stats->updating = true; + State::getInstance()->fw_stats->save(); + $result = self::update__get_multifiles( $api_key ); if( ! empty( $result['error'] ) ){ return $result; } - + $update_folder = self::update__prepare_upd_dir( CT_USP_ROOT . DS . 'fw_files' ); if( ! empty( $update_folder['error'] ) ){ return $update_folder; } - + State::getInstance()->fw_stats->updating_folder = CT_USP_ROOT . DS . 'fw_files'; $download_files_result = Helper::http__download_remote_file__multi( $result['file_urls'], State::getInstance()->fw_stats->updating_folder ); if( empty( $download_files_result['error'] ) ){ - - State::getInstance()->fw_stats->updating = true; + State::getInstance()->fw_stats->update_percent = 0; - State::getInstance()->fw_stats->entries = 0; + State::getInstance()->fw_stats->entries_temp = 0; State::getInstance()->fw_stats->update_start = time(); State::getInstance()->fw_stats->save(); - + Helper::http__request( Server::get( 'HTTP_HOST' ) . CT_USP_AJAX_URI, array( 'spbc_remote_call_token' => md5( $api_key ), 'spbc_remote_call_action' => 'update_security_firewall', 'plugin_name' => 'spbc', - + // Additional params 'multifile_url' => $result['multifile_url'], 'url_count' => count( $result['file_urls'] ), @@ -292,26 +335,30 @@ public static function update( $api_key ){ ), array( 'get', 'async' ) ); - - }else - return $result; - + + }else { + return $result; + } + // Write to DB }elseif( count( $files ) ){ - + $result = self::update__write_to_db( reset( $files ) ); - + if( empty( $result['error'] ) ){ - + if( file_exists(reset($files)) ){ unlink(reset($files)); } - + //Increment firewall entries - State::getInstance()->fw_stats->entries += $result; + State::getInstance()->fw_stats->entries_temp += $result; State::getInstance()->fw_stats->update_percent = round( ( ( (int) $current_file_num + 1 ) / (int) $url_count ), 2) * 100; State::getInstance()->fw_stats->save(); + // Make sure to write all fs actions + sleep(3); + // Make next call Helper::http__request( Server::get( 'HTTP_HOST' ) . CT_USP_AJAX_URI, @@ -328,46 +375,54 @@ public static function update( $api_key ){ array( 'get', 'async' ) ); - }else - return $result; - + } else { + return $result; + } + // Write exclusions }else{ - + $result = self::update__write_to_db__exclusions(); usleep( 500000 ); - + if( empty( $result['error'] ) ){ - + //Increment firewall entries - State::getInstance()->fw_stats->entries += $result; + State::getInstance()->fw_stats->entries_temp += $result; + State::getInstance()->fw_stats->entries = State::getInstance()->fw_stats->entries_temp; State::getInstance()->fw_stats->updating = false; State::getInstance()->fw_stats->update_percent = 0; State::getInstance()->fw_stats->last_update = time(); State::getInstance()->fw_stats->updated_in = time() - State::getInstance()->fw_stats->update_start; State::getInstance()->fw_stats->save(); - }else - return $result; + if ($url_count === $current_file_num) { + rename(CT_USP_ROOT . DS . 'data' . DS . 'fw_nets_network_temp.btree', CT_USP_ROOT . DS . 'data' . DS . 'fw_nets_network.btree'); + rename(CT_USP_ROOT . DS . 'data' . DS . 'fw_nets_temp.storage', CT_USP_ROOT . DS . 'data' . DS . 'fw_nets.storage'); + } + + } else { + return $result; + } } } - + public static function update__prepare_upd_dir( $dir_name ){ - + global $spbc; - + if( $dir_name === '' ) { return array( 'error' => 'FW dir can not be blank.' ); } - + $dir_name .= DS; - + if( ! is_dir( $dir_name ) && ! mkdir( $dir_name ) ){ - + return ! is_writable( CT_USP_ROOT ) ? array( 'error' => 'Can not to make FW dir. Low permissions: ' . fileperms( CT_USP_ROOT ) ) : array( 'error' => 'Can not to make FW dir. Unknown reason.' ); - + } else { $files = glob( $dir_name . '/*' ); if( $files === false ){ @@ -382,11 +437,11 @@ public static function update__prepare_upd_dir( $dir_name ){ } } } - + return (bool) file_put_contents( $dir_name . 'index.php', ' $file_url, 'file_urls' => array_column(Helper::buffer__parse__csv($data), 0), ); - + }else return $result__clear_db; }else @@ -457,7 +512,7 @@ static public function update__get_multifiles( $spbc_key ){ }else return $result; } - + /** * Writes entries from remote files to Firewall database. * @@ -466,32 +521,37 @@ static public function update__get_multifiles( $spbc_key ){ * @return array|bool|int|mixed|string */ public static function update__write_to_db( $file_url ){ - + $data = Helper::get_data_from_local_gz( $file_url ); - + if ( ! Err::check() ) { - + $db = new FileDB( 'fw_nets' ); $networks_to_skip = array(); if( in_array( Server::get_domain(), self::$test_domains ) ){ $networks_to_skip[] = ip2long( '127.0.0.1' ); } - - + + $inserted = 0; while( $data !== '' ){ - + for( $i = 0, $nets_for_save = array(); $i < 2500 && $data !== ''; $i++ ){ - + $entry = Helper::buffer__csv__pop_line_to_array( $data ); if( in_array($entry[0], $networks_to_skip ) ){ continue; } - + + //skip ipv6 because of reasons :( + if ( !is_numeric($entry[0]) ){ + continue; + } + $nets_for_save[] = array( 'network' => $entry[0], 'mask' => sprintf( '%u', bindec( str_pad( str_repeat( '1', $entry[1] ), 32, 0, STR_PAD_RIGHT ) ) ), @@ -500,29 +560,30 @@ public static function update__write_to_db( $file_url ){ 'is_personal' => isset( $entry[4] ) ? intval( $entry[4] ) : 0, // 'country' => isset( $entry[5] ) ? trim( $entry[5], '"' ) : 0, ); - + } - + if( ! empty( $nets_for_save ) ){ - - $inserted += $db->insert( $nets_for_save ); - + + $inserted += $db->insertTemp( $nets_for_save ); + if ( Err::check() ){ Err::prepend( 'Updating FW' ); error_log( var_export( Err::get_all( 'string' ), true ) ); return array( 'error' => Err::get_last( 'string' ), ); } - - }else - Err::add( 'Updating FW', 'No data to save' ); + + } else { + Err::add( 'Updating FW', 'No data to save' ); + } } - return $inserted; - - }else - Err::prepend( 'Updating FW' ); + + } else { + Err::prepend( 'Updating FW' ); + } } - + /** * Adding local exclusions to to the FireWall database. * @@ -531,16 +592,16 @@ public static function update__write_to_db( $file_url ){ * @return array|bool|int|mixed|string */ static public function update__write_to_db__exclusions( $exclusions = array() ){ - + //Exclusion for servers IP (SERVER_ADDR) if ( Server::get('HTTP_HOST') ) { - + // Exceptions for local hosts if( ! in_array( Server::get_domain(), self::$test_domains ) ){ $exclusions[] = Helper::dns__resolve( Server::get( 'HTTP_HOST' ) ); $exclusions[] = '127.0.0.1'; } - + foreach ( $exclusions as $exclusion ) { if (Helper::ip__validate($exclusion) && sprintf('%u', ip2long($exclusion))) { $nets_for_save[] = array( @@ -552,37 +613,49 @@ static public function update__write_to_db__exclusions( $exclusions = array() ){ } } } - + $db = new FileDB( 'fw_nets' ); - + if( isset( $nets_for_save ) ){ - - $inserted = $db->insert( $nets_for_save ); - + + $inserted = $db->insertTemp( $nets_for_save ); + if ( Err::check() ){ Err::prepend('Updating FW exclusions'); error_log( var_export( Err::get_all('string'), true ) ); return array( 'error' => Err::get_last( 'string' ), ); } - + return $inserted; - + }else return 0; } - + /** * Clear SFW table * * @return bool[] */ public static function clear_data() { - + // Clean current database $db = new FileDB( 'fw_nets' ); $db->delete(); - + + return array( 'success' => true, ); + + } + + /** + * Clear temp SFW table + * + * @return bool[] + */ + public static function clear_temp_data() { + $db = new FileDB( 'fw_nets' ); + $db->deleteTemp(); + return array( 'success' => true, ); - } -} \ No newline at end of file +} diff --git a/uniforce/lib/Cleantalk/USP/Uniforce/Firewall/WAF.php b/uniforce/lib/Cleantalk/USP/Uniforce/Firewall/WAF.php index 6551140..a942318 100644 --- a/uniforce/lib/Cleantalk/USP/Uniforce/Firewall/WAF.php +++ b/uniforce/lib/Cleantalk/USP/Uniforce/Firewall/WAF.php @@ -9,26 +9,26 @@ use Cleantalk\USP\Scanner\ScannerH; class WAF extends \Cleantalk\USP\Uniforce\Firewall\FirewallModule { - + public $module_name = 'WAF'; - + protected $waf_xss_check = false; protected $waf_sql_check = false; protected $waf_file_check = false; protected $waf_exploit_check = false; - + private $waf_pattern = array(); // Why WAF is triggered (reason) - + private $waf_sql_patterns = array(); private $waf_exploit_patterns = array(); private $waf_xss_patterns = array(); - + public $waf_file_mime_check = array( 'text/x-php', 'text/plain', 'image/x-icon', ); - + /** * FireWall_module constructor. * Use this method to prepare any data for the module working. @@ -36,72 +36,78 @@ class WAF extends \Cleantalk\USP\Uniforce\Firewall\FirewallModule { * @param array $params */ public function __construct( $params = array() ){ - + parent::__construct( $params ); - + } - + /** * Use this method to execute main logic of the module. * @return mixed */ public function check() { - + $results = array(); - + // Get signatures $signatures = $this->signatures__get(); - + if ( $signatures ) { - - foreach ( $signatures as $signature ) { - - switch ( $signature['attack_type'] ) { - + + $decoded_signatures = array(); + foreach ($signatures as $signature => $value){ + $decoded_signatures[$signature] = $value; + $decoded_signatures[$signature]['body'] = base64_decode($value['body']); + } + + foreach ( $decoded_signatures as $signature_body_decoded ) { + + switch ( $signature_body_decoded['attack_type'] ) { + case 'SQL_INJECTION': - $this->waf_sql_patterns[] = $signature['body']; + $this->waf_sql_patterns[] = $signature_body_decoded['body']; break; case 'XSS': - $this->waf_xss_patterns[] = $signature['body']; + $this->waf_xss_patterns[] = $signature_body_decoded['body']; break; case 'EXPLOIT': - $this->waf_exploit_patterns[] = $signature['body']; + $this->waf_exploit_patterns[] = $signature_body_decoded['body']; break; } } } - + // XSS if( $this->waf_xss_check ){ if($this->waf_xss_check($_POST) || $this->waf_xss_check($_GET) || $this->waf_xss_check($_COOKIE)){ $results[] = array('ip' => end($this->ip_array), 'is_personal' => false, 'status' => 'DENY_BY_WAF_XSS', 'pattern' => $this->waf_pattern); } } - + // SQL-injection if( $this->waf_sql_check ){ if($this->waf_sql_check($_POST) || $this->waf_sql_check($_GET)){ $results[] = array('ip' => end($this->ip_array), 'is_personal' => false, 'status' => 'DENY_BY_WAF_SQL', 'pattern' => $this->waf_pattern); } } - + // File if ($this->waf_file_check ){ if($this->waf_file_check()){ $results[] = array('ip' => end($this->ip_array), 'is_personal' => false, 'status' => 'DENY_BY_WAF_FILE', 'pattern' => $this->waf_pattern); } } - + // Exploits if( $this->waf_exploit_check ){ if($this->waf_exploit_check()){ $results[] = array('ip' => end($this->ip_array), 'is_personal' => false, 'status' => 'DENY_BY_WAF_EXPLOIT', 'pattern' => $this->waf_pattern); } } - + if( ! $results ) $results[] = array( 'status' => 'PASS' ); - + foreach( $results as &$result ){ $result = array_merge( $result, @@ -112,14 +118,18 @@ public function check() { ) ); } - + return $results; - + } - - private function signatures__get(){ - - $signatures = new Storage('signatures', null, '', 'csv', array( + + /** + * Get array of WAF signatures. Return array of signatures or false if no WAF rules found. + * @return array|false + */ + private function signatures__get(){ + + $signatures_source = new Storage('signatures', null, '', 'csv', array( 'id', 'name', 'body', @@ -128,14 +138,17 @@ private function signatures__get(){ 'submitted', 'cci' ) ); - - foreach( $signatures->convertToArray() as $signature ){ - if( $signature['type'] === 'WAF_RULE' ) - $signatures[] = $signature; - } - return $signatures; + + $signatures = []; + + foreach ( $signatures_source->convertToArray() as $signature ) { + if ( $signature['type'] === 'WAF_RULE' ) { + $signatures[] = $signature; + } + } + return !empty($signatures) ? $signatures : false; } - + /** * Checks array for XSS-attack patterns * @@ -144,9 +157,9 @@ private function signatures__get(){ * @return bool */ private function waf_xss_check( $arr ) { - + foreach( $arr as $name => $param ){ - + // Recursion if( is_array( $param ) ){ $result = $this->waf_xss_check( $param ); @@ -154,7 +167,7 @@ private function waf_xss_check( $arr ) { return true; continue; } - + //Check foreach( $this->waf_xss_patterns as $pattern ){ $is_regexp = preg_match( '@^/.*/$@', $pattern ) || preg_match( '@^#.*#$@', $pattern ); @@ -168,11 +181,11 @@ private function waf_xss_check( $arr ) { } } } - + return false; - + } - + /** * Checks array for SQL injections * @@ -181,16 +194,16 @@ private function waf_xss_check( $arr ) { * @return bool */ private function waf_sql_check( $arr ) { - + foreach( $arr as $name => $param ){ - + if( is_array( $param ) ){ $result = $this->waf_sql_check( $param ); if( $result === true ) return true; continue; } - + foreach( $this->waf_sql_patterns as $pattern ){ $is_regexp = preg_match( '@^/.*/$@', $pattern ) || preg_match( '@^#.*#$@', $pattern ); @@ -203,18 +216,18 @@ private function waf_sql_check( $arr ) { } } } - + return false; - + } - + /** * Checks $_SERVER['QUERY_STRING'] for exploits * * @return bool */ private function waf_exploit_check() { - + foreach( $this->waf_exploit_patterns as $pattern ){ $is_regexp = preg_match( '@^/.*/$@', $pattern ) || preg_match( '@^#.*#$@', $pattern ); @@ -226,18 +239,18 @@ private function waf_exploit_check() { return true; } } - + return false; - + } - + /** * Checks uploaded files for malicious code * * @return boolean Does the file contain malicious code */ private function waf_file_check() { - + if( ! empty( $_FILES ) ){ foreach( $_FILES as $filez ){ if ( ( empty($filez['errror'] ) || $filez['errror'] == UPLOAD_ERR_OK ) ) { @@ -264,8 +277,8 @@ private function waf_file_check() { } } } - + return false; - + } -} \ No newline at end of file +} diff --git a/uniforce/uniforce.php b/uniforce/uniforce.php index b4dcbd5..d434565 100644 --- a/uniforce/uniforce.php +++ b/uniforce/uniforce.php @@ -16,12 +16,16 @@ // Helper functions require_once( CT_USP_INC . 'functions.php' ); +global $usp_is_js_attached; + +$usp_is_js_attached = false; + if( $usp->settings->fw || $usp->settings->waf || $usp->settings->bfp ){ // Security FireWall $firewall = new \Cleantalk\USP\Uniforce\Firewall(); - if( $usp->settings->fw && ! $usp->fw_stats->updating && $usp->fw_stats->entries ) + if( $usp->settings->fw && $usp->fw_stats->entries ) $firewall->module__load( new \Cleantalk\USP\Uniforce\Firewall\FW( array( 'state' => $usp, @@ -61,7 +65,7 @@ return; } - if( $firewall->module__is_loaded__any() ){ + if( $firewall->module__is_loaded__any() && ! usp__is_admin() ){ $firewall->run(); } @@ -75,6 +79,10 @@ \Cleantalk\USP\Uniforce\Firewall\BFP::update_log( 'view' ); function uniforce_attach_js( $buffer ){ + global $usp_is_js_attached; + if ($usp_is_js_attached){ + return $buffer; + } if( !(isset($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest') // No ajax @@ -91,11 +99,11 @@ function uniforce_attach_js( $buffer ){ 1 ); } - + if( State::getInstance()->settings->bfp && State::getInstance()->detected_cms ){ - + if( \Cleantalk\USP\Uniforce\Firewall\BFP::is_logged_in( State::getInstance()->detected_cms ) ) { - + if( ! Cookie::get( 'spbct_authorized' ) ){ setcookie( 'spbct_authorized', md5( State::getInstance()->key ), 0, '/' ); \Cleantalk\USP\Uniforce\Firewall\BFP::update_log( 'login' ); @@ -103,18 +111,20 @@ function uniforce_attach_js( $buffer ){ } }else{ - + if( Cookie::get('spbct_authorized') ) { \Cleantalk\USP\Uniforce\Firewall\BFP::update_log( 'logout' ); setcookie( 'spbct_authorized', md5( State::getInstance()->key ), time()-3600, '/' ); } - + if( Post::get( 'spbct_login_form' ) ) \Cleantalk\USP\Uniforce\Firewall\BFP::update_log( 'auth_failed' ); - + } } + $usp_is_js_attached = true; + return $buffer; } diff --git a/uniforce/version.php b/uniforce/version.php index c6197cf..9e152e0 100644 --- a/uniforce/version.php +++ b/uniforce/version.php @@ -1,4 +1,4 @@ @@ -29,23 +29,23 @@ - + Universal Anti-Spam Plugin by CleanTalk - + - + - +
- +
Cleantalk logo @@ -70,14 +70,14 @@
- +
- +
- + + - @@ -85,9 +85,9 @@ var security = ''; var ajax_url = location.href; - + - + diff --git a/uniforce/view/install.php b/uniforce/view/install.php index bbacfda..8d9ed33 100644 --- a/uniforce/view/install.php +++ b/uniforce/view/install.php @@ -50,7 +50,7 @@

Warning: Couldn't connect to cloud SQL. Malware scanner will use local database to store scan results.

- +