You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@james.apache.org by rc...@apache.org on 2023/12/25 03:14:01 UTC
(james-project) branch 3.7.x updated: [FIX] Enforce CRLF as part of SMTP DATA transaction (#1878)
This is an automated email from the ASF dual-hosted git repository.
rcordier pushed a commit to branch 3.7.x
in repository https://gitbox.apache.org/repos/asf/james-project.git
The following commit(s) were added to refs/heads/3.7.x by this push:
new d1ef102540 [FIX] Enforce CRLF as part of SMTP DATA transaction (#1878)
d1ef102540 is described below
commit d1ef102540e504c067b6c1721a6f1e7eee9c6fc6
Author: Benoit TELLIER <bt...@linagora.com>
AuthorDate: Mon Dec 25 04:13:56 2023 +0100
[FIX] Enforce CRLF as part of SMTP DATA transaction (#1878)
---
.../java/org/apache/james/LmtpIntegrationTest.java | 26 ++++++++++++++++++++++
.../DataLineJamesMessageHookHandler.java | 24 ++++++++++++++++++++
2 files changed, 50 insertions(+)
diff --git a/server/apps/memory-app/src/test/java/org/apache/james/LmtpIntegrationTest.java b/server/apps/memory-app/src/test/java/org/apache/james/LmtpIntegrationTest.java
index 134c7fa186..a11074481a 100644
--- a/server/apps/memory-app/src/test/java/org/apache/james/LmtpIntegrationTest.java
+++ b/server/apps/memory-app/src/test/java/org/apache/james/LmtpIntegrationTest.java
@@ -85,6 +85,32 @@ public class LmtpIntegrationTest {
"451 4.0.0 Temporary error deliver message <er...@domain.tld>");
}
+ @Test
+ void preventSMTPSmugglingAttacksByEnforcingCRLF(GuiceJamesServer guiceJamesServer) throws Exception {
+ SocketChannel server = SocketChannel.open();
+ server.connect(new InetSocketAddress(LOCALHOST_IP, guiceJamesServer.getProbe(LmtpGuiceProbe.class).getLmtpPort()));
+ readBytes(server);
+
+ server.write(ByteBuffer.wrap(("LHLO <" + DOMAIN + ">\r\n").getBytes(StandardCharsets.UTF_8)));
+ readBytes(server);
+ server.write(ByteBuffer.wrap(("MAIL FROM: <user@" + DOMAIN + ">\r\n").getBytes(StandardCharsets.UTF_8)));
+ readBytes(server);
+ server.write(ByteBuffer.wrap(("RCPT TO: <user@" + DOMAIN + ">\r\n").getBytes(StandardCharsets.UTF_8)));
+ readBytes(server);
+ server.write(ByteBuffer.wrap(("DATA\r\n").getBytes(StandardCharsets.UTF_8)));
+ readBytes(server); // needed to synchronize
+ server.write(ByteBuffer.wrap((
+ "header:value\r\n\r\nbody 1\r\n.\nMAIL FROM: <a...@toto.com>\r\n" +
+ "RCPT TO: <us...@domain.tld>\r\n" +
+ "DATA\r\n" +
+ "header: yolo 2\r\n" +
+ "\r\nbody 2\r\n.\r\n").getBytes(StandardCharsets.UTF_8)));
+ byte[] dataResponse = readBytes(server);
+ server.write(ByteBuffer.wrap(("QUIT\r\n").getBytes(StandardCharsets.UTF_8)));
+
+ assertThat(new String(dataResponse, StandardCharsets.UTF_8))
+ .contains("500 5.0.0 line delimiter must be CRLF");
+ }
private byte[] readBytes(SocketChannel channel) throws IOException {
ByteBuffer line = ByteBuffer.allocate(1024);
diff --git a/server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/DataLineJamesMessageHookHandler.java b/server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/DataLineJamesMessageHookHandler.java
index f71f11ece3..44d15b2fbc 100644
--- a/server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/DataLineJamesMessageHookHandler.java
+++ b/server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/DataLineJamesMessageHookHandler.java
@@ -37,6 +37,7 @@ import org.apache.james.protocols.api.Response;
import org.apache.james.protocols.api.handler.ExtensibleHandler;
import org.apache.james.protocols.api.handler.LineHandler;
import org.apache.james.protocols.api.handler.WiringException;
+import org.apache.james.protocols.netty.CommandInjectionDetectedException;
import org.apache.james.protocols.smtp.MailEnvelope;
import org.apache.james.protocols.smtp.SMTPResponse;
import org.apache.james.protocols.smtp.SMTPRetCode;
@@ -62,6 +63,22 @@ import com.google.common.collect.ImmutableList;
*/
public class DataLineJamesMessageHookHandler implements DataLineFilter, ExtensibleHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(DataLineJamesMessageHookHandler.class);
+ public static final boolean DETECT_SMTP_SMUGGLING = System.getProperty("james.prevent.smtp.smuggling", "true").equals("true");
+
+ /*
+ SMTP smuggling: https://sec-consult.com/blog/detail/smtp-smuggling-spoofing-e-mails-worldwide/
+ Strict CRLF enforcement rationals: https://haraka.github.io/barelf
+ */
+ private static void detectSMTPSmuggling(byte[] line) {
+ if (DETECT_SMTP_SMUGGLING) {
+ if (line.length < 2
+ || line[line.length - 2] != '\r'
+ || line[line.length - 1] != '\n') {
+
+ throw new CommandInjectionDetectedException();
+ }
+ }
+ }
private List<JamesMessageHook> messageHandlers;
@@ -79,6 +96,7 @@ public class DataLineJamesMessageHookHandler implements DataLineFilter, Extensib
.orElseThrow(() -> new RuntimeException("'" + SMTPConstants.DATA_MIMEMESSAGE_STREAMSOURCE.asString() + "' has not been filled."));
try {
+ detectSMTPSmuggling(line);
OutputStream out = mmiss.getWritableOutputStream();
// 46 is "."
@@ -134,6 +152,12 @@ public class DataLineJamesMessageHookHandler implements DataLineFilter, Extensib
SMTPResponse response = new SMTPResponse(SMTPRetCode.LOCAL_ERROR, DSNStatus.getStatus(DSNStatus.TRANSIENT, DSNStatus.UNDEFINED_STATUS) + " Error processing message: " + e.getMessage());
LOGGER.error("Unknown error occurred while processing DATA.", e);
return response;
+ } catch (CommandInjectionDetectedException e) {
+ LifecycleUtil.dispose(mmiss);
+ SMTPResponse response = new SMTPResponse(SMTPRetCode.SYNTAX_ERROR_COMMAND_UNRECOGNIZED, DSNStatus.getStatus(DSNStatus.PERMANENT, DSNStatus.UNDEFINED_STATUS) + " line delimiter must be CRLF");
+ LOGGER.info("Use of CRLF, which might indicate SMTP smuggling attempt");
+ return response;
+
}
return null;
}
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org