Python原型链污染
Python原型链污染
前段时间学了Js的原型链污染,加上国赛和DAS都出了Sanic框架下的Python原型链污染的题,感觉之后还会碰上这样的题目,所以学习一下
一些简介
原型:
每个对象拥有一个原型对象(prototype),对象以该原型为模板,从原型继承方法和属性
原型链:
原型对象可能拥有原型,从中继承方法和属性,一层一层、以此类推,这样的关系结构叫做原型链
prototype和____proto ____
一个类除了自定义的属性外,还有一个prototype属性
实例化一个对象后,对象会存在一个____proto__属性,这个带下划线的proto会指向其继承的父类的prototype
当我们查找a的属性时,假如a中不存在该属性,浏览器就会向上找a的原型,也就是A,如果A也没有,就会一直找到Object(默认所有类都继承自Object),最后指向Null
用一个比较接近的比喻来形容:父亲(类)生了儿子(对象),儿子具备父亲的特征属性,和自己的一些属性,当
我们找这个儿子帮忙时(浏览器查找属性),儿子说办不到(不存在该属性),找他爸爸应该能帮上忙(查找对象的原型,也就是prototype),找到他爸爸,他爸爸说这个事情得他家老爷子来办(对象的原型也没有,再往上一层查找),如果老爷子说这事能办,那就能成(假设爷爷是Object,如果存在该属性,则调用),老爷子说办不了,那就谁也帮不上忙了(NULL)
键值对、字典、元组、模块
键值对:”key”:“value” 类似这个格式的,键后边对应着相应值,这是键值对
字典:d = {key1 : value1, key2 : value2 } d是一个字典
元组:tup1 = () tup1是一个空元组
模块:Python模块是一个Python文件,以.py结尾,包含了Python对象定义和Python语句,模块能定义函数、类、变量,也能包含可执行的代码
Python原型链污染和Nodejs原型链污染根本原理是一样的。虽然python没有原型这一说,但其继承关系却和js类似,这就导致了相关漏洞的诞生
Nodejs是对键值对的控制来污染,而Python则是对类的属性值的污染,并且Python原型链污染只能对类的属性来进行污染,不能污染类的方法
危险代码
和js类似,Python当中也有merge操作,简单说这个merge操作就是:把源参数赋值给目标参数。一般来说原型链污染都发生在类似的地方
1 | def merge(src, dst): |
个例演示
1 | class father: |
获取目标类
在上面的例子中,payload是通过__base__属性来查找到继承的父类的,然后污染父类当中的secret 参数
但,如果目标类和切入点没有父子类的继承关系,我们就无法用base属性来对目标类进行获取和污染
获取全局变量
______init___初始化方法是类的一个内置方法,在没有被重写作为函数的时候,它的数据类型会被当作装饰器,装饰器的特点就是:都具有一个全局属性____globals__属性
gloabls属性是函数对象的一个属性,用于访问该函数所在的 模块的全局命名空间。globals属性会返回一个字典,里面包含了函数定义时所在模块的全局变量
获取其他模块
全局变量前提下,我们都是在入口文件中的类的对象或者属性进行操作的,但是如果操作位置在入口文件中,而目标对象并不在入口文件当中,这个时候就需要通过加载其他模块来获取了
import加载获取
简单关系情况下,我们可以直接通过import来加载其他模块,在payload中只需要对对应的模块进行重新定位就可以了
sys模块加载获取
很多环境当中会引入第三方模块或者是内置模块,不是简单的import当前文件下面的目录,因此,我们要借助sys模块中的module属性。
module属性能够加载出在自运行开始时所有已加载的模块,我们能从属性当中获取到我们想要污染的目标模块
加载器lodaer获取
loader加载器在python中的作用是为实现模块加载而设计的类,在importlib这一内置模块当中有具体体现。
importlib模块下所有的py文件均引入了sys模块,这样我们和上面的sys模块获取已加载模块就能联系起来了,我们的目标就变成了只要获取了loader加载器,就可以通过lodaer.init.globals[‘sys’]来获取到sys模块,然后再获取我们想要的模块
目标变成了获取lodaer
在Python当中,___lodaer__是一个内置的属性,包含了加载模块的loader对象,loader对象负责创建模块对象,通过loader属性,我们可以获取到加载特定模块的loader对象
1 | import math |
math模块的__loader__属性包含了一个loader对象,负责加载math模块
在python当中还存在一个___sepc___,包含了关于类加载时候的信息
它定义在Lib/importlib/_bootstrap.py的类ModuleSpec中,因此可以直接用<模块名>.___sepc___.___init___.___globals___['sys']
来获取到sys模块
替换函数形参默认值
Python当中,__defaults__
是一个元组,用于存储函数或者方法的默认参数值。当我们去定义一个函数时,可以为其中的参数指定默认值。这些默认值会被存储在__defaults__
元组当中
1 | def a(var_1, var_2 =2, var_3 = 3): |
可以通过替换该属性,对函数位置或者是键值默认值进行替换
但前提条件是:我们要替换的值是元组的形式
1 | payload = { |
还有一种是____kwdefaults___,是以字典形式来进行收录的
利用:关键信息替换
flask密钥替换
如果可以对flask密钥进行替换,将其改为我们想要的,我们就可以进行session伪造
题目:
1 | from flask import Flask,request |
因为secret_key是在当前入口文件下的,可以直接通过___init___.___gloabals___
来获取全局变量
然后,我们通过app.config[“SECRET_KEY”]来进行污染
1 | { |
_got_first_request验证
_got_first_request
用于判定请求是否为自Flask启动后的第一次请求,是Flask.got_first_request()函数的返回值
它还会影响装饰器app.before_first_request的调用,当_got_first_request的值为假时才会调用
如果我们想要调用第一次访问前的请求,摈弃给在后续请求中继续进行使用的话,就需要把_got_first_request的值从true改为false(欺骗目标,使其认为我们并非第一次访问的用户),这样就能在后续访问中继续调用app.before_first_request下面的可用信息
1 | 题目: |
1 | payload={ |
_static_url_path污染静态目录
python指定static静态目录之后,再访问static文件夹下对应的文件,就不会存在目录穿梭漏洞
但如果我们想访问其他文件下面的敏感信息,就需要污染这个静态目录来让它自动帮我们实现定向
题目:
1 | #static/index.html |
1 | payload={ |
这里题目给的提示是flag在./flag下,但是我们这里所在的位置是static/index.html
于是我们污染静态目录,使其定位到上一层目录
最后找到flag文件