Apache UserDir /home/* can publish private dotfiles

·3 mins

A personal-homepage feature can expose a user’s shell history even when directory listings are disabled. With Apache mod_userdir, a whole-home mapping such as UserDir /home/* maps a request for /~demo/.bash_history directly into that home directory. Restrict publication to a dedicated subtree, and verify that a direct request for a private canary fails while the public page still loads.

This is a filesystem-publication error. It requires neither path traversal nor a dotfile-specific Apache vulnerability: the requested file is already inside the configured web namespace.

How the mapping exposes private files

The Apache UserDir documentation distinguishes relative directory names from absolute-path templates. Compare:

# Unsafe publication boundary if this tree is readable and authorized:
UserDir /home/*

# Publish only the dedicated subtree:
UserDir /home/*/public_html

In the first form, /~demo/.audit-canary maps to /home/demo/.audit-canary. In the second, it maps to /home/demo/public_html/.audit-canary. Filesystem permissions and Apache authorization still determine whether the request succeeds. The mapping alone does not grant read access.

A leading dot is a shell convention, not a general HTTP access-control rule. Likewise, Options -Indexes prevents generated directory listings; it does not prevent fetching a known filename. A deployment may deny some dotfiles explicitly without covering every private file in a home directory.

Exercise the exposure without collecting secrets

In an authorized test, have the administrator create a harmless .audit-canary outside the public subtree and a public index.html inside it. Make the canary readable by the test server so the check tests the mapping, not an accidental permission denial. Do not loosen permissions on real private files.

Against a disposable Apache instance bound to loopback on port 18080, request:

curl --fail --silent --show-error http://127.0.0.1:18080/~demo/.audit-canary
curl --fail --silent --show-error http://127.0.0.1:18080/~demo/public_html/

The vulnerable fixture returned the private marker and the public marker, both with HTTP 200. That establishes unintended file disclosure without downloading anyone’s history or keys.

Real exploitation follows the same direct-file read. Shell history can contain passwords, tokens, or an otpauth:// provisioning URI. That URI contains the shared TOTP seed: anyone who obtains it can generate the same codes as the authenticator. If the same exposure also yields an unencrypted private key accepted by a service requiring key plus TOTP, both factors can be compromised. File disclosure alone does not prove that such credentials exist or that another service accepts them.

Prevent publication at the directory boundary

Use a dedicated public directory with an explicit participant policy:

UserDir disabled
UserDir enabled demo
UserDir /home/*/public_html

<Directory "/home/*/public_html">
    AllowOverride None
    Options -Indexes -FollowSymLinks
    Require all granted
</Directory>

This fragment assumes mod_userdir is loaded. Adapt it to the site’s authorization policy, and check for overlapping directory sections. Remove broad grants on home directories, and keep private files unreadable to the web-server identity. If homepages are unnecessary, turn off the feature entirely. Symlinks and user-controlled overrides deserve separate review because they can expand the effective publication boundary.

If secrets were exposed, closing the route does not revoke copies already obtained. Replace affected keys and tokens, re-enroll affected TOTP credentials, and review access logs. Deleting a provisioning command from history cannot invalidate the seed.

Verify the fix and distrust status-code folklore

I tested the mapping with Debian’s Apache 2.4.69 in a disposable local fixture containing only synthetic files. After adding /public_html to the template, the same private-canary request returned 404, and /~demo/ returned the public marker with 200. No authentication service or real credential was involved.

The fixture’s nonexistent username also returned 404. Do not generalize a particular server’s 403/404 pattern into a reliable account-existence oracle. In Apache 2.4.62’s mapper source , the absolute-template branch substitutes the supplied name without the home-directory lookup used by relative mappings. Later filesystem and authorization processing affects the response.

Before trusting a personal-homepage setup, put a nonsecret canary immediately outside its intended public directory and request it by its exact URL.