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")); + } + }