Skip to content

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.

Edited by Zeger-Jan van de Weg