Go Quickly - Cleaning Filepaths

Mar 31 2016

One of the more useful, but underutilized, packages in Go is the path/filepath package. This library contains a dozen or so tools for manipulating paths on the filesystem. Unlike path, filepath functions are aware of the filesystem, and the functions are not merely string manipulators.

I've recently seen several cases where people misunderstood one of the functions in path/filepath. The misconception is that filepath.Clean() is a security function that will sanitize a path. It's not.

filepath.Clean() takes a path and attempts to re-format it to the shortest possible representation of that path. For example, consider the path /foo/./../bar. This is a legal path, but is not the shortest representation of that path.

If we run filepath.Clean("/foo/./../bar"), it will output /bar, which is indeed the shortest equivalent path.

But just because this recalculates paths does not mean that it increases security. If you simply pass an untrusted path through filepath.Clean(), it is not guaranteed that the returned path will somehow be safer for you to access.

For example, consider an old school hack that once plagued a popular web server. By supplying a URL with a path foo/../../../../../etc/password, one could trick the web server into returning /etc/password. Does filepath.Clean() solve this problem for us?

Here's a little program to test:

package main

import (
    "fmt"
    "path/filepath"
)

func main() {
    fmt.Println(filepath.Clean("foo/../../../../../../etc/passwd"))
}

And the output is...

../../../../../etc/passwd

Indeed, that is the shortest path. And if you toss that bad boy into your web server code, your web server is going to happily serve your password file to the world.

So beware: filepath.Clean() is not a security function or a sanitizer.

What filepath.Clean() (and, even better, filepath.Rel()) can get you is an indication of whether a path points to something above the current directory level. Combining functions like this with a quick strings.HasPrefix(foo, "../") is one way of identifying paths that are suspect.

filepath.Rel() is less likely to give false positives. It is designed to answer the question "Given a path, what is its relative path to my base path?" So you can use it to test whether a given path is inside of your approved directory. If the result returned from Rel() is an error or begins with ../, that path might not be trustworthy.

Finally, the filepath.Abs() function provides a nice way to convert any relative path to an absolute path. This will get rid of all relative components, and make it easier for you to compare the resolved path to another path.

Whether you choose Clean(), Rel(), or Abs(), you will need to write code to check the results they return. None of these is designed as a sanitizer. They're just helpers for common path manipulations.



comments powered by Disqus