views:

77

answers:

2

Hello,

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 wether 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.

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.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
A: 

This isn't something you need to bother with. That's something the operating system or your application server does for you. You should know what system-user effectively calls your script. If this user is allowed to access all those 'secret' files, you really should set up another system-user with more restricted rights to execute the scripts.

xor_eq
Well, <code>/etc/password</code> was maybe a bad example, because, as you said, that is protected by the file permissions.
Simon
if you already knew this, what security mechanism do you want to implement that won't be covered by file permissions?
xor_eq
Perhaps one wishes to allow custom templates, but only allows access through a web-based interface (no filesystem access) and don't want the users to get hold of the templating system. Besides, it's not a bad idea using multiple levels of security.
snemarch
multiple levels of security are a good thing. But it's not if it builds upon bad security like having an admin user execute the script.
xor_eq
It is more about files, that are readable by the web application, for example the template files of other users or the configuration file for the web application.
Simon
ok, I didn't get that from your question, sorry.
xor_eq
+1  A: 

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.

blaze
Ok, that makes sense, because an intermediate directory could be also a symbolic link.
Simon