views:

257

answers:

3

I have a rootPath that I trust and a relativePath that I don't. I want to combine them in such a way that I can be sure that the result is under rootPath and that the user can't use .. to get back past the starting point. I do want the relative path to allow things like: hello\..\world == world

A: 

One thing you can do is to count the number of backslashes (\) and double-dots (..), and make sure that the number of double-dots is smaller than the number of backslashes. In order to go above the rootPath in your folder structure, you'll need at least as many backslashes as double dots - thus, if you only allow relativePaths with at least one more backslash, you should be safe.

Tomas Lycken
but\..\..\this\goes\who\knowns\where
BCS
I think you end up needing to keep a running count
BCS
+4  A: 

System.IO.Path.GetFullPath?

To expand: use Path.Combine, then call GetFullPath on the result and check that that result starts with rootPath.

It won't protect you against hardlinks, but it should catch simple things like double-dots.

the above as code:

string Resolve(string fileName)
{
    string root = FileRoot();
    string ret = Path.GetFullPath(Path.Combine(root, fileName));
    if (ret.StartsWith(root.TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar)) return ret;
    throw new ArgumentException("path resolved to out of accesable directroy");
}
technophile
I like it :D
BCS
However, if root does not end with \, it's still possible to play a limited trick -- end up in a different directory, a sibling of the root one, which starts with that as a prefix. E.g., root may be \zip\zop, relative path ..\zopper\zup, you end up in \zip\zopper\zup, outside of the tree rooted in \zip\zop but still satisfying the StartsWith test. Small risk, but not zero.
Alex Martelli
Nice corner case ... going to add that to the code.
Daniel Brückner
You can mitigate that attack by using .StartsWith(root.TrimEnd('\\') + '\\'), to ensure the correct path is used.
technophile
+2  A: 

You could just call Path.GetFullPath() and check if it starts with your trusted rootPath. If you tend to paranoia, check also that rootPath is rooted.

public Boolean IsPathSafe(String rootPath, String relativePath)
{
  return rootPath.EndsWith(Path.DirectorySeparatorChar.ToString()) &&
    Path.IsPathRooted(rootPath) &&
    Patch.Combine(rootPath, relativePath).GetFullPath().StartsWith(rootPath);
}

For an explaination of the first test see Alex Martelli's comment on technophile's answer.

Daniel Brückner