Show homograph-warning-icon before email address, unify warning wording
Some checks are pending
E2E / Linux / PHP ${{ matrix.php }} (8.1) (push) Waiting to run
E2E / Linux / PHP ${{ matrix.php }} (8.3) (push) Waiting to run
E2E / Linux / PHP ${{ matrix.php }} (8.5) (push) Waiting to run
CI / Coding Style (push) Waiting to run
CI / Static Analysis (push) Waiting to run
Message Rendering / Linux / PHP ${{ matrix.php }} (8.3) (push) Waiting to run
Message Rendering / Linux / PHP ${{ matrix.php }} (8.4) (push) Waiting to run
Message Rendering / Linux / PHP ${{ matrix.php }} (8.5) (push) Waiting to run
Unit / Linux / PHP ${{ matrix.php }} (8.1) (push) Waiting to run
Unit / Linux / PHP ${{ matrix.php }} (8.2) (push) Waiting to run
Unit / Linux / PHP ${{ matrix.php }} (8.3) (push) Waiting to run
Unit / Linux / PHP ${{ matrix.php }} (8.4) (push) Waiting to run
Unit / Linux / PHP ${{ matrix.php }} (8.5) (push) Waiting to run
Unit / Windows / PHP ${{ matrix.php }} (8.1) (push) Waiting to run
Unit / Windows / PHP ${{ matrix.php }} (8.2) (push) Waiting to run
Unit / Windows / PHP ${{ matrix.php }} (8.3) (push) Waiting to run
Unit / Windows / PHP ${{ matrix.php }} (8.4) (push) Waiting to run
Unit / Windows / PHP ${{ matrix.php }} (8.5) (push) Waiting to run

This moves the warning icon that is triggered by the homograph check from the generic "notification area" (between
headers and body) to the header area, before the address that the warning is referring to.
The previous warning left it unclear which address was found to be problematic, which now is obvious.

Additionally there's now a test to check for these warnings to show up in the DOM.
This commit is contained in:
Pablo Zmdl 2025-08-20 16:18:55 +02:00 committed by Pablo Zmdl
parent f76cace186
commit 8eeedc0c8c
6 changed files with 58 additions and 43 deletions

View File

@ -1380,10 +1380,11 @@ class rcmail_action_mail_index extends rcmail_action
}
$mailto = rcube_utils::idn_to_utf8($mailto);
// Homograph attack detection (#6891)
if ($spoofcheck && !self::$SUSPICIOUS_EMAIL) {
self::$SUSPICIOUS_EMAIL = rcube_spoofchecker::check($mailto);
}
// Homograph attack detection (#6891),
// and phishing email prevention (#1488981) (e.g. "valid@email.addr <phishing@email.addr>")
$show_fraud_warning = $spoofcheck
&& !self::$PRINT_MODE // Don't show the warning in print mode, because there's no danger of interaction.
&& (rcube_spoofchecker::check($mailto) || ($name && $name != $mailto && preg_match('/@||﹫/', $name)));
if (self::$PRINT_MODE) {
$address = '&lt;' . rcube::Q($mailto) . '&gt;';
@ -1398,26 +1399,16 @@ class rcmail_action_mail_index extends rcmail_action
'onclick' => sprintf("return %s.command('compose','%s',this)",
rcmail_output::JS_OBJECT_NAME, rcube::JQ(format_email_recipient($mailto, $name))),
];
$prefix = '';
if ($name && $name != $mailto && preg_match('/@||﹫/', $name)) {
// phishing email prevention (#1488981), e.g. "valid@email.addr <phishing@email.addr>"
$content = rcube::SQ(sprintf('%s <%s>', $name, $mailto));
$msg = $rcmail->gettext('senderphishingwarning');
$prefix = html::span([
'class' => 'sender-phishing-warning',
'title' => $msg,
'role' => 'img',
'aria-label' => $msg,
], '');
} elseif ($show_email && $name && $mailto) {
// In case of a fraud warning always show all details, regardless of the config.
if ($show_fraud_warning || ($show_email && $name && $mailto)) {
$content = rcube::SQ(sprintf('%s <%s>', $name, $mailto));
} else {
$content = rcube::SQ($name ?: $mailto);
$attrs['title'] = $mailto;
}
$address = $prefix . html::a($attrs, $content);
$address = html::a($attrs, $content);
} else {
$address = html::span(['title' => $mailto, 'class' => 'rcmContactAddress'],
rcube::SQ($name ?: $mailto));
@ -1447,6 +1438,18 @@ class rcmail_action_mail_index extends rcmail_action
}
}
if ($show_fraud_warning) {
// $content = rcube::SQ(sprintf('%s <%s>', $name, $mailto));
$msg = $rcmail->gettext('suspiciousemail');
$prefix = html::span([
'class' => 'suspicious-address-warning',
'title' => $msg,
'role' => 'img',
'aria-label' => $msg,
], '');
$address = $prefix . $address;
}
$address = html::span('adr', $address);
$allvalues[] = $address;

View File

@ -283,29 +283,6 @@ class rcmail_action_mail_show extends rcmail_action_mail_index
return html::div($attrib, $msg . '&nbsp;' . html::span('boxbuttons', $buttons));
}
/**
* Display a warning whenever a suspicious email address has been found in the message.
*
* @return string HTML content of the warning element
*/
public static function suspicious_content_warning()
{
if (empty(self::$SUSPICIOUS_EMAIL)) {
return '';
}
$rcmail = rcmail::get_instance();
$attrib = [
'id' => 'suspicious-content-message',
'class' => 'notice',
];
$msg = html::span(null, rcube::Q($rcmail->gettext('suspiciousemail')));
return html::div($attrib, $msg);
}
public static function message_buttons()
{
$rcmail = rcmail::get_instance();
@ -352,7 +329,6 @@ class rcmail_action_mail_show extends rcmail_action_mail_index
$content = [
self::message_buttons(),
self::remote_objects_msg(),
self::suspicious_content_warning(),
];
$plugin = $rcmail->plugins->exec_hook('message_objects',

View File

@ -233,4 +233,3 @@ $messages['emptyattachment'] = 'This attachment appears to be empty.<br>Please,
$messages['oauthloginfailed'] = 'OAuth login failed. Please try again.';
$messages['oauthinvalidrequest'] = 'Authorization request was invalid or incomplete.';
$messages['oauthaccessdenied'] = 'Authorization server or the user denied the request.';
$messages['senderphishingwarning'] = 'This sender name and address look forged, please be careful!';

View File

@ -434,7 +434,7 @@ body.task-error-login #layout {
margin: 1rem 1rem 0 1rem;
}
.sender-phishing-warning:before {
.suspicious-address-warning:before {
.font-icon-class();
float: none;
display: inline-block;

View File

@ -0,0 +1,28 @@
<?php
namespace Tests\MessageRendering;
/**
* Test class to test simple messages.
*/
class EmailAdressSpoofingAttacksTest extends MessageRenderingTestCase
{
/**
* Test that two text mime-parts with disposition "attachment" are shown as
* attachments.
*/
public function testEmailAdressSpoofingAttacks()
{
$domxpath = $this->runAndGetHtmlOutputDomxpath('1176148a1ed73947311a08a3fc11264e64d8f775eae596e5fa660cfd1684a5e6@example.net');
$this->assertSame('Email address spoofing attacks', $this->getScrubbedSubject($domxpath));
$suspiciousAddressWarnings = $domxpath->query('//span[@class="suspicious-address-warning"]');
// It should be present three times: two times for the phishing attack in the From header (once in the header
// summary element, once in the header details element), one time for the homograph attack in the To header.
$this->assertCount(3, $suspiciousAddressWarnings, 'Sender phishing warning');
$expectedMsg = 'This message contains suspicious email addresses that may be fraudulent.';
$this->assertSame($expectedMsg, $suspiciousAddressWarnings[0]->attributes->getNamedItem('title')->textContent);
$this->assertSame($expectedMsg, $suspiciousAddressWarnings[1]->attributes->getNamedItem('title')->textContent);
$this->assertSame($expectedMsg, $suspiciousAddressWarnings[2]->attributes->getNamedItem('title')->textContent);
}
}

View File

@ -0,0 +1,9 @@
From: "Someone" <valid@example.net>:<attacker@example.org>
To: test@Рaypal.com
Subject: Email address spoofing attacks
MIME-Version: 1.0
Date: Fri, 12 June 2025 23:42:00 +0200
Message-ID: <1176148a1ed73947311a08a3fc11264e64d8f775eae596e5fa660cfd1684a5e6@example.net>
Content-Type: text/plain
The content doesn't really matter.