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,
     )
 );