Check for spoofed sender addresses, only display the address if so.

This adds a check for malware spoofing of RFC 822 mailbox addresses such
as those found in Mailsploit, and if found only displays the email
address part and not the mailbox name part.

Part 1 of Mailsploit mitigation.

* src/engine/rfc822/rfc822-mailbox-address.vala (MailboxAddress): Add new
  is_spoofed method to check if the mailbox address looks like it has
  been spoofed. Add is_distinct method to determine if the name and the
  label is the same. Do whitespace and non-printing character stripping
  when generating display versions of the mailbox address, rename methods
  to make it more obvious what they do and update call sites. Add unit
  tests to cover all this.

* src/client/conversation-viewer/conversation-message.vala
  (ConversationMessage): Check name is distinct and is not valid before
  displaying it. Use new MailboxAddress methods for getting display
  versions of the address, to ensure we get the stripped versions of the
  addresses.

* src/client/conversation-list/formatted-conversation-data.vala
  (ParticipantDisplay): Ensure full addresses are always HTML-markup
  escaped before displaying them as markup, to avoid dropping "<address>"
  values as invalid HTML. Always show the full address if an address is
  invalid.

* src/engine/util/util-string.vala (reduce_whitespace): Strip not only
  whitespace but also non-printing characters. Add unit tests.
This commit is contained in:
Michael James Gratton 2018-01-29 09:57:24 +10:30
parent f6b4b5c9e8
commit 71e0e6835e
14 changed files with 369 additions and 108 deletions

View file

@ -10,6 +10,11 @@ class Geary.RFC822.MailboxAddressTest : Gee.TestCase {
public MailboxAddressTest() {
base("Geary.RFC822.MailboxAddressTest");
add_test("is_valid_address", is_valid_address);
add_test("is_spoofed", is_spoofed);
add_test("has_distinct_name", has_distinct_name);
add_test("to_full_display", to_full_display);
add_test("to_short_display", to_short_display);
add_test("to_rfc822_string", to_rfc822_string);
}
public void is_valid_address() {
@ -30,4 +35,81 @@ class Geary.RFC822.MailboxAddressTest : Gee.TestCase {
assert(Geary.RFC822.MailboxAddress.is_valid_address("") == false);
}
public void is_spoofed() {
assert(new MailboxAddress(null, "example@example.com").is_spoofed() == false);
assert(new MailboxAddress("", "example@example.com").is_spoofed() == false);
assert(new MailboxAddress("", "example@example.com").is_spoofed() == false);
assert(new MailboxAddress("test", "example@example.com").is_spoofed() == false);
assert(new MailboxAddress("test test", "example@example.com").is_spoofed() == false);
assert(new MailboxAddress("test test", "example@example.com").is_spoofed() == false);
assert(new MailboxAddress("test?", "example@example.com").is_spoofed() == false);
assert(new MailboxAddress("test@example.com", "example@example.com").is_spoofed() == true);
assert(new MailboxAddress("test @ example . com", "example@example.com").is_spoofed() == true);
assert(new MailboxAddress("\n", "example@example.com").is_spoofed() == true);
assert(new MailboxAddress("\n", "example@example.com").is_spoofed() == true);
assert(new MailboxAddress("test", "example@\nexample@example.com").is_spoofed() == true);
assert(new MailboxAddress("test", "example@example@example.com").is_spoofed() == true);
try {
assert(new MailboxAddress.from_rfc822_string("\"=?utf-8?b?dGVzdCIgPHBvdHVzQHdoaXRlaG91c2UuZ292Pg==?==?utf-8?Q?=00=0A?=\" <demo@mailsploit.com>")
.is_spoofed() == true);
} catch (Error err) {
assert_no_error(err);
}
}
public void has_distinct_name() {
assert(new MailboxAddress("example", "example@example.com").has_distinct_name() == true);
assert(new MailboxAddress("", "example@example.com").has_distinct_name() == false);
assert(new MailboxAddress(" ", "example@example.com").has_distinct_name() == false);
assert(new MailboxAddress("example@example.com", "example@example.com").has_distinct_name() == false);
assert(new MailboxAddress(" example@example.com ", "example@example.com").has_distinct_name() == false);
assert(new MailboxAddress(" example@example.com ", "example@example.com").has_distinct_name() == false);
}
public void to_full_display() {
assert(new MailboxAddress("", "example@example.com").to_full_display() ==
"example@example.com");
assert(new MailboxAddress("Test", "example@example.com").to_full_display() ==
"Test <example@example.com>");
assert(new MailboxAddress("example@example.com", "example@example.com").to_full_display() ==
"example@example.com");
assert(new MailboxAddress("Test", "example@example@example.com").to_full_display() ==
"example@example@example.com");
}
public void to_short_display() {
assert(new MailboxAddress("", "example@example.com").to_short_display() ==
"example@example.com");
assert(new MailboxAddress("Test", "example@example.com").to_short_display() ==
"Test");
assert(new MailboxAddress("example@example.com", "example@example.com").to_short_display() ==
"example@example.com");
assert(new MailboxAddress("Test", "example@example@example.com").to_short_display() ==
"example@example@example.com");
}
public void to_rfc822_string() {
assert(new MailboxAddress("", "example@example.com").to_rfc822_string() ==
"example@example.com");
assert(new MailboxAddress(" ", "example@example.com").to_rfc822_string() ==
"example@example.com");
assert(new MailboxAddress("test", "example@example.com").to_rfc822_string() ==
"test <example@example.com>");
assert(new MailboxAddress("test test", "example@example.com").to_rfc822_string() ==
"test test <example@example.com>");
assert(new MailboxAddress("example@example.com", "example@example.com").to_rfc822_string() ==
"example@example.com");
// Technically, per
// https://tools.ietf.org/html/rfc5322#appendix-A.1.2 this
// would be fine as just "test? <example@example.com>",
// i.e. without the name being quoted, but I guess GMime is
// just being conservative here?
assert(new MailboxAddress("test?", "example@example.com").to_rfc822_string() ==
"\"test?\" <example@example.com>");
assert(new MailboxAddress(";", "example@example.com").to_rfc822_string() ==
"\";\" <example@example.com>");
}
}

View file

@ -70,7 +70,7 @@ https://app.foobar.com/xxxxxxxxxxxxx">https://app.foobar.com/xxxxxxxxxxx</a=
></p></td></tr>
</table></body></html>""";
public static string HTML_BODY1_EXPECTED = "Hi Kenneth, We xxxxx xxxx xx xxx xxx xx xxxx x xxxxxxxx xxxxxxxx.  Thank you, XXXXXX XXXXXX You can reply directly to this message or click the following link: https://app.foobar.com/xxxxxxxxxxxxxxxx1641966deff6c48623aba You can change your email preferences at: https://app.foobar.com/xxxxxxxxxxx";
public static string HTML_BODY1_EXPECTED = "Hi Kenneth, We xxxxx xxxx xx xxx xxx xx xxxx x xxxxxxxx xxxxxxxx. Thank you, XXXXXX XXXXXX You can reply directly to this message or click the following link: https://app.foobar.com/xxxxxxxxxxxxxxxx1641966deff6c48623aba You can change your email preferences at: https://app.foobar.com/xxxxxxxxxxx";
public static string HTML_BODY2_ENCODED = """<!DOCTYPE html>
<!--2c2a1c66-0638-7c87-5057-bff8be4291eb_v180-->
@ -618,5 +618,5 @@ x 133, 3000 Bern 6, Switzerland
""";
public static string HTML_BODY2_EXPECTED = "Buy It Now from US $1,750.00 to US $5,950.00. eBay Daccordi, Worldwide: 2 new matches today Daccordi 50th anniversary edition with... Buy it now: US $5,950.00 100% positive feedback Daccordi Griffe Campagnolo Croce D'Aune... Buy it now: US $1,750.00 100% positive feedback View all results Refine this search Disable emails for this search   Email reference id: [#d9f42b5e860b4eabb98195c2888cba9e#] We don't check this mailbox, so please don't reply to this message. If you have a question, go to Help & Contact. ©2016 eBay Inc., eBay International AG Helvetiastrasse 15/17 - P.O. Box 133, 3000 Bern 6, Switzerland";
public static string HTML_BODY2_EXPECTED = "Buy It Now from US $1,750.00 to US $5,950.00. eBay Daccordi, Worldwide: 2 new matches today Daccordi 50th anniversary edition with... Buy it now: US $5,950.00 100% positive feedback Daccordi Griffe Campagnolo Croce D'Aune... Buy it now: US $1,750.00 100% positive feedback View all results Refine this search Disable emails for this search Email reference id: [#d9f42b5e860b4eabb98195c2888cba9e#] We don't check this mailbox, so please don't reply to this message. If you have a question, go to Help & Contact. ©2016 eBay Inc., eBay International AG Helvetiastrasse 15/17 - P.O. Box 133, 3000 Bern 6, Switzerland";
}

View file

@ -0,0 +1,47 @@
/*
* Copyright 2018 Michael Gratton <mike@vee.net>
*
* This software is licensed under the GNU Lesser General Public License
* (version 2.1 or later). See the COPYING file in this distribution.
*/
class Geary.String.Test : Gee.TestCase {
public Test() {
base("Geary.String.Test");
add_test("test_whitespace", test_whitespace);
add_test("test_nonprinting", test_nonprinting);
}
public void test_whitespace() {
assert(reduce_whitespace("") == "");
assert(reduce_whitespace(" ") == "");
assert(reduce_whitespace(" ") == "");
assert(reduce_whitespace(" ") == "");
assert(reduce_whitespace("test") == "test");
assert(reduce_whitespace("test ") == "test");
assert(reduce_whitespace("test ") == "test");
assert(reduce_whitespace("test\n") == "test");
assert(reduce_whitespace("test\r") == "test");
assert(reduce_whitespace("test\t") == "test");
assert(reduce_whitespace(" test") == "test");
assert(reduce_whitespace(" test") == "test");
assert(reduce_whitespace("test test") == "test test");
assert(reduce_whitespace("test test") == "test test");
assert(reduce_whitespace("test\ntest") == "test test");
assert(reduce_whitespace("test\n test") == "test test");
assert(reduce_whitespace("test \ntest") == "test test");
assert(reduce_whitespace("test \n test") == "test test");
assert(reduce_whitespace("test\rtest") == "test test");
assert(reduce_whitespace("test\ttest") == "test test");
}
public void test_nonprinting() {
assert(reduce_whitespace("\0") == ""); // NUL
assert(reduce_whitespace("\u00A0") == ""); // ENQUIRY
assert(reduce_whitespace("\u00A0") == ""); // NO-BREAK SPACE
assert(reduce_whitespace("\u2003") == ""); // EM SPACE
assert(reduce_whitespace("test\n") == "test");
assert(reduce_whitespace("test\ntest") == "test test");
}
}