From 3de013e83d426f17d5046da37a5785910d206f02 Mon Sep 17 00:00:00 2001 From: christer kahasha <62720246+christer77@users.noreply.github.com> Date: Fri, 4 Oct 2024 15:54:28 +0200 Subject: [PATCH] Improved detection and protection against business email compromise (BEC) such as CEO fraud --- modules/imap/handler_modules.php | 116 ++++++++++++++++++++++++++++++- modules/imap/hm-imap.php | 4 +- modules/imap/output_modules.php | 49 +++++++++++++ modules/imap/setup.php | 6 ++ 4 files changed, 170 insertions(+), 5 deletions(-) diff --git a/modules/imap/handler_modules.php b/modules/imap/handler_modules.php index cadf6b2fb..667a6e050 100644 --- a/modules/imap/handler_modules.php +++ b/modules/imap/handler_modules.php @@ -814,7 +814,12 @@ public function process() { $offset = 0; $msgs = array(); $list_page = 1; + $include_content_body = false; $include_preview = $this->user_config->get('active_preview_message_setting', false); + $ceo_use_detect_ceo_fraud = $this->user_config->get('ceo_use_detect_ceo_fraud_setting', false); + if ($include_preview || $ceo_use_detect_ceo_fraud) { + $include_content_body = true; + } list($success, $form) = $this->process_form(array('imap_server_id', 'folder')); if ($success) { @@ -840,16 +845,28 @@ public function process() { $existingEmails = array_map(function($c){ return $c->value('email_address'); },$contact_list); - list($total, $results) = $imap->get_mailbox_page(hex2bin($form['folder']), $sort, $rev, $filter, $offset, $limit, $keyword, $existingEmails, $include_preview); + list($total, $results) = $imap->get_mailbox_page(hex2bin($form['folder']), $sort, $rev, $filter, $offset, $limit, $keyword, $existingEmails, $include_content_body); } else { - list($total, $results) = $imap->get_mailbox_page(hex2bin($form['folder']), $sort, $rev, $filter, $offset, $limit, $keyword, null, $include_preview); + list($total, $results) = $imap->get_mailbox_page(hex2bin($form['folder']), $sort, $rev, $filter, $offset, $limit, $keyword, null, $include_content_body); } foreach ($results as $msg) { $msg['server_id'] = $form['imap_server_id']; $msg['server_name'] = $details['name']; $msg['folder'] = $form['folder']; + $uid = $msg['uid']; + // echo "
"; var_dump("cxxxxxxxxxxxxxxxx, ", $msg); die; + if ($ceo_use_detect_ceo_fraud) { + $isValid = $this->validationMessage($msg['to'], $uid, $form['folder'], $msg['subject'], $msg['preview_msg'], $imap, $this->cache, $form['imap_server_id']); + if (! $isValid) { + $msg = []; + } + } + if (! $include_preview && isset($msg['preview_msg'])) { + $msg['preview_msg'] = ""; + } $msgs[] = $msg; } + if ($imap->selected_mailbox) { $imap->selected_mailbox['detail']['exists'] = $total; $this->out('imap_folder_detail', array_merge($imap->selected_mailbox, array('offset' => $offset, 'limit' => $limit))); @@ -862,6 +879,82 @@ public function process() { $this->out('do_not_flag_as_read_on_open', $this->user_config->get('unread_on_open_setting', false)); } } + public function validationMessage($email, $uid, $current_folder, $subject, $msg, $imap, $cache, $imap_server_id,) { + // 1. Check Suspicious Terms or Requests + $suspiciousTerms = explode(",", $this->user_config->get("ceo_suspicious_terms_setting")); + if ($this->detectSuspiciousTerms($msg, $suspiciousTerms) || $this->detectSuspiciousTerms($subject, $suspiciousTerms)) { + + // 2. check ceo_rate_limit + $amount = $this->extractAmountFromEmail($msg); + $amountLimit = $this->user_config->get("ceo_rate_limit_setting"); + if ($amount > $amountLimit) { + // 3. Check Sender's Email Address + $folder = "Suspicious emails"; + if (!count($imap->get_mailbox_status($folder))) { + $imap->create_mailbox($folder); + } + $dest_folder = bin2hex($folder); + // $server_id = $imap_server_id ."_". bin2hex($folder); + $server_ids = array( + $imap_server_id => [ + $current_folder => $uid + ] + ); + + if ($this->user_config->get("ceo_use_trusted_contact_setting")) { + $contacts = $this->get('contact_store'); + $contact_list = $contacts->getAll(); + $existingEmails = array_map(function($c){ + return $c->value('email_address'); + },$contact_list); + if (!$this->isValidateAddrEmail(array_values($existingEmails), $email)) { + // 4. action to execute implement here + $xxx = imap_move_same_server($server_ids, "move", $cache, [null, null, $dest_folder]); + var_dump($xxx); die; + return false; + } + } else { + // 4. action to execute implement here + $xxx = imap_move_same_server($server_ids, "move", $cache, [null, null, $dest_folder]); + var_dump($xxx); die; + return false; + } + } + } + return true; + } + private function detectSuspiciousTerms($msg, $suspiciousTerms) { + foreach ($suspiciousTerms as $phrase) { + if (stripos($msg, trim($phrase)) !== false) { + return true; + } + } + + return false; + } + private function detectUnusualAmount($normalLimit, $amount) { + if ($amount > $normalLimit) { + return true; + } + return false; + } + private function isValidateAddrEmail($trustedDomain, $email) { + foreach ($trustedDomain as $e) { + if ($email === $e) { + return true; + } + } + return false; + } + private function extractAmountFromEmail($emailBody) { + $pattern = '/\b\d+(?:,\d+)?\.?\d*\s*(?:USD|dollars?|US\$?|EUR|euros?|€|JPY|yen|¥|GBP|pounds?|£|CAD|CAD\$|AUD|AUD\$)/i'; + + preg_match_all($pattern, $emailBody, $matches); + + if (count($matches[0]) > 0) { + return $matches[0][0]; + } + } } /** @@ -2167,6 +2260,7 @@ function process_move_messages_in_screen_email_enabled_callback($val) { return $ process_site_setting('move_messages_in_screen_email', $this, 'process_move_messages_in_screen_email_enabled_callback', true, true); } } + class Hm_Handler_process_setting_active_preview_message extends Hm_Handler_Module { public function process() { function process_active_preview_message_callback($val) { return $val; } @@ -2174,4 +2268,20 @@ function process_active_preview_message_callback($val) { return $val; } } } - +/** + * Process setting_ceo_detection_fraud in the settings page + * @subpackage core/handler + */ +class Hm_Handler_process_setting_ceo_detection_fraud extends Hm_Handler_Module { + public function process() { + function process_ceo_use_detect_ceo_fraud_callback($val) { return $val; } + function process_ceo_use_trusted_contact_callback($val) { return $val; } + function process_ceo_suspicious_terms_callback($val) { return $val; } + function process_ceo_rate_limit_callback($val) { return $val; } + + process_site_setting('ceo_use_detect_ceo_fraud', $this, 'process_ceo_use_detect_ceo_fraud_callback'); + process_site_setting('ceo_use_trusted_contact', $this, 'process_ceo_use_trusted_contact_callback'); + process_site_setting('ceo_suspicious_terms', $this, 'process_ceo_suspicious_terms_callback'); + process_site_setting('ceo_rate_limit', $this, 'process_ceo_rate_limit_callback'); + } +} diff --git a/modules/imap/hm-imap.php b/modules/imap/hm-imap.php index fbb8dbaa8..0bb0d8635 100644 --- a/modules/imap/hm-imap.php +++ b/modules/imap/hm-imap.php @@ -875,7 +875,7 @@ public function poll() { * @param bool $raw flag to disable decoding header values * @return array list of headers and values for the specified uids */ - public function get_message_list($uids, $raw=false, $include_preview = false) { + public function get_message_list($uids, $raw=false, $include_content_body = false) { if (is_array($uids)) { sort($uids); $sorted_string = implode(',', $uids); @@ -891,7 +891,7 @@ public function get_message_list($uids, $raw=false, $include_preview = false) { $command .= 'X-GM-MSGID X-GM-THRID X-GM-LABELS '; } $command .= "BODY.PEEK[HEADER.FIELDS (SUBJECT X-AUTO-BCC FROM DATE CONTENT-TYPE X-PRIORITY TO LIST-ARCHIVE REFERENCES MESSAGE-ID X-SNOOZED)]"; - if ($include_preview) { + if ($include_content_body) { $command .= " BODY[0.1]"; } $command .= ")\r\n"; diff --git a/modules/imap/output_modules.php b/modules/imap/output_modules.php index d730edeeb..b94079047 100644 --- a/modules/imap/output_modules.php +++ b/modules/imap/output_modules.php @@ -1486,6 +1486,7 @@ protected function output() { return $res; } } + class Hm_Output_setting_active_preview_message extends Hm_Output_Module { protected function output() { $settings = $this->get('user_settings', array()); @@ -1500,4 +1501,52 @@ protected function output() { return $res; } } +class Hm_Output_setting_ceo_detection_fraud extends Hm_Output_Module { + protected function output() { + $settings = $this->get('user_settings', array()); + $ceo_use_detect_ceo_fraud = ""; + $ceo_use_trusted_contact = "checked"; + $ceo_suspicious_terms = "wire transfer, urgent, account details, payment instruction"; + $ceo_rate_limit = "100"; + if (array_key_exists('ceo_use_detect_ceo_fraud', $settings)) { + if ($settings['ceo_use_detect_ceo_fraud']) { + $ceo_use_detect_ceo_fraud = "checked"; + } else { + $ceo_use_detect_ceo_fraud = ""; + } + } + if (array_key_exists('ceo_use_trusted_contact', $settings)) { + if ($settings['ceo_use_trusted_contact']) { + $ceo_use_trusted_contact = "checked"; + } else { + $ceo_use_trusted_contact = ""; + } + } + + if (array_key_exists('ceo_suspicious_terms', $settings) && $settings['ceo_suspicious_terms']) { + if ($settings['ceo_suspicious_terms']) { + $ceo_suspicious_terms = $settings['ceo_suspicious_terms']; + } + } + if (array_key_exists('ceo_rate_limit', $settings) && $settings['ceo_rate_limit']) { + if ($settings['ceo_rate_limit']) { + $ceo_rate_limit = $settings['ceo_rate_limit']; + } + } + + $res = ''; + $res .= ' '; + $res .= ' '; + $res .= ' '; + return $res; + } +} diff --git a/modules/imap/setup.php b/modules/imap/setup.php index 09638efd0..72fa05e91 100644 --- a/modules/imap/setup.php +++ b/modules/imap/setup.php @@ -46,6 +46,7 @@ add_handler('settings', 'process_first_time_screen_emails_per_page_setting', true, 'imap', 'date', 'after'); add_handler('settings', 'process_setting_move_messages_in_screen_email', true, 'imap', 'process_first_time_screen_emails_per_page_setting', 'after'); add_handler('settings', 'process_setting_active_preview_message', true, 'imap', 'process_setting_move_messages_in_screen_email', 'after'); +add_handler('settings', 'process_setting_ceo_detection_fraud', true, 'imap', 'process_setting_move_messages_in_screen_email', 'after'); add_output('settings', 'imap_server_ids', true, 'imap', 'page_js', 'before'); add_output('settings', 'start_sent_settings', true, 'imap', 'end_settings_form', 'before'); add_output('settings', 'sent_since_setting', true, 'imap', 'start_sent_settings', 'after'); @@ -64,6 +65,7 @@ add_output('settings', 'first_time_screen_emails_per_page_setting', true, 'imap', 'imap_auto_advance_email', 'after'); add_output('settings', 'setting_move_messages_in_screen_email', true, 'imap', 'first_time_screen_emails_per_page_setting', 'after'); add_output('settings', 'setting_active_preview_message', true, 'imap', 'setting_move_messages_in_screen_email', 'after'); +add_output('settings', 'setting_ceo_detection_fraud', true, 'imap', 'default_sort_order_setting', 'after'); /* compose page data */ add_output('compose', 'imap_server_ids', true, 'imap', 'page_js', 'before'); @@ -450,5 +452,9 @@ 'permissions' => FILTER_DEFAULT, 'action' => FILTER_DEFAULT, 'active_preview_message' => FILTER_VALIDATE_BOOLEAN, + 'ceo_use_detect_ceo_fraud' => FILTER_VALIDATE_BOOLEAN, + 'ceo_use_trusted_contact' => FILTER_VALIDATE_BOOLEAN, + 'ceo_suspicious_terms' => FILTER_DEFAULT, + 'ceo_rate_limit' => FILTER_VALIDATE_INT, ) );