SMTP smuggling in Apache James
Description
Apache James prior to versions 3.8.1 and 3.7.5 is vulnerable to SMTP smuggling.
A lenient behaviour in line delimiter handling might create a difference of interpretation between the sender and the receiver which can be exploited by an attacker to forge an SMTP envelop, allowing for instance to bypass SPF checks.
The patch implies enforcement of CRLF as a line delimiter as part of the DATA transaction.
We recommend James users to upgrade to non vulnerable versions.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Apache James prior to versions 3.8.1 and 3.7.5 is vulnerable to SMTP smuggling due to lenient line delimiter handling, allowing email spoofing and SPF bypass.
Vulnerability
Description
CVE-2023-51747 affects Apache James, a Java SMTP server, prior to versions 3.8.1 and 3.7.5. The vulnerability is a classic SMTP smuggling issue rooted in the server's lenient handling of line delimiters during the DATA transaction. Officially, SMTP uses CRLF (0x0D0A) as the line delimiter, but some implementations, including older versions of Apache James, accept bare LF (0x0A) as a valid line terminator [1][2]. This discrepancy between sender and receiver behavior is the root cause of the vulnerability.
Exploitation
An attacker can exploit this by crafting an email message that contains a non-standard End-of-DATA sequence, such as . or ., instead of the standard . [3]. This tricks the receiving server into interpreting the message as terminated early, allowing the attacker to inject additional SMTP commands (like MAIL FROM, RCPT TO, DATA) within what was supposed to be the email body. The attack does not require authentication against the vulnerable server itself; rather, it exploits the composition of a sending MTA (that allows bare LF) and a receiving MTA (that expects strict CRLF) [3][4].
Impact
Successful exploitation enables an attacker to send emails with a forged SMTP envelope, effectively spoofing the sender address. This bypasses SPF (Sender Policy Framework) checks because the attacking server sends the spoofed message from its own IP, which may be authorized to send mail on behalf of the forged domain [1][2]. The impact is that recipients see the email as coming from a legitimate source, making phishing and social engineering attacks more effective.
Mitigation
The fix for Apache James enforces strict CRLF (0x0D0A) as the only allowed line delimiter during the DATA transaction, as shown in the commit to the James project [4]. Users must upgrade to versions 3.8.1 or 3.7.5 (the two release lines that contain the patch). There is no mention of a workaround if upgrading is not immediately possible, so patching is the recommended action. The vulnerability was disclosed in December 2023 by SEC Consult as part of a broader SMTP smuggling research effort that also affected other MTAs like Postfix, Sendmail, and Exim [1][3].
AI Insight generated on May 20, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
org.apache.james:james-serverMaven | < 3.7.5 | 3.7.5 |
org.apache.james:james-serverMaven | >= 3.8.0, < 3.8.1 | 3.8.1 |
Affected products
2- Apache Software Foundation/Apache James serverv5Range: 0
Patches
2d5cd8bb098aa[FIX] Enforce CRLF as part of SMTP DATA transaction (#1877)
2 files changed · +51 −0
server/apps/memory-app/src/test/java/org/apache/james/LmtpIntegrationTest.java+26 −0 modified@@ -85,6 +85,32 @@ void lmtpShouldBeConfigurableToReport(GuiceJamesServer guiceJamesServer) throws "451 4.0.0 Temporary error deliver message <error@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: <user@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);
server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/DataLineJamesMessageHookHandler.java+25 −0 modified@@ -36,6 +36,7 @@ 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; @@ -61,6 +62,22 @@ */ 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; @@ -70,11 +87,13 @@ public class DataLineJamesMessageHookHandler implements DataLineFilter, Extensib @Override public Response onLine(SMTPSession session, byte[] line, LineHandler<SMTPSession> next) { + ExtendedSMTPSession extendedSMTPSession = (ExtendedSMTPSession) session; MimeMessageInputStreamSource mmiss = extendedSMTPSession.getMimeMessageWriter(); try { OutputStream out = mmiss.getWritableOutputStream(); + detectSMTPSmuggling(line); // 46 is "." // Stream terminated @@ -129,6 +148,12 @@ public Response onLine(SMTPSession session, byte[] line, LineHandler<SMTPSession 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; }
d1ef102540e5[FIX] Enforce CRLF as part of SMTP DATA transaction (#1878)
2 files changed · +50 −0
server/apps/memory-app/src/test/java/org/apache/james/LmtpIntegrationTest.java+26 −0 modified@@ -85,6 +85,32 @@ void lmtpShouldBeConfigurableToReport(GuiceJamesServer guiceJamesServer) throws "451 4.0.0 Temporary error deliver message <error@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: <user@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);
server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/DataLineJamesMessageHookHandler.java+24 −0 modified@@ -37,6 +37,7 @@ 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 @@ */ 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 Response onLine(SMTPSession session, ByteBuffer lineByteBuffer, LineHandl .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 Response onLine(SMTPSession session, ByteBuffer lineByteBuffer, LineHandl 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; }
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
9- github.com/advisories/GHSA-p5q9-86w4-2xr5ghsaADVISORY
- lists.apache.org/thread/rxkwbkh9vgbl9rzx1fkllyk3krhgydkoghsavendor-advisoryWEB
- nvd.nist.gov/vuln/detail/CVE-2023-51747ghsaADVISORY
- www.openwall.com/lists/oss-security/2024/02/27/4ghsaWEB
- github.com/apache/james-project/commit/d1ef102540e504c067b6c1721a6f1e7eee9c6fc6ghsaWEB
- github.com/apache/james-project/commit/d5cd8bb098aa78d8d62c9645f3c532689ef1cb03ghsaWEB
- postfix.org/smtp-smuggling.htmlghsarelatedWEB
- sec-consult.com/blog/detail/smtp-smuggling-spoofing-e-mails-worldwideghsaWEB
- sec-consult.com/blog/detail/smtp-smuggling-spoofing-e-mails-worldwide/mitrerelated
News mentions
0No linked articles in our index yet.