Nir
Nir

Reputation:

Preventing Python code from importing certain modules?

I'm writing an application where users can enter a python script and execute it in a sandbox. I need a way to prevent the exec'ed code from importing certain modules, so malicious code won't be as much of a problem. Is there a way to do this in Python?

Upvotes: 28

Views: 32461

Answers (6)

a_guest
a_guest

Reputation: 36249

You can register a custom MetaPathFinder as the first element of sys.meta_path. This finder can maintain a list of forbidden modules and only return None if the import is acceptable, in order to delegate to other finders; otherwise, it can raise an ImportError to indicate that the import is illegal.

from importlib.abc import MetaPathFinder
import sys


class ForbiddenModules(MetaPathFinder):
    def __init__(self, modules):
        super().__init__()
        self.modules = modules

    def find_spec(self, fullname, path, target=None):
        if fullname in self.modules:
            raise ImportError(fullname)


sys.meta_path.insert(0, ForbiddenModules({'typing'}))

import math  # works
import typing  # raises ImportError

However, at interpreter startup already a bunch of modules are automatically imported. You can check this by using the -v flag, e.g. python -vc "" (it's a long list, so I won't copy it here).

So you also need to remove those modules from sys.modules: sys.modules.clear().

Also note that the list of forbidden modules must include sys itself, because otherwise user code could simply remove the custom finder via sys.meta_path.pop(0) and then continue with importing forbidden modules. This implies that any module which depends on sys (i.e. imports sys) will be forbidden, too.

So this is the full code:

from importlib.abc import MetaPathFinder
import sys


class ForbiddenModules(MetaPathFinder):
    def __init__(self, modules):
        super().__init__()
        self.modules = modules

    def find_spec(self, fullname, path, target=None):
        if fullname in self.modules:
            raise ImportError(fullname)


sys.meta_path.insert(0, ForbiddenModules({'sys', ...}))
sys.modules.clear()
del MetaPathFinder, ForbiddenModules, sys

Upvotes: 0

Tcll
Tcll

Reputation: 7382

8 years, yeesh, and nobody has figured this one out? :/

You can override the import statement or aka the __import__ function.

This is just a tested scribble-code because I couldn't find any legit reference:

import importlib

def secure_importer(name, globals=None, locals=None, fromlist=(), level=0):

    if name != 'C': print(name, fromlist, level)

    # not exactly a good verification layer
    frommodule = globals['__name__'] if globals else None
    if name == 'B' and frommodule != 'C':
        raise ImportError("module '%s' is restricted."%name)

    return importlib.__import__(name, globals, locals, fromlist, level)

__builtins__.__dict__['__import__'] = secure_importer

import C

and here's the tests for that code:

Python 3.4.3 |Anaconda 2.3.0 (32-bit)| (default, Mar  6 2015, 12:08:17) [MSC v.1600 32 bit (Intel)] on win32
Type "copyright", "credits" or "license()" for more information.
>>> ================================ RESTART ================================
>>> 
B ('f',) 0
imported secure module
>>> from B import f
B ('f',) 0
linecache None 0
encodings.utf_8 ['*'] 0
Traceback (most recent call last):
  File "<pyshell#0>", line 1, in <module>
    from B import f
  File "\home\tcll\Projects\python\test\restricted imports\main.py", line 11, in secure_importer
    raise ImportError("module '%s' is restricted."%name)
ImportError: module 'B' is restricted.
>>> import C
>>> 

Please do not comment about me using Python34, I have my reasons, and it's my primary interpreter on Linux specifically for testing things (like the above code) for my primary project.

Upvotes: 14

nilamo
nilamo

Reputation: 1942

Have you checked the python.org article on SandboxedPython, and the linked article?

Both of those pages have links to other resources.

Specifically, PyPi's RestrictedPython lets you define exactly what is available, and has a few 'safe' defaults to choose from.

Upvotes: 19

del-boy
del-boy

Reputation: 3654

If you put None in sys.modules for a module name, in won't be importable...

>>> import sys
>>> import os
>>> del os
>>> sys.modules['os']=None
>>> import os
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ImportError: No module named os
>>>

Upvotes: 36

Alex Martelli
Alex Martelli

Reputation: 881555

Google App Engine's open source SDK has a detailed and solid implementation of mechanics to stop the importing of unwanted modules (to help detect code trying to import modules that aren't made available in the production instances of App Engine), though even that could be subverted if the user code was evil rather than just mistaken (production instances obviously have more layers of defense, such as simply not having those modules around at all;-).

So it all depends on how in-depth your defense needs to be. At one extreme you just stash the builtin __import__ somewhere else and replace it with your function that does all the checks you want before delegating to the __builtin__; that's maybe 20 lines of code, 30 minutes to implement and test thoroughly... but it might not protect you for long if somebody credibly offered me a million bucks to break into your system (and, hypothetically, I wasn't the goody-two-shoes kind of guy I actually AM, of course;-). At the other extreme you deploy an in-depth series of layers of defense that might take thousands of lines and weeks of implementation and testing work -- given that kind of resource budget I could surely implement something I would be unable to penetrate (but there's always the risk that somebody ELSE is smarter and more Python-savvy than I am, of course!).

So, how deep do you want to go, or rather, how deep can you AFFORD to go...?

Upvotes: 8

EMP
EMP

Reputation: 61971

Unfortunately, I think that what you're trying to do is fundamentally impossible. If users can execute arbitrary code in your application then they can do whatever they want. Even if you were able to prevent them from importing certain modules there would be nothing stopping them from writing equivalent functionality themselves (from scratch or using some of the modules that are available).

I don't really know the specifics of implementing a sandbox in Python, but I would imagine it's something that needs to be done at the interpreter level and is far from easy!

Upvotes: 0

Related Questions