Description
A directory listing displays the files in a requested folder. Go's http.FileServer may list a directory when it has no index.html. Exposing it without separate access controls can reveal directory structure and filenames. Attackers may discover configuration files, backups, logs or scripts and use them for account compromise, code analysis or further vulnerability discovery.
Potential impact
- Directory structure, filenames and backup or configuration information may become public.
- Hiding links is not access control. If a discovered file is readable, its contents may also be accessible.
- Exposed source code or configuration may help attackers find other weaknesses, such as SQL injection or authentication bypasses.
Remediation
- Do not point
http.FileServerat a project root or an unnecessarily broad directory. - Serve a dedicated directory containing only public files under a limited path. Check for sensitive files and symlinks pointing outside that directory, and prevent untrusted users from creating files or links there.
- Disable unwanted directory listings even when a separate static server or proxy serves the files.
- Check user permissions before serving protected files with
ServeContent,ServeFileor similar functions. Those functions do not provide authorization. - Check static deployment files for configuration,
.env,.git, backups and logs before release.
Examples
The revised example focuses on disabling listings. ./public must contain only public files and no symlinks to outside content. A short list of filenames or extensions cannot identify every sensitive file.
Before
go
package main
import (
"log"
"net/http"
)
// Before: expose the project root directory directly
func main() {
// Expose the current directory (".") through FileServer
fs := http.FileServer(http.Dir("."))
// Accessing http://localhost:8080/ on the server
// May reveal the directory listing and files inside
http.Handle("/", fs)
log.Fatal(http.ListenAndServe(":8080", nil))
}
After
go
package main
import (
"log"
"net/http"
"os"
"path/filepath"
)
// After:
// 1) Serve only a dedicated static directory
// 2) Filter sensitive filenames before serving
// Directory containing only public static files
const staticDir = "./public"
// Illustrative filter for some sensitive names and extensions
func isForbidden(path string) bool {
base := filepath.Base(path)
if base == ".env" || base == "config.yaml" || base == "config.yml" {
return true
}
ext := filepath.Ext(path)
switch ext {
case ".log", ".bak", ".old", ".zip":
return true
}
return false
}
func secureStaticHandler(w http.ResponseWriter, r *http.Request) {
// Calculate the static file path
cleanPath := filepath.Clean(r.URL.Path)
fullPath := filepath.Join(staticDir, cleanPath)
if isForbidden(fullPath) {
http.NotFound(w, r)
return
}
// Check whether the file exists
info, err := os.Stat(fullPath)
if err != nil || info.IsDir() {
// Reject directories instead of listing them
http.NotFound(w, r)
return
}
http.ServeFile(w, r, fullPath)
}
func main() {
// Serve static files only under /static
http.HandleFunc("/static/", func(w http.ResponseWriter, r *http.Request) {
// Remove the /static prefix and call the internal handler
r.URL.Path = r.URL.Path[len("/static"):]
secureStaticHandler(w, r)
})
// Use the regular handler for other paths
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("<h1>Home</h1>"))
})
log.Fatal(http.ListenAndServe(":8080", nil))
}
Explanation:
- Before: The current directory is served without authentication. A listing may appear when there is no index file, and readable configuration, logs or backups may also be exposed.
- After: The dedicated directory's files are served under
/static/, and directory requests return 404. BecauseisForbiddenrejects only some files, deployment contents and write permissions still need control.