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
Python 3's pathlib
module makes this straightforward with its Path.parents attribute. For example:
from pathlib import Path
root = Path('/path/to/root')
child = root / 'some' / 'child' / 'dir'
other = Path('/some/other/path')
Then:
>>> root in child.parents
True
>>> other in child.parents
False