Reputation:
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
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
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
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
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
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
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