ddl来了

发布于 2026-03-01  273 次阅读


因为某些不可抗力因素被 ban 了,所以图基本上都是官方 wp 偷的(鞠躬)

Ezmd5

if ($user !== $pass && md5($user) === md5($pass)) {
  echo "Congratulations! Here is your flag: <br>";
  echo file_get_contents($flag_path);
} 

从题目能看出,需要传入两个参数$ user 和 $pass

同时满足满足要求 $pass 不全等于 $user 且二者的哈希值全等即可得到 flag

刚开始我是尝试最常见的用 0e 开头的哈希碰撞,失败了

but 因为当时刚好在学 php 板块,所以考虑到了是否能利用数组来进行,理由 is:如果 $user 是一个数组,比如 $user = array(), $pass = array(),那么 md5($user) 会返回 NULL,并产生一个警告,但返回值是 NULL。

这样两个 NULL 用 === 比较结果是 true。

so 我利用 bp 抓包,在请求页构造了语句: uese[]=1&pass[]=1,得到 flag

~admin~

启动环境得到

用题目给的 user/user23 登录得到

并且登陆后 index.html 后存在一个 key=…

搜索得知这是一个 jwt 令牌,解析后知道了 jwt 令牌存在有用户名和有效时间,然后,我分别尝试修改了用户名和有效时间,然后!然后就没有然后了…

看了官方 wp 后知道需要去拿字典爆破得到密钥,使用密钥对伪造的JWT令牌进行签名后替换连接中的 key,登入管理员 admin 的账号得到 flag

ps 赛后去到处找 wp,根本找不到有师傅发这道题的 wp(也可能是我菜)搞不懂还没被 ban 前看到的那大几百个人到底怎么做的…

然后其实就没有然后了,赛中 ezmd5 做出来的时候挺开心的(因为前面经受了 kfc 的摧残)结果第二题就碰上了 admin…然后第三题就是贪吃蛇(什么天才咱也不知道 )

最后靠队里一个 牛牛的ai 小子干掉了一个 pyeditor 和一个 ccpreview,然后其他 web 题就没有机会看了哈哈哈哈

PyEditor&猫猫最后的复仇

Python沙箱进行了AST检查(反正目前为止是没学过的)只能能学一点是一点来了

import ast
import subprocess
import tempfile
import os
import time
import threading
from flask import Flask, render_template, request, jsonify
from flask_socketio import SocketIO, emit
import secrets

app = Flask(__name__)
app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', secrets.token_hex(32))
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024
socketio = SocketIO(app, cors_allowed_origins="*")

active_processes = {}

class PythonRunner:

    def __init__(self, code, args=""):
        self.code = code
        self.args = args
        self.process = None
        self.output = []
        self.running = False
        self.temp_file = None
        self.start_time = None

    def validate_code(self):
        try:
            if len(self.code) > int(os.environ.get('MAX_CODE_SIZE', 1024)):
                return False, "代码过长"

            tree = ast.parse(self.code)

            banned_modules = ['os', 'sys', 'subprocess', 'shlex', 'pty', 'popen', 'shutil', 'platform', 'ctypes', 'cffi', 'io', 'importlib']

            banned_functions = ['eval', 'exec', 'compile', 'input', '__import__', 'open', 'file', 'execfile', 'reload']

            banned_methods = ['system', 'popen', 'spawn', 'execv', 'execl', 'execve', 'execlp', 'execvp', 'chdir', 'kill', 'remove', 'unlink', 'rmdir', 'mkdir', 'makedirs', 'removedirs', 'read', 'write', 'readlines', 'writelines', 'load', 'loads', 'dump', 'dumps', 'get_data', 'get_source', 'get_code', 'load_module', 'exec_module']

            dangerous_attributes = ['__class__', '__base__', '__bases__', '__mro__', '__subclasses__', '__globals__', '__builtins__', '__getattribute__', '__getattr__', '__setattr__', '__delattr__', '__call__']

            for node in ast.walk(tree):
                if isinstance(node, ast.Import):
                    for name in node.names:
                        if name.name in banned_modules:
                            return False, f"禁止导入模块: {name.name}"

                elif isinstance(node, ast.ImportFrom):
                    if node.module in banned_modules:
                        return False, f"禁止从模块导入: {node.module}"

                elif isinstance(node, ast.Call):
                    if isinstance(node.func, ast.Name):
                        if node.func.id in banned_functions:
                            return False, f"禁止调用函数: {node.func.id}"

                    elif isinstance(node.func, ast.Attribute):
                        if node.func.attr in banned_methods:
                            return False, f"禁止调用方法: {node.func.attr}"

                    elif isinstance(node.func, ast.Name):
                        if node.func.id == 'open':
                            return False, "禁止文件操作"

                elif isinstance(node, ast.With):
                    for item in node.items:
                        if isinstance(item.context_expr, ast.Call):
                            if isinstance(item.context_expr.func, ast.Name):
                                if item.context_expr.func.id == 'open':
                                    return False, "禁止文件操作"
                
                elif isinstance(node, ast.Attribute):
                    if node.attr in dangerous_attributes:
                        if isinstance(node.value, ast.Call) or isinstance(node.value, ast.Name):
                            return False, f"禁止访问危险属性: {node.attr}"
                
                elif isinstance(node, ast.Subscript):
                    if isinstance(node.value, ast.Attribute):
                        if node.value.attr == '__subclasses__':
                            return False, "禁止访问__subclasses__"
            
            return True, "代码验证通过"
            
        except SyntaxError as e:
            return False, f"语法错误: {str(e)}"
        except Exception as e:
            return False, f"验证错误: {str(e)}"
    
    def create_script(self):
        try:
            self.temp_file = tempfile.NamedTemporaryFile(
                mode='w', 
                suffix='.py', 
                dir='/tmp',
                delete=False
            )
            
            wrapper = """
import sys

def safe_exec():
    try:
{indented_code}
        return 0
    except SystemExit as e:
        return e.code if isinstance(e.code, int) else 0
    except Exception as e:
        print(f"执行错误: {{e}}", file=sys.stderr)
        return 1

sys.argv = ['sandbox.py'] + {args}

exit_code = safe_exec()

exit()
# Hey bro, don't forget to remove this before release!!!
import os
import sys

flag_content = os.environ.get('GZCTF_FLAG', '')
os.environ['GZCTF_FLAG'] = ''

try:
    with open('/flag.txt', 'w') as f:
        f.write(flag_content)
except:
    pass
"""
            
            indented_code = '\n'.join(['        ' + line for line in self.code.split('\n')])
            
            full_code = wrapper.format(
                indented_code=indented_code,
                args=str(self.args.split() if self.args else [])
            )
            
            self.temp_file.write(full_code)
            self.temp_file.flush()
            os.chmod(self.temp_file.name, 0o755)
            
            return self.temp_file.name
            
        except Exception as e:
            raise Exception(f"创建脚本失败: {str(e)}")
    
    def run(self):
        try:
            is_valid, message = self.validate_code()
            if not is_valid:
                self.output.append(f"验证失败: {message}")
                return False
                
            script_path = self.create_script()
            
            cmd = ['python', script_path]
            if self.args:
                cmd.extend(self.args.split())
            
            self.process = subprocess.Popen(
                cmd,
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE,
                stdin=subprocess.PIPE,
                text=True,
                bufsize=1,
                universal_newlines=True
            )
            
            self.running = True
            self.start_time = time.time()
            
            def read_output():
                while self.process and self.process.poll() is None:
                    try:
                        line = self.process.stdout.readline()
                        if line:
                            self.output.append(line.strip())
                            socketio.emit('output', {'data': line})
                    except:
                        break
                
                stdout, stderr = self.process.communicate()
                if stdout:
                    for line in stdout.split('\n'):
                        if line.strip():
                            self.output.append(line.strip())
                            socketio.emit('output', {'data': line})
                if stderr:
                    for line in stderr.split('\n'):
                        if line.strip():
                            self.output.append(f"错误: {line.strip()}")
                            socketio.emit('output', {'data': f"错误: {line}"})
                
                self.running = False
                socketio.emit('process_end', {'pid': self.process.pid})
            
            thread = threading.Thread(target=read_output)
            thread.daemon = True
            thread.start()
            
            return True
            
        except Exception as e:
            self.output.append(f"运行失败: {str(e)}")
            return False
    
    def send_input(self, data):
        if self.process and self.process.poll() is None:
            try:
                self.process.stdin.write(data + '\n')
                self.process.stdin.flush()
                return True
            except:
                return False
        return False
    
    def terminate(self):
        if self.process and self.process.poll() is None:
            self.process.terminate()
            self.process.wait(timeout=5)
            self.running = False
            
            if self.temp_file:
                try:
                    os.unlink(self.temp_file.name)
                except:
                    pass
            return True
        return False

@app.route('/')
def index():
    return render_template('index.html')

@app.route('/api/run', methods=['POST'])
def run_code():
    data = request.json
    code = data.get('code', '')
    args = data.get('args', '')
    
    runner = PythonRunner(code, args)
    
    pid = secrets.token_hex(8)
    active_processes[pid] = runner
    
    success = runner.run()
    
    if success:
        return jsonify({
            'success': True,
            'pid': pid,
            'message': '进程已启动'
        })
    else:
        return jsonify({
            'success': False,
            'message': '启动失败'
        })

@app.route('/api/terminate', methods=['POST'])
def terminate_process():
    data = request.json
    pid = data.get('pid')
    
    if pid in active_processes:
        active_processes[pid].terminate()
        del active_processes[pid]
        return jsonify({'success': True})
    
    return jsonify({'success': False, 'message': '进程不存在'})

@app.route('/api/send_input', methods=['POST'])
def send_input():
    data = request.json
    pid = data.get('pid')
    input_data = data.get('input', '')
    
    if pid in active_processes:
        success = active_processes[pid].send_input(input_data)
        return jsonify({'success': success})
    
    return jsonify({'success': False})

@socketio.on('connect')
def handle_connect():
    emit('connected', {'data': 'Connected'})

@socketio.on('disconnect')
def handle_disconnect():
    pass

if __name__ == '__main__':
    socketio.run(app, host='0.0.0.0', port=5000, debug=False, allow_unsafe_werkzeug=True)

刚开始学 ctf 的啥子对 flag 这几个字母真是有够敏感的,导致读代码的时候很快就找到了带了 flag 的地方,但技术不到家,找到了也没用

wrapper = """
import sys

def safe_exec():
    try:
{indented_code}
        return 0
    except SystemExit as e:
        return e.code if isinstance(e.code, int) else 0
    except Exception as e:
        print(f"执行错误: {{e}}", file=sys.stderr)
        return 1

sys.argv = ['sandbox.py'] + {args}

exit_code = safe_exec()

exit()
# Hey bro, don't forget to remove this before release!!!
import os
import sys

flag_content = os.environ.get('GZCTF_FLAG', '')
os.environ['GZCTF_FLAG'] = ''

try:
    with open('/flag.txt', 'w') as f:
        f.write(flag_content)
except:
    pass
"""

下面来自官方 wp

这段代码功能就是读取flag内容并写入flag.txt,但是,这段代码前面有 exit()(一种用于终止程序的方式),所以需要跳过exit()使代码被执行

因为存在 os 模块(<font style=“color:rgba(0, 0, 0, 0.8);background-color:rgb(248, 244, 241);”>Python标准库的一部分,提供了丰富的方法来利用操作系统功能,可以用来创建文件夹、删除文件、获取文件列表和属性等)</font>

<font style=“color:rgba(0, 0, 0, 0.8);background-color:rgb(248, 244, 241);”>所以可以利用该模块获取 flag,输入代码打印环境变量,得到 flag</font>

print(sys.modules['os'].environ['GZCTF_FLAG'])

“sys.modules” 是一个字典,存储了所有已导入的模块

“sys.modules[‘os’]” is 获取已经导入的 os 模块对象

“os.environ” 是一个包含当前系统环境变量的字典

没想到吧一句打印就 game over 了红红火火恍恍惚惚我是啥子。。。

总之就是各种绕过方式(因为这道题 ban 掉了很多调用方法)

(勉强看得懂,真的。很!勉!强!)

绝了。看完写完发现能真的整明白的就这两道题。。。闹呢(黑人问号

剩余的不是啥子能干的,老老实实学理论去了。。。


咔咔咔咔咔咔嚓