Feb 17, 2026·6 min read·5 visits
The `id()` method in `directorytree/imapengine` (< 1.22.3) concatenates user input directly into IMAP commands without escaping. Attackers can inject double quotes and CRLF sequences to execute unauthorized commands like `FETCH` or `LOGOUT`.
A critical failure in input sanitization within the `directorytree/imapengine` PHP library allows attackers to perform IMAP Command Injection. By manipulating the parameters passed to the `id()` method, malicious actors can break out of the protocol's quoted-string syntax and inject arbitrary IMAP commands. This can lead to unauthorized email exfiltration, data modification, or denial of service against the mail server.
IMAP (Internet Message Access Protocol) is one of those ancient, text-based protocols that holds the internet together with duct tape and hope. Unlike modern binary protocols that clearly delineate data from instructions, IMAP relies heavily on line-based ASCII commands. If you've ever telnetted into port 143, you know the drill: you type a tag, a command, and arguments. The server responds. It's chatty, it's human-readable, and it is absolutely terrifying if you mishandle user input.
Enter directorytree/imapengine, a PHP library designed to abstract away the pain of raw socket handling. It promises a clean, object-oriented interface for developers who don't want to manually parse RFC 3501. Ideally, a wrapper library acts as a bodyguard, sanitizing inputs and ensuring that when a developer asks to "identify the client," the library doesn't accidentally tell the server to "delete all emails."
Unfortunately, in version 1.22.3 and below, imapengine forgot its primary job. It treated the IMAP ID extension (RFC 2971)—used to tell the server things like "I am an iPhone" or "I am Outlook"—as a trusted zone. By failing to sanitize the inputs destined for this command, the library opened a direct pipe for attackers to speak raw IMAP to the backend server, bypassing the application's intended logic entirely.
The vulnerability lies in how imapengine constructs the ID command. The IMAP ID command allows a client to send a list of field-value pairs to the server for identification purposes. The RFC expects these values to be 'quoted-strings'. For example, a legitimate command might look like this:
A001 ID ("name" "MyEmailApp" "version" "1.0")
The flaw is a textbook Injection vulnerability (CWE-74). The library takes an array of parameters provided by the developer (which may originate from user input, like a 'Client Name' setting in a webmail app) and naively concatenates them into the command string. It wraps them in double quotes, assuming that the input itself won't contain double quotes.
This is the coding equivalent of locking your front door but leaving the key under the mat with a neon sign pointing to it. Because the library didn't check for the presence of " characters or CRLF (Carriage Return Line Feed) sequences inside the input, an attacker can simply close the quote, add a space, and start typing their own IMAP commands. The server, being a dutiful line-processing machine, executes them without hesitation.
Let's look at the code. The vulnerability exists in src/Connection/ImapConnection.php within the id() method. Here is the vulnerable logic prior to the patch:
// VULNERABLE CODE
public function id(?array $ids = null): UntaggedResponse
{
// ... setup code ...
$token = '(';
foreach ($ids as $id) {
// CRITICAL FLAW: No escaping of $id
$token .= '"'.$id.'" ';
}
$token = rtrim($token).')';
// Sends: TAG ID ("value1" "value2")
return $this->command('ID', $token);
}See that $token .= '"'.$id.'" '; line? That is where the world ends. If $id contains ", the structure of the IMAP command breaks. If $id contains \r\n, the command terminates, and a new command begins.
Here is how the maintainers fixed it in version 1.22.3. They introduced a sanitization helper (likely Str::escape or similar logic) to handle the dirty work:
// PATCHED CODE
foreach ($ids as $id) {
// FIX: Input is escaped before concatenation
$token .= '"'.Str::escape($id).'" ';
}The fix is boring, which is exactly how security patches should be. It escapes quotes and presumably strips or encodes control characters, ensuring the data remains data and never becomes code.
Exploiting this requires controlling a string passed to the id() method. Let's assume the application allows us to set a custom "Device Name" that gets logged via IMAP.
Step 1: The Breakout
If we send the string MyDevice, the library sends: ... ("name" "MyDevice").
If we send My"Device, the library sends: ... ("name" "My"Device"). This is a syntax error in IMAP, but we want execution, not errors.
Step 2: The Injection
We need to terminate the current command and start a new one. IMAP commands are separated by CRLF (\r\n). Our payload needs to:
"\r\nA666 LOGOUT\r\nTAG_IGN ID ("The Payload:
evil"\r\nA666 LOGOUT\r\nA002 ID ("ign
The Resulting Stream:
A001 ID ("name" "evil"
A666 LOGOUT
A002 ID ("ign")The server sees three distinct lines. It processes the first (likely fails or ignores the partial ID). Then it hits A666 LOGOUT. The server immediately terminates the authenticated session. Boom, Denial of Service.
But we can do better. If we are authenticated (or if this runs post-auth), we can inject:
evil"\r\nA666 FETCH 1:* (BODY[])\r\nA002 ID ("ign
This commands the server to dump the entire inbox content to the socket. The application might crash processing the unexpected response, but an attacker capturing network traffic (or if the app reflects the raw response) just stole the user's mail.
Why should you panic? Because IMAP is the gateway to the kingdom. If an attacker can inject commands here, the impact ranges from annoying to catastrophic depending on the application's implementation.
FETCH commands allows retrieving email headers, bodies, and attachments. In a password reset scenario, this means account takeover.STORE commands to modify flags (e.g., marking unread emails as read to hide activity) or EXPUNGE to permanently delete messages.LOGOUT injection is a trivial way to kill sessions repeatedly, rendering the mail service unusable for the target.This vulnerability is particularly dangerous because it often occurs in background workers or "setup" phases where detailed logging (via the ID command) is common.
The remediation is straightforward, but urgency is required.
1. Patch Immediately
Update directorytree/imapengine to version 1.22.3 or higher. This version includes the necessary escaping logic in the id() method.
2. Defensive Coding Even with the patch, never trust user input destined for protocol wrappers. Validate configuration inputs (like client names or device IDs) against a strict allowlist (e.g., alphanumeric only). There is rarely a valid reason for a device name to contain a carriage return.
3. Network Monitoring Configure your IDS/IPS (Snort/Suricata) to look for anomalies in IMAP traffic, specifically client-to-server packets containing multiple distinct commands in a single TCP PSH frame where the application logic dictates only one should be sent.
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:H| Product | Affected Versions | Fixed Version |
|---|---|---|
directorytree/imapengine DirectoryTree | < 1.22.3 | 1.22.3 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-74 / CWE-93 |
| Attack Vector | Network (IMAP Protocol) |
| CVSS v3.1 | 7.6 (High) |
| CVSS Vector | CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:H |
| Exploit Status | Proof of Concept Available |
| Platform | PHP |
Improper Neutralization of Special Elements in Output Used by a Downstream Component ('Injection')