Private certificate disclosure via Symlinks
From external security tests, https://gitlab.com/gitlab-com/infrastructure/issues/2438:
- Effort: Low
- Impact: Medium
- Location: GitLab Pages
Details
Within GitLab Pages, the tryFile method is defined as follows:
func (d *domain) tryFile(w http.ResponseWriter, r *http.Request,
projectName string, subPath ...string) error {
path, err := d.resolvePath(projectName, subPath...)
if err != nil {
return err }
path, err = d.checkPath(w, r, path)
if err != nil {
return err }
return d.serveFile(w, r, path)
}
Within the called methods resolvePath
and checkPath
checks are implemented to ensure that no traversals (especially via symlinks) out of projects public
directory are made. However, within the subsequent call to serveFile
, those checks can be bypassed:
func (d *domain) serveFile(w http.ResponseWriter, r *http.Request,
origPath string) error {
Location
Details
fullPath := handleGZip(w, r, origPath)
file, err := os.Open(fullPath)
if err != nil {
return err }
defer file.Close()
fi, err := file.Stat()
if err != nil {
return err }
// Set caching headers
w.Header().Set("Cache-Control", "max-age=600")
w.Header().Set("Expires",
time.Now().Add(10*time.Minute).Format(time.RFC1123))
fmt.Println("Serving", fullPath, "for", r.URL.Path)
// ServeContent sets Content-Type for us
http.ServeContent(w, r, origPath, fi.ModTime(), file)
return nil
}
The handleGZip
method will concatenate .gz
to the checked filepath
if the HTTP request accepts GZIPed content and such a file exists. However, if the .gz
file is a symlink itself, no checks are performed.
This issue is partially mitigated as the GitLab Pages process is chrooted. Therefore, no system files can be exfiltrated by this issue. However, config.json
files of other projects are affected by this behavior. Those files can contain sensitive information, such as private keys for custom Pages domains.
Reproduction Steps
Create a Pages repository containing a file x.html
and a symbolic link x.html.gz
, the symbolic link should point to e.g. ../config.json
. After deployment of the page the traversal can be observed by requesting the file from its Pages domain with an Accept-Encoding: gzip
Header set.
Recommendation
The handleGZip
method should check if the .gz files are symbolic links in order to mitigate this issue.