Skip to content

mod_authn_socache: bind cache keys to the authentication domain - #930

Open
0xABCD01 wants to merge 3 commits into
apache:trunkfrom
0xABCD01:fix/authn-socache-key-binding
Open

0xABCD01 wants to merge 3 commits into
apache:trunkfrom
0xABCD01:fix/authn-socache-key-binding

Conversation

@0xABCD01

@0xABCD01 0xABCD01 commented Nov 3, 2026

Copy link
Copy Markdown

mod_authn_socache: bind cache keys to the authentication domain

Background

This follows up a report I sent to security@apache.org about
mod_authn_socache. Eric Covener classified it as hardening rather than
a vulnerability and suggested opening a public PR for the docs or
implementation; this is that PR, for the implementation half.

The problem

construct_key() built the cache key from the server hostname, the
request URI truncated at the last slash, and the username. Nothing in
the key identified the authentication domain that produced the entry:
not the provider, not the credential source, not the realm (for Basic
auth), not the port.

With the default AuthnCacheContext directory, two <Files> sections
in one directory that use different AuthUserFiles therefore share
cache entries. Reproduction (two password files in one directory,
alice/lowpass in the first, alice/highpass in the second,
AuthBasicProvider socache file):

GET /auth/low.txt   -u alice:lowpass    -> 200   (populates the cache)
GET /auth/high.txt  -u alice:lowpass    -> 200   (wrong file's password accepted)
GET /auth/high.txt  -u alice:highpass   -> 401   (correct password locked out)
GET /auth/high.txt  -u alice:wrongpass  -> 401

The third line is a consequence of the same key: the cached foreign
hash is authoritative, and every successful login on the first file
re-stores it with a fresh expiry, so the lockout does not end with
AuthnCacheTimeout while the colliding domain stays in use.

The change (mod_authn_socache only, no API changes)

construct_key() now binds an entry to its authentication domain:

  • the directory context identifies an entry by server (hostname and
    port) and the full request URI instead of the URI truncated at
    the last slash, so per-resource credential sources (such as
    <Files> sections) no longer merge;
  • the server context gains the port;
  • the key includes the name of the provider that produced the entry
    (the value providers already pass to ap_authn_cache_store);
    the lookup paths try the key of each provider named in
    AuthnCacheProvideFor;
  • for Basic auth the key includes the protection space (AuthName),
    as it already did the realm for Digest auth.

A custom literal AuthnCacheContext is unchanged: an administrator
who deliberately sets one still chooses how widely entries are shared,
and the documentation now says when that is and is not safe.

Existing caches are not migrated: old entries simply miss under the
new keys and expire within AuthnCacheTimeout, as they would on any
restart. No configuration change is required.

The documentation for AuthnCacheContext is updated to describe what
the default context now binds, and to warn that a shared custom
context is only safe where the whole scope uses a single credential
source.

Testing

Built the patched module against 2.4.69 and ran it in a local lab
(the 2.4.69 mod_authn_socache.c is identical to trunk's):

  • the reproduction above against the patched module returns
    200 / 401 / 200 / 401 with a fresh cache, and stays correct with
    a warm cache;
  • caching still works: within AuthnCacheTimeout, a password rotation
    in the file does not take effect until the entry expires (the
    documented behaviour), for both the previously colliding resources;
  • a second virtual host with a different AuthName authenticates
    independently;
  • Digest auth through AuthDigestProvider socache file works,
    including the socache-served second request, and a wrong password
    still returns 401.

Not in this PR

On a cached-hash mismatch the module still returns AUTH_DENIED
without falling through to the real provider. Within
AuthnCacheTimeout that behaviour is the documented staleness
trade-off, and this PR does not change it; binding the key removes
the cross-domain lockout described above, which was the part not
covered by the documentation.

construct_key() built the key from the server hostname, the URI
truncated at the last slash, and the username, so distinct
credential sources in one directory (for example <Files> sections
with different AuthUserFile settings) silently shared cache
entries under the default AuthnCacheContext directory.

The key now includes the full request URI and port for the
directory and server contexts, the name of the provider that
produced the entry, and the realm (for Basic auth, the AuthName
protection space, as Digest already used the realm).  Lookups try
the key of each provider named in AuthnCacheProvideFor.

Update the AuthnCacheContext documentation accordingly, including
when a shared custom context is and is not safe.
@covener

covener commented Nov 3, 2026

Copy link
Copy Markdown
Member

I wonder if the directory aspect could remain now that the AuthName is mixed in? Along with a caveat in the docs that the default requires unique directory-like URL paths, authnames, or a custom AuthnCacheContext to differentiate between different backend provider results.

@covener

covener commented Nov 3, 2026

Copy link
Copy Markdown
Member

Could really use some coverage in the pyhttpd / modules/ suite for this module, current coding assistants are really good at deriving from what's there.

Per review: with the provider and the realm (AuthName) now mixed
into the key, the directory context no longer needs the full request
URI to separate authentication domains. Restore the directory of the
URI as the context component (the server name keeps the port added
in the previous commit), and state the resulting limit in the
AuthnCacheContext documentation: the default separates backend
provider results only where the directory-like URL path, the
provider, or the AuthName differs, so a configuration that varies
the credential source within one directory and AuthName must use
distinct AuthName values or a custom AuthnCacheContext per source.
test_020_authn_socache covers the cache-key separation of one
directory whose two files are backed by different htpasswd files:
separated by AuthName, separated by a custom AuthnCacheContext, and
shared when neither differentiates the sources, as the manual now
documents. The aaa suite gains the auth_basic, authn_socache and
socache_shmcb modules and two Basic htpasswd files for this.
@0xABCD01

0xABCD01 commented Nov 3, 2026

Copy link
Copy Markdown
Author

Thanks for the review — both points are addressed in two new commits.

Directory aspect. Restored. construct_key() again builds the directory context from the directory of the request URI; what stays from the first commit is the provider name and the realm in the key (for Basic, the AuthName via ap_auth_name()), plus the port in the server name. The full-URI change is gone.

The caveat is now in the AuthnCacheContext documentation, in the terms you put it: the default separates backend provider results only where the directory-like URL path, the provider, or the AuthName differs. A configuration that varies the credential source within one directory and one AuthName — the <Files> sections with different AuthUserFile settings from my original report — must use distinct AuthName values or a custom AuthnCacheContext per source.

I re-ran the lab differential against the revised code, with the report's geometry (one directory, per-file AuthUserFile, AuthnCacheTimeout 600):

  • distinct AuthName per <Files>: 200/401/200/401; the stock module gives 200/200/401 on the same area, so the AuthName in the key is what separates them
  • same AuthName, distinct AuthnCacheContext per <Files>: 200/401/200/401
  • same AuthName, no custom context: 200/200/401/401 — shared, exactly the case the caveat documents
  • rotating a password inside the timeout still serves the cached entry (old password 200, new password 401), so the documented staleness behavior is unchanged

Test coverage. New test/modules/aaa/test_020_authn_socache.py with three areas (byname / byctx / samename) encoding those outcomes, including the shared case as documented behavior. The suite setup gains the auth_basic, authn_socache and socache_shmcb modules plus two Basic htpasswd files. Run locally against a trunk build of this branch: the new file passes 3/3 and the full aaa suite passes 87/87.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants