<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Apache on Lessons learned</title><link>https://eghuro.cz/tags/apache/</link><description>Recent content in Apache on Lessons learned</description><generator>Hugo</generator><language>en-us</language><copyright>© Alexandr Mansurov · Licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/)</copyright><lastBuildDate>Wed, 07 Oct 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://eghuro.cz/tags/apache/index.xml" rel="self" type="application/rss+xml"/><item><title>Apache UserDir /home/* can publish private dotfiles</title><link>https://eghuro.cz/posts/mod-userdir-whole-home-template-publishes-dotfiles/</link><pubDate>Wed, 07 Oct 2026 00:00:00 +0000</pubDate><guid>https://eghuro.cz/posts/mod-userdir-whole-home-template-publishes-dotfiles/</guid><description>&lt;p&gt;A personal-homepage feature can expose a user&amp;rsquo;s shell history even when directory listings are disabled. With Apache &lt;code&gt;mod_userdir&lt;/code&gt;, a whole-home mapping such as &lt;code&gt;UserDir /home/*&lt;/code&gt; maps a request for &lt;code&gt;/~demo/.bash_history&lt;/code&gt; 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.&lt;/p&gt;&#10;&lt;p&gt;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.&lt;/p&gt;&#10;&#10;&lt;h2 id="how-the-mapping-exposes-private-files"&gt;How the mapping exposes private files &lt;/h2&gt;&#10;&lt;p&gt;The &lt;a&#10; href="https://httpd.apache.org/docs/2.4/mod/mod_userdir.html"&#10; &#10; target="_blank" rel="noopener"&#10; &#10; &#10;&gt;Apache UserDir documentation&lt;/a&gt;&#10; distinguishes relative directory names from absolute-path templates. Compare:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-apache" data-lang="apache"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# Unsafe publication boundary if this tree is readable and authorized:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;UserDir&lt;/span&gt; &lt;span class="sx"&gt;/home/&lt;/span&gt;*&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# Publish only the dedicated subtree:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;UserDir&lt;/span&gt; &lt;span class="sx"&gt;/home/&lt;/span&gt;*/public_html&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In the first form, &lt;code&gt;/~demo/.audit-canary&lt;/code&gt; maps to &lt;code&gt;/home/demo/.audit-canary&lt;/code&gt;. In the second, it maps to &lt;code&gt;/home/demo/public_html/.audit-canary&lt;/code&gt;. Filesystem permissions and Apache authorization still determine whether the request succeeds. The mapping alone does not grant read access.&lt;/p&gt;&#10;&lt;p&gt;A leading dot is a shell convention, not a general HTTP access-control rule. Likewise, &lt;code&gt;Options -Indexes&lt;/code&gt; 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.&lt;/p&gt;&#10;&#10;&lt;h2 id="exercise-the-exposure-without-collecting-secrets"&gt;Exercise the exposure without collecting secrets &lt;/h2&gt;&#10;&lt;p&gt;In an authorized test, have the administrator create a harmless &lt;code&gt;.audit-canary&lt;/code&gt; outside the public subtree and a public &lt;code&gt;index.html&lt;/code&gt; 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.&lt;/p&gt;&#10;&lt;p&gt;Against a disposable Apache instance bound to loopback on port 18080, request:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;curl --fail --silent --show-error http://127.0.0.1:18080/~demo/.audit-canary&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;curl --fail --silent --show-error http://127.0.0.1:18080/~demo/public_html/&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The vulnerable fixture returned the private marker and the public marker, both with HTTP 200. That establishes unintended file disclosure without downloading anyone&amp;rsquo;s history or keys.&lt;/p&gt;&#10;&lt;p&gt;Real exploitation follows the same direct-file read. Shell history can contain passwords, tokens, or an &lt;code&gt;otpauth://&lt;/code&gt; 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.&lt;/p&gt;&#10;&#10;&lt;h2 id="prevent-publication-at-the-directory-boundary"&gt;Prevent publication at the directory boundary &lt;/h2&gt;&#10;&lt;p&gt;Use a dedicated public directory with an explicit participant policy:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-apache" data-lang="apache"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;UserDir&lt;/span&gt; disabled&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;UserDir&lt;/span&gt; enabled demo&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;UserDir&lt;/span&gt; &lt;span class="sx"&gt;/home/&lt;/span&gt;*/public_html&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;&amp;lt;Directory&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;/home/*/public_html&amp;#34;&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;AllowOverride&lt;/span&gt; &lt;span class="k"&gt;None&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;Options&lt;/span&gt; -Indexes -FollowSymLinks&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;Require&lt;/span&gt; &lt;span class="k"&gt;all&lt;/span&gt; granted&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;&amp;lt;/Directory&amp;gt;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This fragment assumes &lt;code&gt;mod_userdir&lt;/code&gt; is loaded. Adapt it to the site&amp;rsquo;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.&lt;/p&gt;&#10;&lt;p&gt;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.&lt;/p&gt;&#10;&#10;&lt;h2 id="verify-the-fix-and-distrust-status-code-folklore"&gt;Verify the fix and distrust status-code folklore &lt;/h2&gt;&#10;&lt;p&gt;I tested the mapping with Debian&amp;rsquo;s Apache 2.4.69 in a disposable local fixture containing only synthetic files. After adding &lt;code&gt;/public_html&lt;/code&gt; to the template, the same private-canary request returned 404, and &lt;code&gt;/~demo/&lt;/code&gt; returned the public marker with 200. No authentication service or real credential was involved.&lt;/p&gt;&#10;&lt;p&gt;The fixture&amp;rsquo;s nonexistent username also returned 404. Do not generalize a particular server&amp;rsquo;s 403/404 pattern into a reliable account-existence oracle. In &lt;a&#10; href="https://github.com/apache/httpd/blob/2.4.62/modules/mappers/mod_userdir.c"&#10; &#10; target="_blank" rel="noopener"&#10; &#10; &#10;&gt;Apache 2.4.62&amp;rsquo;s mapper source&lt;/a&gt;&#10;, 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.&lt;/p&gt;&#10;&lt;p&gt;Before trusting a personal-homepage setup, put a nonsecret canary immediately outside its intended public directory and request it by its exact URL.&lt;/p&gt;&#10;</description></item></channel></rss>