CVE-2026-6478: Timing Channel in PostgreSQL MD5 Passwords
On May 14, 2026, PostgreSQL released security fixes for versions 18.4, 17.10, 16.14, 15.18, and 14.23. Among the vulnerabilities fixed is CVE-2026-6478, described by the project as a timing side channel in MD5 password comparison during authentication.
The core issue is simple: passwords stored in the legacy MD5 format can leak information through small differences in authentication response time. With repeated measurements, an attacker could recover enough credential material to authenticate. Passwords stored in scram-sha-256 format are not affected.
Official sources:
- PostgreSQL Security: CVE-2026-6478
- PostgreSQL Release: 18.4, 17.10, 16.14, 15.18 and 14.23
- Documentation: Password Authentication
- Documentation: pg_authid
The problem in one sentence
Migrating your PostgreSQL configuration to SCRAM doesn’t automatically mean every role has stopped storing MD5 passwords.
This is the most common operational mistake.
An environment can have:
password_encryption = scram-sha-256
and still have legacy users with rolpassword starting with:
md5...
This happens because password_encryption only defines how new passwords will be written. It doesn’t automatically rewrite passwords already stored in the PostgreSQL catalog.
Where the password is stored
PostgreSQL roles live in the pg_authid catalog. The rolpassword column holds the password verifier, not the plaintext password.
For MD5 passwords, the format is:
md5<32 hexadecimal characters>
According to the PostgreSQL documentation, this hash is derived from the password concatenated with the username:
md5(password + username)
Conceptual example:
user: joao
password: exemplo123
base: exemplo123joao
hash: md5(base)
The value stored in pg_authid.rolpassword would look like:
md5...
For SCRAM-SHA-256, the format is different:
SCRAM-SHA-256$<parameters>$<keys>
This format is more modern and is not affected by this CVE.
Why upgraded clusters can remain vulnerable
The typical scenario is:
- The company was running PostgreSQL 13 or earlier.
- Some roles had passwords stored as MD5.
- The cluster was upgraded to PostgreSQL 14, 15, 16, 17, or 18.
- The configuration was changed to use SCRAM.
- The old passwords were never reset.
Result: the current configuration looks correct, but old MD5-format credentials still exist.
This detail matters because CVE-2026-6478 specifically targets authentication involving MD5 passwords.
What a timing channel is
A timing channel is an indirect leak through time.
The system doesn’t explicitly return:
this character is correct
But it may respond a bit faster or a bit slower depending on the internal code path executed.
Picture a naive comparison between two values:
real secret: abcd
attempt: x000
If the first character is already wrong, the program may stop immediately.
Now compare:
real secret: abcd
attempt: a000
Here, the first character matches. The program moves on to the second before failing. This can take a bit longer.
Now:
real secret: abcd
attempt: ab00
The program compares two correct characters before failing. It can take even longer.
Individually, these differences are tiny. On a real network, CPU noise, cache effects, latency, concurrency, and system variance can hide all of it. The risk appears when someone runs many attempts and aggregates the measurements.
How this kind of attack works at a high level
The attacker doesn’t necessarily need to know the original password. They need to turn the server into a timing oracle.
An oracle, in this context, is a system that doesn’t reveal the answer directly but provides observable clues. The clue here is response time.
The conceptual flow would be:
- Choose a target role that can attempt to authenticate.
- Send carefully controlled authentication attempts.
- Measure the response time of each attempt.
- Repeat many times to reduce noise.
- Compare time distributions across candidates.
- Infer which candidate appears to trigger a longer comparison.
- Repeat the process until enough material is recovered to authenticate.
This process is not a direct read of the password. It’s a statistical inference.
It’s also not simply testing words from a wordlist like a traditional brute-force attack. In timing-comparison vulnerabilities, the attacker tries to observe whether a given candidate makes the server progress deeper into the internal comparison.
Since MD5 values are represented in hexadecimal, the visible character space of a hash typically involves:
0 1 2 3 4 5 6 7 8 9 a b c d e f
But the practical detail of turning this into credential recovery depends on the protocol, the implementation, the exact point of the vulnerable comparison, and the ability to measure time precisely. For defensive purposes, the important part is understanding that response time can act as a probabilistic signal.
Why a single attempt proves nothing
A single measurement isn’t reliable.
Example:
candidate A: 10.3 ms
candidate B: 10.7 ms
This difference could be caused by anything: network latency, OS scheduling, cache, server load, or concurrency.
The attack depends on repetition:
candidate A: hundreds or thousands of measurements
candidate B: hundreds or thousands of measurements
candidate C: hundreds or thousands of measurements
After that, the attacker would compare means, medians, spread, and outliers to infer which candidate has consistently different behavior.
So the risk from this CVE isn’t the server “handing over the password” in a single response. The risk is the server allowing a tiny difference to be amplified statistically.
Who is affected
An environment should be considered exposed when these conditions combine:
- The PostgreSQL version predates the fixed release.
- Roles exist with
rolpasswordstarting withmd5. - An attacker can attempt authentication against the server.
The fixed versions are:
PostgreSQL 18.4
PostgreSQL 17.10
PostgreSQL 16.14
PostgreSQL 15.18
PostgreSQL 14.23
Earlier versions within these majors are affected.
How to audit your environment
Connect as a superuser or with a user authorized to query pg_authid.
Check the version:
SELECT version();
Check the current configuration:
SHOW password_encryption;
List the stored password types:
SELECT
rolname,
rolcanlogin,
CASE
WHEN rolpassword IS NULL THEN 'no password'
WHEN rolpassword LIKE 'md5%' THEN 'MD5'
WHEN rolpassword LIKE 'SCRAM-SHA-256$%' THEN 'SCRAM'
ELSE 'other'
END AS password_type
FROM pg_authid
ORDER BY password_type, rolname;
List roles still on MD5:
SELECT rolname, rolcanlogin
FROM pg_authid
WHERE rolpassword LIKE 'md5%'
ORDER BY rolname;
If this query returns rows, MD5 verifiers still exist in the cluster.
How to fix it
The correct order is:
- Update PostgreSQL to the fixed version.
- Audit roles with MD5 passwords.
- Reset those passwords with
password_encryption = 'scram-sha-256'. - Adjust
pg_hba.confto SCRAM where possible. - Validate that no MD5 hashes remain.
1. Apply the fixed minor release
Update to one of these versions or higher within the same major:
14.23
15.18
16.14
17.10
18.4
According to the PostgreSQL announcement, minor updates don’t require dump/restore or pg_upgrade. The procedure usually involves stopping the service, updating the binaries/packages, and starting it back up, following your distribution’s or vendor’s installation method.
2. Make SCRAM the default for new passwords
Configure:
ALTER SYSTEM SET password_encryption = 'scram-sha-256';
SELECT pg_reload_conf();
SHOW password_encryption;
Or adjust it directly in postgresql.conf, following your environment’s operational standard.
3. Reset legacy passwords
For each role that still shows up as MD5:
ALTER ROLE role_name PASSWORD 'strong_new_password';
You can also use psql’s interactive command:
\password role_name
The important part is that the reset must happen while password_encryption is set to scram-sha-256.
4. Validate the migration
After the resets:
SELECT rolname, rolcanlogin
FROM pg_authid
WHERE rolpassword LIKE 'md5%'
ORDER BY rolname;
The ideal result is zero rows.
It’s also worth checking roles without a password:
SELECT rolname, rolcanlogin
FROM pg_authid
WHERE rolpassword IS NULL
ORDER BY rolname;
Passwordless roles aren’t a direct target of this issue, but they should be reviewed to make sure the environment’s authentication model is intentional.
5. Review pg_hba.conf
Once all clients support SCRAM, prefer entries with:
scram-sha-256
Example:
host all all 10.0.0.0/8 scram-sha-256
Avoid keeping md5 as an authentication method for indefinite compatibility. The PostgreSQL documentation itself notes that MD5 password support is deprecated and will be removed in a future release.
Recommended operational procedure
Checklist for production:
- Inventory PostgreSQL 14 through 18 clusters.
- Identify the minor version of each cluster.
- Prioritize upgrading clusters below the fixed versions.
- Audit
pg_authidon each cluster. - Generate a list of roles with
rolpassword LIKE 'md5%'. - Validate impact with applications and client libraries.
- Ensure client-side SCRAM support.
- Define a password rotation window for affected roles.
- Reset passwords under
password_encryption = 'scram-sha-256'. - Change
pg_hba.conftoscram-sha-256where applicable. - Reload the configuration.
- Validate that no MD5 verifiers remain.
- Record evidence of the mitigation.
Minimum evidence for internal documentation
Before:
SELECT version();
SHOW password_encryption;
SELECT rolname, rolcanlogin
FROM pg_authid
WHERE rolpassword LIKE 'md5%'
ORDER BY rolname;
After:
SELECT version();
SHOW password_encryption;
SELECT rolname, rolcanlogin
FROM pg_authid
WHERE rolpassword LIKE 'md5%'
ORDER BY rolname;
Acceptance criteria:
PostgreSQL version is patched.
No role with login privileges keeps an MD5 verifier.
New passwords are written as SCRAM-SHA-256.
pg_hba.conf doesn't force MD5 where SCRAM is supported.
Conclusion
CVE-2026-6478 is a good example of the gap between the configuration you intend and the actual state of the cluster.
It’s not enough to say:
we use SCRAM
You need to confirm:
no role still holds an MD5 verifier in pg_authid
The effective fix has two parts. First, apply the fixed minor release, because that’s what closes the timing side channel. Second, remove the legacy material that was the target of the vulnerability by resetting MD5 passwords under SCRAM.
In practical terms: the patch fixes the bug; password rotation removes the legacy risk.