What is the safest way to check that a file resides within a base directory?

What is the safest and most secure way in Go to validate on any platform that a given file path lies within a base path?

The paths are initially provided as strings and use "/" as separators, but they are user-supplied and I need to assume plenty of malicious inputs. Which kind of path normalization should I perform to ensure that e.g. sequences like ".." are evaluated, so I can safely check against the base path? What exceptions are there to watch out for on various file systems and platforms? Which Go libraries are supposed to be safe in that respect?

The results will be fed to external functions like os.Create and sqlite3.Open and any failure to recognize that the base path is left would be a security violation.

Upvotes: 0

Views: 447

Answers (1)

Eli Bendersky
Eli Bendersky

Reputation: 273686

I believe you could use filepath.Rel for this (and check if it returns a value not starting with ..).

Rel returns a relative path that is lexically equivalent to targpath when joined to basepath with an intervening separator. That is, Join(basepath, Rel(basepath, targpath)) is equivalent to targpath itself. On success, the returned path will always be relative to basepath, even if basepath and targpath share no elements. An error is returned if targpath can't be made relative to basepath or if knowing the current working directory would be necessary to compute it. Rel calls Clean on the result.

filepath.Rel also calls filepath.Clean on its input paths, resolving any .s and ..s.

Clean returns the shortest path name equivalent to path by purely lexical processing. It applies the following rules iteratively until no further processing can be done:

  1. Replace multiple Separator elements with a single one.
  2. Eliminate each . path name element (the current directory).
  3. Eliminate each inner .. path name element (the parent directory) along with the non-.. element that precedes it.
  4. Eliminate .. elements that begin a rooted path: that is, replace "/.." by "/" at the beginning of a path, assuming Separator is '/'.

You could also use filepath.Clean directly and check for prefix when it's done. Here are some sample outputs for filepath.Clean:

ps := []string{
    "/a/../b",
    "/a/b/./c/../../d",
    "/b",
}
for _, p := range ps {
    fmt.Println(p, filepath.Clean(p))
}

Prints:

/a/../b /b
/a/b/./c/../../d /a/d
/b /b

That said, path manipulation shouldn't be the only security mechanism you deploy. If you truly worry about exploits, use defense in depth by sandboxing, creating a virtual file system / containers, etc.

Upvotes: 2

Related Questions