How to check whether a directory is a sub directory of another directory
filesystems, python, security, validation
Solution
os.path.realpath(path): Return the canonical path of the specified filename, eliminating any symbolic links encountered in the path (if they are supported by the operating system).
Use it on directory and subdirectory name, then check latter starts with former.
Problem
I like to write a template system in Python, which allows to include files. e.g. ``` This is a template You can safely include files with safe_include`othertemplate.rst` ``` As you know, including files might be dangerous. For example, if I use the template system in a web application which allows users to create their own templates, they might do something like ``` I want your passwords: safe_include`/etc/password` ``` So therefore, I have to restrict the inclusion of files to files which are for example in a certain subdirectory (e.g. `/home/user/templates`) The question is now: How can I check, whether `/home/user/templates/includes/inc1.rst` is in a subdirectory of `/home/user/templates`? Would the following code work and be secure? ``` import os.path def in_directory(file, directory, allow_symlink = False): #make both absolute directory = os.path.abspath(directory) file = os.path.abspath(file) #check whether file is a symbolic link, if yes, return false if they are not allowed if not allow_symlink and os.path.islink(file): return False #return true, if the common prefix of both is equal to directory #e.g. /a/b/c/d.rst and directory is /a/b, the common prefix is /a/b return os.path.commonprefix([file, directory]) == directory ``` As long, as `allow_symlink` is False, it should be secure, I think. Allowing symlinks of course would make it insecure if the user is able to create such links. UPDATE - Solution The code above does not work, if intermediate directories are symbolic links. To prevent this, you have to use `realpath` instead of `abspath`. UPDATE: adding a trailing / to directory to solve the problem with commonprefix() Reorx pointed out. This also makes `allow_symlink` unnecessary as symlinks are expanded to their real destination ``` import os.path def in_directory(file, directory): #make both absolute directory = os.path.join(os.path.realpath(directory), '') file = os.path.realpath(file) #return true, if the common prefix of both is equal to directory #e.g. /a/b/c/d.rst and directory is /a/b, the common prefix is /a/b return os.path.commonprefix([file, directory]) == directory ```