-
Notifications
You must be signed in to change notification settings - Fork 5.2k
Description
Background and motivation
See golang/go#56219 for a similar feature request for Go.
It is common for programs to accept filenames from untrusted sources. For example, an archive extractor might create files based on names in the archive, or a web server may serve the content of local files identified by a URL path. In both cases, the untrusted path is a relative path that should never leave a known directory specified in the program, e.g. if serving files from D:\webserver
, paths like ../dir
must not allow the user to read D:\dir
.
Currently, user has to do this validation by hand, either by trying to parse the relative path directly (which is non-trivial and ripe with edge cases), or doing something like
var resolvedPath = Path.GetFullPath(Path.Combine(basePath, untrustedPath));
if (resolvedPath.StartsWith(basePath + Path.DirectorySeparatorChar)) { ... }
, of which I've also seen multiple incorrect variants on StackOverflow and similar sites (typically forgetting to add the directory separator to basePath
). For either of these options, I'm not convinced that I can write a correct implementation without a lot of fuzzing.
API Proposal
Add a new method to System.IO.Path
, bool IsPathLocal(string relativePath)
, which returns true if relativePath
does not leave the current directory. The method should not be dependent on any particular directory, operating only on the string path, without referencing the filesystem.
API Usage
var untrustedPath = readClientRequest();
if (!Path.IsPathLocal(untrustedPath)) {
sendError("can't touch that");
return;
}
// ...
sendFileToClient(Path.Join(basePath, untrustedPath));
Alternative Designs
..
handling
I see two possibilities of how paths containing ..
could be handled:
- Allow the path, as long as remains inside the directory (more complicated).
- Reject any path that contains
..
as a segment. This is more restrictive, but I'd assume that in practice, it would work as well as the first variant for common scenarios while being much simpler to implement.
Retrieving the full path
Alternatively, the API could be implemented as a method string? JoinLocal(string basePath, string relativePath)
(not sure about the name), which also receives the base directory and returns a string?
which either contains the resolved absolute path, or is null if relativePath
is not local. This might be more performant, since the API user will typically want to eventually resolve the path, but combines validation and resolution into a single API, which might be less flexible in cases where the API user wants to do the validation upfront, but only resolve the path later in the program.
Risks
No response