因为某些不可抗力因素被 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 掉了很多调用方法)
(勉强看得懂,真的。很!勉!强!)

绝了。看完写完发现能真的整明白的就这两道题。。。闹呢(黑人问号
剩余的不是啥子能干的,老老实实学理论去了。。。

Comments | 1 条评论
前来学习