Skip to content

Commit

Permalink
Improved detection and protection against business email compromise (…
Browse files Browse the repository at this point in the history
…BEC) such as CEO fraud
  • Loading branch information
christer77 committed Nov 11, 2024
1 parent a4c136d commit 3de013e
Show file tree
Hide file tree
Showing 4 changed files with 170 additions and 5 deletions.
116 changes: 113 additions & 3 deletions modules/imap/handler_modules.php
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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 "<pre>"; 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)));
Expand All @@ -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];
}
}
}

/**
Expand Down Expand Up @@ -2167,11 +2260,28 @@ 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; }
process_site_setting('active_preview_message', $this, 'process_active_preview_message_callback', true, true);
}
}


/**
* 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');
}
}
4 changes: 2 additions & 2 deletions modules/imap/hm-imap.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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";
Expand Down
49 changes: 49 additions & 0 deletions modules/imap/output_modules.php
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand All @@ -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 = '<tr class="general_setting"><td><label for="ceo_use_detect_ceo_fraud">'.
$this->trans('CEO fraud: Use Detect CEO Fraud').
'</label></td><td><input class="form-check-input" type="checkbox" role="switch" id="ceo_use_detect_ceo_fraud" name="ceo_use_detect_ceo_fraud" '. $ceo_use_detect_ceo_fraud .' ></td></tr>';
$res .= '<tr class="general_setting"><td><label for="ceo_use_trusted_contact">'.
$this->trans('CEO fraud: Use Trusted Contacts as Valid emails ').
'</label></td><td><input class="form-check-input" type="checkbox" role="switch" id="ceo_use_trusted_contact" name="ceo_use_trusted_contact" '. $ceo_use_trusted_contact .' ></td></tr>';
$res .= '<tr class="general_setting"><td><label for="ceo_suspicious_terms">'.
$this->trans('CEO fraud: Suspicious Phrases or Requests(separate by ",")').
'</label></td><td><textarea class="form-control form-control-sm w-auto" role="switch" id="ceo_suspicious_terms" name="ceo_suspicious_terms">'. $ceo_suspicious_terms .'</textarea></td></tr>';
$res .= '<tr class="general_setting"><td><label for="ceo_rate_limit">'.
$this->trans('CEO fraud: Rate-Limit or Monitor Unusual Requests').
'</label></td><td><input class="form-control form-control-sm w-auto" type="number" min="0" role="switch" id="ceo_rate_limit" name="ceo_rate_limit" value="'. $ceo_rate_limit .'" ></td></tr>';
return $res;
}
}
6 changes: 6 additions & 0 deletions modules/imap/setup.php
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand All @@ -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');
Expand Down Expand Up @@ -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,
)
);

0 comments on commit 3de013e

Please sign in to comment.