From 01ed9556949e57307c50313bffdf308df0f36dda Mon Sep 17 00:00:00 2001 From: Lyin-OTTF <3037869661@qq.com> Date: Mon, 19 Oct 2026 17:40:02 +0800 Subject: [PATCH] Reject CR or LF in IMAP commands and arguments IMAP.sendCommandWithID() concatenates the command ID, command and arguments into one control line terminated by CRLF, without checking the inputs. FTP.sendCommand(), POP3.sendCommand(), NNTP.sendCommand() and SMTP.sendCommand() all reject CR or LF for exactly this reason, so an IMAP command such as imap.select("INBOX\r\nA001 DELETE INBOX") writes two lines to the control connection and executes an attacker-chosen second command. IMAP.quoteMailboxName() already rejects CR/LF, but that only covers the arguments that go through it - LOGIN credentials and any direct sendCommand() call bypass it. The command layer is the last line of defense and was the only one of the five protocols missing the guard. The check mirrors the wording and placement of the POP3/NNTP/SMTP guards. IMAPTest gains four cases covering CR, LF and CRLF in the argument and CR/CRLF in the command name; they fail with NPE on an unconnected client before this change and throw IllegalArgumentException with it. --- .../org/apache/commons/net/imap/IMAP.java | 7 ++++++ .../org/apache/commons/net/imap/IMAPTest.java | 22 +++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/src/main/java/org/apache/commons/net/imap/IMAP.java b/src/main/java/org/apache/commons/net/imap/IMAP.java index 395be9c9d..987e8e9d8 100644 --- a/src/main/java/org/apache/commons/net/imap/IMAP.java +++ b/src/main/java/org/apache/commons/net/imap/IMAP.java @@ -395,6 +395,13 @@ public int sendCommand(final String command, final String args) throws IOExcepti * @return The server reply code (either {@link IMAPReply#OK}, {@link IMAPReply#NO} or {@link IMAPReply#BAD}). */ private int sendCommandWithID(final String commandID, final String command, final String args) throws IOException { + // Mirrors the guards in FTP.sendCommand, POP3.sendCommand, NNTP.sendCommand and SMTP.sendCommand: + // CR or LF in a command or argument would terminate the control line early and let an attacker + // inject additional IMAP commands, e.g. through a mailbox name taken from user input. + if (command != null && (command.indexOf('\r') >= 0 || command.indexOf('\n') >= 0) + || args != null && (args.indexOf('\r') >= 0 || args.indexOf('\n') >= 0)) { + throw new IllegalArgumentException("Commands and arguments cannot contain CR or LF characters"); + } final StringBuilder builder = new StringBuilder(); if (commandID != null) { builder.append(commandID); diff --git a/src/test/java/org/apache/commons/net/imap/IMAPTest.java b/src/test/java/org/apache/commons/net/imap/IMAPTest.java index 1193ec0a1..79a61bcff 100644 --- a/src/test/java/org/apache/commons/net/imap/IMAPTest.java +++ b/src/test/java/org/apache/commons/net/imap/IMAPTest.java @@ -110,4 +110,26 @@ void testTrueChunkListener() { assertTrue(IMAP.TRUE_CHUNK_LISTENER.chunkReceived(new IMAP())); } + @Test + void testSendCommandRejectsCarriageReturnInArgs() { + // Mirrors the guards in FTP/POP3/NNTP/SMTP: the command layer is the last line of defense, + // even for arguments that do not go through quoteMailboxName (e.g. LOGIN user/password). + assertThrows(IllegalArgumentException.class, () -> new IMAP().sendCommand("SELECT", "INBOX\rA001 DELETE Sent")); + } + + @Test + void testSendCommandRejectsCRLFInArgs() { + assertThrows(IllegalArgumentException.class, () -> new IMAP().sendCommand("SELECT", "INBOX\r\nA001 DELETE Sent")); + } + + @Test + void testSendCommandRejectsLineFeedInArgs() { + assertThrows(IllegalArgumentException.class, () -> new IMAP().sendCommand("SELECT", "INBOX\nA001 DELETE Sent")); + } + + @Test + void testSendCommandRejectsCarriageReturnInCommand() { + assertThrows(IllegalArgumentException.class, () -> new IMAP().sendCommand("SELECT\r\nX")); + } + }