Skip to content

Commit 8591d7d

Browse files
committed
Fix #85
1 parent a8c840a commit 8591d7d

2 files changed

Lines changed: 52 additions & 4 deletions

File tree

src/main/java/eu/righettod/SecurityUtils.java

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1052,6 +1052,10 @@ public static byte[] sanitizeFile(String inputFilePath, InputFileType inputFileT
10521052
* <li>Is not using address literals.</li>
10531053
* <li>Is not using source routes.</li>
10541054
* <li>Is not using the "percent hack".</li>
1055+
* <li>Does not contain newline or carriage-return characters (CRLF injection prevention).</li>
1056+
* <li>The domain part contains at least one dot (reject single-label domains such as localhost or internal hostnames).</li>
1057+
* <li>The local part is not a quoted string (i.e. not wrapped in double quotes).</li>
1058+
* <li>Respect the RFC 5321 length limits: local part ≤ 64 characters, domain ≤ 255 characters, total address ≤ 320 characters.</li>
10551059
* </ul><br>
10561060
* This is based on the research work from <a href="https://portswigger.net/research/gareth-heyes">Gareth Heyes</a> added in references (Portswigger).<br><br>
10571061
*
@@ -1070,7 +1074,7 @@ public static boolean isEmailAddress(String addr) {
10701074
boolean isValid = false;
10711075
String work = addr.toLowerCase(Locale.ROOT);
10721076
Pattern encodedWordRegex = Pattern.compile("[=?]+", Pattern.CASE_INSENSITIVE);
1073-
Pattern forbiddenCharacterRegex = Pattern.compile("[():!%\\[\\],;]+", Pattern.CASE_INSENSITIVE);
1077+
Pattern forbiddenCharacterRegex = Pattern.compile("[():!%\\[\\],;\"\n\r]+", Pattern.CASE_INSENSITIVE);
10741078
try {
10751079
//Start with the use of the dedicated EmailValidator from Apache Commons Validator
10761080
if (EmailValidator.getInstance(true, true).isValid(work)) {
@@ -1085,10 +1089,24 @@ public static boolean isEmailAddress(String addr) {
10851089
// Source routes,
10861090
// The percent hack.
10871091
if (!forbiddenCharacterRegex.matcher(work).find()) {
1088-
isValid = true;
1092+
//If OK ensure that the domain part contains at least one dot
1093+
long arobaseCount = addr.chars().filter(c -> c == '@').count();
1094+
if (arobaseCount == 1) {
1095+
String[] parts = addr.split("@");
1096+
String localPart = parts[0];
1097+
String domainPart = parts[1];
1098+
if (domainPart.contains(".")) {
1099+
//If OK the check the respect to the RFC 5321 length limits:
1100+
// local part ≤ 64 characters, domain ≤ 255 characters, total address ≤ 320 characters.
1101+
if (localPart.length() <= 64 && domainPart.length() <= 255 && addr.length() <= 320) {
1102+
isValid = true;
1103+
}
1104+
}
1105+
}
10891106
}
10901107
}
10911108
}
1109+
10921110
}
10931111
} catch (Exception e) {
10941112
isValid = false;

src/test/java/eu/righettod/TestSecurityUtils.java

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -477,12 +477,42 @@ public void isEmailAddress() {
477477
final String templateMsgFalseNegative = "Email address '%s' must be detected as invalid!";
478478
final String templateMsgFalsePositive = "Email address '%s' must be detected as valid!";
479479
//Test invalid email addresses
480-
List<String> invalidEmailAddressesList = Arrays.asList("=?utf-8?q?=41=42=43?=test@test.com", "=?utf-7?q?=41GYAbwBvAGIAYBy-?=@test@com", "=?utf-8?b?Zm9vYmFy?=@test.com", "@mail.mit.edu:peter@hotmail.com", "peter%hotmail.com@mail.mit.edu", "rusx!umoskva!kgbvax!dimitri@gateway.ru", "test@example.com@evil.com", "(foo)user@(bar)example.com", "postmaster@[123.123.123.123]", "postmaster@[IPv6:2001:0db8:85a3:0000:0000:8a2e:0370:7334]", "foo@xn--mnchen-2ya.com");
480+
List<String> invalidEmailAddressesList = Arrays.asList(
481+
"notanemail",
482+
"@domain.com",
483+
"user@",
484+
"user@@domain.com",
485+
"user @domain.com",
486+
"=?utf-8?B?dXNlcg==?=@domain.com",
487+
"user@=?utf-8?Q?domain?=.com",
488+
"user(comment)@domain.com",
489+
"user@domain(comment).com",
490+
"user@xn--nxasmq6b.com",
491+
"user@xn--p1ai.xn--e1afmapc.com",
492+
"relay!user@domain.com",
493+
"host1!host2!user@domain.com",
494+
"user@[192.168.1.1]",
495+
"user@[IPv6:2001:db8::1]",
496+
"@relay.com:user@domain.com",
497+
"@r1.com,@r2.com:user@domain.com",
498+
"user%admin@domain.com",
499+
"foo%bar%baz@domain.com",
500+
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa@domain.com", "user@aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.com", "user@aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb.com",
501+
"\"user\"@domain.com",
502+
"\"user name\"@domain.com",
503+
"\"user@evil.com\"@domain.com",
504+
"user@localhost",
505+
"user@internalhost",
506+
"user@com",
507+
"user\r\n@domain.com",
508+
"user\n@domain.com",
509+
"user@domain\r.com"
510+
);
481511
invalidEmailAddressesList.forEach(addr -> {
482512
assertFalse(SecurityUtils.isEmailAddress(addr), String.format(templateMsgFalseNegative, addr));
483513
});
484514
//Test valid email addresses
485-
List<String> validEmailAddressesList = Arrays.asList("test@test.com", "test-test@test.com", "test.test@test.com", "test_test@test.com", "test132@test.com", "test+label@test.com", "\"John..Doe\"@example.com", "\"@\"@example.com");
515+
List<String> validEmailAddressesList = Arrays.asList("test@test.com", "test-test@test.com", "test.test@test.com", "test_test@test.com", "test132@test.com", "test+label@test.com");
486516
validEmailAddressesList.forEach(addr -> {
487517
assertTrue(SecurityUtils.isEmailAddress(addr), String.format(templateMsgFalsePositive, addr));
488518
});

0 commit comments

Comments
 (0)