
Hello, Most frameworks provide some sort of mechanism for serving files from a directory. Generally some subset of the url is mapped directly to files on the disk. But, with this comes the risk that users will be able to access files they should not be able to. For example, something like: http://localhost:8000/../../../etc/passwd The tricky question is, how do we determine what paths are safe and what paths are not. I think the first step is to break the url into path segments and url decode each segment. So something a bit like this: pathElements :: String -> [String] pathElements = map urlDecode . splitOnChar '/' note that we want to split on '/' before we decode. That is because the url might contain an embedded %2f, which is the '/' character. It is encoded as %2f precisely because it is *not* supposed to treated as a path separator. Now that we have a list of path segments we want to check that those path segments can actually be mapped to a valid and safe path on the disk. So, we need to filter out any path segments that contain embedded path separators (like / or \ depending on the platform). Those characters can appear in a valid url. But they can never appear in a valid path segment (they can only be used as path separators). We also want to reject any path that contains embedded "..". Or do we? In theory, we could canonicalize path, "foo/../bar" to just "bar"? Certainly *after* canonicalization, we want to reject any path that contains a ".." segment. Under windows we have some extra concerns. For example, there are special names like LPT1, COM1, etc. Also, we do not want to allow someone to specify a drive name like C:\foo. But, how do we know if we have gotten everything right ? An alternative method would be to use System.Directory.canonicalizePath on the root directory we are serving from and the requested file. Then we check that the canonicalized root directory is the prefix of the canonicalized requested path: isChild :: FilePath -> FilePath -> IO Bool isChild root requested = do root' <- canonicalizePath root requested' <- canonicalizePath requested return (root' `isPrefixOf` requested') (Needs to handle exceptions when the root or requested path does not actually exist). One issue with that solution is that it disallows the use of symlinks which point to directories outside of the root. That can be viewed as either a feature or a bug. It is also not clear that it is actually free of any security issues. So, this is what I have so far. First I use a function like the above 'pathElements' to split the url into path segments. Then I use this function to test that it is a safe/valid path: isSafePath :: [FilePath] -> Bool isSafePath [] = True isSafePath (s:ss) = isValid s && (all (not . isPathSeparator) s) && not (hasDrive s) && not (isParent s) && isSafePath ss -- note: could be different on other OSs isParent :: FilePath -> Bool isParent ".." = True isParent _ = False Something like this: let pathEls = pathElements requestPath in if (isSafePath pathEls) then let fp = joinPath (rootPath : pathEls) in .... else fail "unsafe path" Does anyone see any issues with this? Security bugs or otherwise? Thanks! - jeremy