Repository navigation
[Security] SCRAM-SHA-256 accepts dangerously low iteration count from server #3655
Description
Activity
This is somewhat intentional as there is valid use cases for an iteration count of 1. Namely if your password is already long and cryptographically random (as it should be, e.g.,
tr -d -c 'a-zA-Z0-9' </dev/urandom | head -c 64), then there's nothing that's actually possible to brute force even with a single iteration. For applications that create a lot of connections (e.g., talking to an intermediate pooler that holds the "real" connections) the overhead of a high iteration count is a measurable hit.If you're worried about a malicious server, a more meaningful patch may be to limit the auth mechanisms and iteration counts. For pgjdbc (the Java driver) we have connection properties that allow the user to mandate that SCRAM is used. Adding something like that to
pgwould be useful to enforce the auth mechanism. It's orthogonal to things likesslmodeto require TLS or verify the cert.The protocol flow for PG has the server decide the auth mechanism and it asks the client for it. A malicious server can even ask for the plaintext password (see
AuthenticationCleartextPasswordin https://www.postgresql.org/docs/current/protocol-flow.html#PROTOCOL-FLOW-START-UP) and a compliant client will send it back. That exists as an escape hatch to allow for arbitrary authentication schemes as it's passed as-is to the auth handler (e.g., AWS RDS uses this for IAM based to have the client send a signature that they can read on the server side, though they also mandate TLS).We might have a more real problem with a malicious server that sends a huge iteration count, e.g.,
i=9007199254740991. On nodejs that would lock up the single thread CPU effectively forever trying to calculate the PBKDF2.If you're worried about a malicious server, a more meaningful patch may be to limit the auth mechanisms and iteration counts. For pgjdbc (the Java driver) we have connection properties that allow the user to mandate that SCRAM is used. Adding something like that to
pgwould be useful to enforce the auth mechanism.Yes, I think that’s worth adding (support for
require_authlike libpq). Iteration counts are interesting: anyone who knows to configure a lower bound probably also knows to use a secure password that makes iteration moot. Is supporting bad passwords important enough to break backward compatibility? As for an upper bound, pretending to be resistant to DoS from a malicious server might be a false sense of security (I’m sure the rest of pg isn’t), but maybe we should implement one anyway.Thanks @sehrope and @charmander for the thorough analysis — I agree with all of it.
Re-reading with your points, the threat model is narrower than I initially framed:
- Low iteration count (i=1): agreed this isn't a meaningful risk given cryptographically random passwords are the norm, and adding a lower bound is a false sense of security for users with weak passwords. Not worth breaking backward compatibility for.
- Huge iteration count (e.g.
i=9007199254740991): this is the real concern — a malicious server can CPU-lock the Node event loop on PBKDF2. This is a legitimate server-in-the-middle DoS and has nothing to do with password strength. An upper bound (something likei <= 1_000_000, matching libpq) would prevent it. require_authlike libpq: separate-but-related feature. Happy to track this separately.
Would a PR that just caps the server-advertised iteration count (rejecting
i > 1_000_000with a clear error) be the right scope here? Therequire_authmechanism controls are orthogonal and probably deserve their own issue.Would a PR that just caps the server-advertised iteration count (rejecting
i > 1_000_000with a clear error) be the right scope here? Therequire_authmechanism controls are orthogonal and probably deserve their own issue.Yea I think that makes sense. Not a big concern, but might as well not accept wildly inappropriate iteration counts.
Thanks @brianc. I'll send a PR that adds an iteration-count ceiling in
packages/pg/lib/crypto/sasl.js— rejecting anyi > 1_000_000from the server with a clear error message that names SCRAM and the iteration field. Will hold off on therequire_auth/mechanism-allowlist work for a separate issue per @charmander's suggestion above.Saw #3677 landed 2026-05-18 with a 100K cap — that handles the unbounded-iteration angle. The cap value is also more conservative than what I'd have proposed (common production sha-256 sources sit at 4096-15000, so 100K headroom is generous).
Closing on my end. Happy to reopen if anything related surfaces later.
- Thanks yall!!…On Sun, May 24, 2026 at 5:09 AM Ran ***@***.***> wrote: *eddieran* left a comment (brianc/node-postgres#3655) <#3655 (comment)> Saw #3677 <#3677> landed 2026-05-18 with a 100K cap — that handles the unbounded-iteration angle. The cap value is also more conservative than what I'd have proposed (common production sha-256 sources sit at 4096-15000, so 100K headroom is generous). Closing on my end. Happy to reopen if anything related surfaces later. — Reply to this email directly, view it on GitHub <#3655 (comment)>, or unsubscribe <https://github.com/notifications/unsubscribe-auth/AAAMHIKMHLI57N7R7G4FWYD44JRXXAVCNFSM6AAAAACXW5CQP6VHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHM2DKMRXGI2DAMZYGQ> . You are receiving this because you were mentioned.Message ID: ***@***.***>
Split from #3651 as suggested by @charmander.
Summary
The SCRAM-SHA-256 implementation accepts any iteration count >= 1 from the server. A rogue PostgreSQL server can send a SCRAM challenge with
i=1, making the derived key trivially cheap to brute-force offline.Affected code
packages/pg/lib/crypto/sasl.js—parseServerFirstMessage():The regex only validates that the iteration count is a positive integer. There is no minimum bound check.
Threat model
This is a rogue server scenario. If a client connects to a malicious PostgreSQL server, the server controls the SCRAM
ServerFirstMessageincluding the iteration count. By settingi=1, the server receives the client's SCRAM proof computed with only 1 PBKDF2 iteration. An attacker who captures this exchange can brute-force the password offline at effectively plaintext speed.RFC 7677 (SCRAM-SHA-256) Section 4 states:
Steps to reproduce
r=<nonce>,s=<salt>,i=1pgclient with a passwordImpact
Password compromise via offline brute-force after a single observed authentication exchange with a rogue server.
Suggested fix
Enforce a minimum iteration count in
parseServerFirstMessage():