CWE-548: Exposure of Information Through Directory Listing
Overview
A directory listing is what a server returns when a request for a directory path has no index file to serve and auto-index is enabled: an index of the directory's contents, showing file names and structure. The same exposure comes from a framework's static-file handler with its listing option turned on, or from an application route that browses a directory with no access control. Attackers read listings to find backup files, configuration files, source code and documentation that were not meant to be public.
Relationship to Other CWEs
Report a finding here when the listing itself is the exposure. Where the listing is merely how something else was noticed, the more useful mapping is whatever put that file within reach. CWE-548 sits under CWE-497 (Exposure of Sensitive System Information to an Unauthorized Control Sphere) and has no children, so there is nothing more specific to route to below it.
The pages around it differ by what the client actually got hold of:
- CWE-548 (this page) - the server enumerated a directory's contents, because auto-indexing was on and no index file was there to serve instead
- CWE-538 (Insertion of Sensitive Information into Externally-Accessible File or Directory) - a file that should never have been reachable is sitting in the webroot: a backup, a VCS directory, a config file. Turning off the listing hides it rather than removing it, so a finding here is usually worth checking for one there as well
- CWE-22 (Path Traversal) - the caller supplied the path and escaped the intended directory, rather than being handed the contents of a directory the server was configured to serve
- CWE-200 (Exposure of Sensitive Information to an Unauthorized Actor) - the router page for exposure findings, and where to go when what leaked came back in a response body rather than as the filesystem laid open
OWASP Classification
A01:2025 - Broken Access Control
Risk
Medium: A listing gives away the application's structure and file naming conventions, and points straight at backup and temporary files (.bak, .old, ~), configuration files, source code and documentation. It is not exploitable on its own, but it tells an attacker where to look next.
Remediation Steps
Core Principle: Disable directory browsing on the web server and on every application framework that serves static files. An index file in each public directory is a backstop, not the fix.
Disable Directory Listing in Web Server
Apache:
# In httpd.conf or the site's vhost - target the block that matches your real document root
<Directory /var/www/html>
Options -Indexes
</Directory>
Put the directive in a <Directory> block for the actual docroot rather than in <Directory />. Debian and Ubuntu ship <Directory /var/www/> Options Indexes FollowSymLinks</Directory> in apache2.conf, and that more specific block wins over anything set on /, so a "global disable" at the root is silently overridden.
The .htaccess route works only if the enclosing <Directory> block allows it. Debian and Ubuntu set AllowOverride None on /var/www/ by default, so an .htaccess file is never read and the fix appears to apply while changing nothing; if AllowOverride is set but does not include Options, Apache returns 500 instead. To use .htaccess, set AllowOverride Options (or AllowOverride All) on that directory first - and if you can edit the server config to do that, set Options -Indexes there directly instead.
Nginx:
# In nginx.conf or site config
autoindex off;
# Ensure in all location blocks
location / {
autoindex off;
}
IIS:
<!-- In web.config -->
<configuration>
<system.webServer>
<directoryBrowse enabled="false" />
</system.webServer>
</configuration>
Don't Substitute Placeholder Index Files for the Server Setting
Dropping an empty index.html into every directory gives the server something to serve instead of a listing, but it leaves auto-index enabled. The exposure returns the moment a directory appears without one - a new upload folder, or a path a deploy script forgot to touch. It also does nothing about non-standard access paths such as WebDAV PROPFIND, which some servers use to enumerate a directory without going through normal index resolution. Treat a placeholder index file as a stopgap for a directory you can't immediately reconfigure, not as a replacement for disabling auto-index at the server level above.
Configure Application Frameworks
A framework can reintroduce directory listing even when the web server's own auto-index is off. A static-file middleware with its listing option enabled, or a hand-written route that lists a directory and renders the result, produces the same exposure at the application layer. Serve static files through the framework's plain static-file handler with no listing option enabled, rather than through a dedicated directory-browsing package or middleware.
// VULNERABLE - a route hand-rolls directory listing with no access control
route('/files/', () => {
files = list_directory('/var/www/files')
return render('files.html', files)
})
// SECURE - remove the listing endpoint entirely, or gate it behind authorization
route('/files/', require_authentication, () => {
if not current_user.is_admin:
return 403
files = get_user_files(current_user) // scoped to what this user is allowed to see
return render('files.html', files)
})
Turn listing off on whichever component actually serves the directory. No layer above it can suppress a listing the origin has already generated: a CDN in front of the site has no listing directive of its own, so an origin that returns a listing gets that listing served and cached. Check the setting on the component itself:
- nginx:
autoindex- off by default; confirm nothing has switched it on. - Apache:
Options -Indexeson the<Directory>block for the docroot, as above. - IIS:
<directoryBrowse enabled="false" />- directory browsing is disabled by default. - Tomcat: the
listingsinit-param onDefaultServletinconf/web.xml- defaults tofalse. - Express:
express.staticserves named files only and returns 404 for a directory path - it has no listing option at all. A listing appears only where the separateserve-indexpackage is mounted, so the fix there is to remove that middleware.
Remove Sensitive Files from Webroot
Don't store these in publicly accessible directories:
- Backup files:
*.bak,*.old,*~ - Configuration:
.env,config.php,web.config - Source code:
.git/,.svn/,*.py,*.rb - Documentation:
README.md,INSTALL.txt - Database files:
*.db,*.sqlite
Test for Directory Listing
Assert on the response body, not the status code. There is no single correct status: Apache and nginx return 403 once Options -Indexes/autoindex off is in place, express.static returns 404 for a directory path, a directory that has an index file correctly returns 200, and a redirect to a canonical path is also fine. What all of those have in common is that the body is not a generated listing.
# Every directory path should fail this grep. A hit is a listing.
for path in /uploads/ /images/ /css/ /js/; do
if curl -sL "http://example.com${path}" | grep -qi "<title>Index of\|<h1>Index of"; then
echo "LISTING SERVED: ${path}"
fi
done
Index of in the <title> and <h1> is what both mod_autoindex and nginx emit; a framework-generated listing will use its own markup, so check the specific package's output when the listing came from the application layer rather than the web server.
Monitor and Alert
Set up monitoring for:
- A directory path returning a generated listing body. Alert on the listing markup rather than the status code, for the reasons given under Test for Directory Listing above
- Scanners walking the directory structure, and request patterns that look like enumeration
Additional Resources
- Apache
mod_autoindex- theOptions -Indexessetting that disables listings - CWE-548: Exposure of Information Through Directory Listing
- OWASP Testing Guide - Review Old Backup and Unreferenced Files
- OWASP Top 10 2025 A01: Broken Access Control
- nginx
autoindexdirective - defaults tooff; confirm it has not been enabled