【2025】HECTF
第一天打得头疼眼睛疼,我以为是被脑洞给干烂了,结果是tm感冒了,回去吃个药秒好了,但是脑洞太难蚌了,第二天打舞萌去了,没解后面的题了,不过幸好没看,后面有些题还真不大好写。
Web:
老爷爷的金块
现在不知不觉2025年了,曾经在4399里遨游的小孩儿也变成大人了… 重新看了一遍4399的经典游戏,想起了这个努力挖矿的老爷爷。 下载附件,打开exe,重温童年的乐趣!
提示11.请注意获得的flag第六段前面有一个空格哦~
打开是个游戏,打完就有flag,当然分数要过1000
另外一个想法,就是,浏览文件,发现bk_flag.png等多个图片,winhex打开,搜索flag,不知道为啥IDA里没有,找到了这个:
把这个交上去看看,成功。
PHPGift
ctrl+u看到:
1 | <!-- hhhhhh!!!! where is xxx.php --> |
提示是三字文件,flag上面有个ser是唯一三字符的读读看:
1 |
|
pop链。
链子思路大概是Logger中的__invoke,触发之后调用log进行任意文件写,在这里写入木马即可,那么调用__invoke的点可以是User中的call_user_func,__toString可以由FileHandler中的echo $this->fileName; 触发,再向上就是__destruct,其他杂七杂八的类和方法太多了,看上去这个题难,结果一看好像不难,生成payload如下:
1 |
|
之后访问upload.php拿到shell,传参是1。
base64解码得到:HECTF{c0ngr4ts_l1ttl3_h4ck3r_y0u_f0und_my_53cr3t_g1ft}
像素勇者和神秘宝藏
三个门,有用代码如下:
1 | <script> |
第一个门就正常按按鼠标就开了,多点很多次而已,第二个门是只有vip勇者能进,点击获取令牌,然后抓包发现Cookie:
1 | role=user; token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoicGxheWVyIiwiYmxlc3NlZCI6ZmFsc2UsImV4cCI6MTc2NjIyNTY0OH0.H10-DibPmALQ0RyNSp08JlQlL4Rmc7wo8cOd1kau1-U |
看起来是Jwt解密,role也是user,姑且把user换成vip(这里是小写,因为大写绕不过)绕过:
VIP 通道畅通!但宝藏仍被封印……
说明得开第三门,Jwt解密看看:
根据这个猜测把blessed换成true即可,那么就是猜测密钥的问题了,根据上面注释:
1 | <!-- |
先说了 HECTF是大写还是小写,然后后面又说了 这是秘密,草,坑在这里啊,HECTF就是密钥,吗?那么密钥的大写和小写就是问题了, 直接爆破,下面这个代码直接运行就能爆破出flag:
1 | import jwt |
谁家blog里有这功能?
Bob在宿舍里闲的damn疼,不知道干什么,他的朋友Alice最近整了个个人博客主页,最近几天天 天在他面前炫耀,他觉得心理很不爽,有种被朋友内卷自己却在摆烂而感觉到了背叛的感觉,于是 他费尽心力终于捣鼓出来了一个,但是他搓出来的网站有一些其他博客作者不会有的功能……
注意:由于出题人学艺不精的问题,在做题过程中可能会出现网站无法访问的情况,可能是exit把 整个程序停掉了,请等待三十秒,三十秒后应该会重新启动,若三十秒后没有启动的,可以重启环 境。
一般类似的CMS的题直接登后台爆破弱口令碰碰运气,不过这个有可能爆不出来,但存在忘记密码的接口,这个是很多漏洞的可以利用的点,所以这里可以赌一把试试看:
这里看url似乎有可以利用的点,测测看是否存在这种逻辑漏洞(前端对后端响应进行验证,然后跳转到第二个重置密码的页面,以此类推,直到最后的一个页面,当后端认为前两次验证已经完成了,不需要进行后续验证,之类可能会存在任意密码修改的洞,不过实际情况下应该大部分都修了,我这里只是模拟这个洞的点,不会根实际情况一样),这里抓一下响应包看看:
相应包之类可以修改"success":false为"success":true,成功跳转到第二次验证:
之后随便输,抓包看看:
原本的step1变成了step2,可以猜猜看最后的一步可以是step3或者step4,不过这里还是老老实实一步步下去,老样子,抓相应包,改参数:
成功跳转到这一步,那就修改密码为123,之后抓包看看:
之后抓相应包看看:
1117210622168.png&pos_id=img-A0YJbOft-1767957868072)
这里我没改,证明修改密码成功了,之后就可以去登录了,成功进入后台:
往下面翻发现了这里有个超链接,似乎可以点,点击之后发现是下载博客的地方:
抓包之后发现了文件下载接口,似乎可以任意文件读:
试着读取根目录下的flag文件失败了,被forbidden了,不过可以读取源码,直接下载app.py得到源码,源码里有用的地方在这儿:
1 | from flask import Flask, request, jsonify, render_template, session, redirect, url_for, flash,send_file, abort |
这是一个沙箱,可以执行代码,但是,存在三重检验,一个是源码层面的检验source_simple_check,需要代码里不存在这些黑名单关键词,第二层是字节码检验,第三层是运行时检验,也就是常说的addaudithook,主要需要绕过的是第一层和第三层,可以先从沙箱入手,想办法绕过沙箱,不过,沙箱中的"__builtins__": safe_builtins,可知内建函数中没有可以让我们导入的东西,那么可以想办法逃逸出沙箱,到更外层可能会有可以使用的点,所以用栈帧逃逸逃到外界即可。
首先是想办法逃逸到外界,我们可以确定的是,外界的程序里肯定是有"__builtins__"的,那么就可以通过如下方式进行绕过:
1 | def waff(): |
可见,这里存在"__builtins__",可惜是个函数,那么,看看这一层是哪一层:
1 | def waff(): |
先来做个test文件测试下,将这两个沙箱函数稍微改改,本地调试下:
1 | def source_opcode_checker(code_obj): |
可以简单整改下源码任期能够在本地运行,之后就是调试:
1 | def waff(): |
这里的"__builtins__"是模块,但是因为第一层waf过滤太多关键字了,所以根据模块的调用方式这个似乎没法进行绕过,那么再回溯一层再看看:
1 | def waff(): |
这里就成了字典,那么就可以进行绕过了,这里成功获取了"__builtins__"的字典,那么在题目里测试下看看:
1 | def waff(): |
证明是成功了,但是,这里需要注意,如果直接输出 b 会导致程序crash掉,尽可能别输出,这似乎是别的什么问题导致的报错。
既然有了"__builtins__"之后,那么就可以考虑直接把os._exit(0)给杨掉,这样就算执行了system之类的函数也不会报错,直接扔下payload:
1 | def waff(): |
这里通过了__builtins__中的setattr修改了os中的_exit为print,那么就算被钩子检测到了,也不用担心被直接退出了,之后就是直接 RCE 了:
1 | def waff(): |
因为不能使用read()(在关键词上被禁用了),所以这里只有通过这种方式进行绕过了,通过getattr获取popen执行过后的对象中的_proc之后用stdout进行输出
先贴一个payload,最后payload如下:
1 | def waff(): |
这里就对这个payload就行下讲解就行了,就不一步步进行调试了:
首先,三个waf函数里,第二个waf作用不是很大,但是会导致#错误: 执行错误: Disallowed global access: q,不过像之后那样就不会出现这个报错。第一个主要是杨掉了常见的字段,第三个则是沙箱的核心点,虽然看上去第三个waf防的很死,但是用处不大,因为是用的os._exit()直接exit掉了,所以这里直接给os里的这个方法给杨掉就行了,不需要在意,所以这里的payload就是:
1 | def waff(): |
这里贴一下k13in大佬的poc:
1 | def f(): |
ez_include(复现)
不太一样的文件包含
贴个源码:
1 |
|
存在$filterPrefix = 'php://filter/string.strip_tags/resource=';,依稀记得BUUOJ-[NPUCTF2020]ezinclude 1这个题里有一个这个伪协议,有个特性:
在上传文件时,如果出现
Segment Fault,那么上传的临时文件不会被删除。这里的上传文件需要说明一下,一般认为,上传文件需要对应的功能点,但实际上,无论是否有文件上传的功能点,只要 HTTP 请求中存在文件,那么就会被保存为临时文件,当前 HTTP 请求处理完成后,垃圾回收机制会自动删除临时文件。使
php陷入死循环直,产生Segment Fault的方法:(具体原理未找到,如果有大佬清楚,请告知,感谢。)
- 使用
1 >php://filter/string.strip_tags/resource=文件
- 使用
1 >php://filter/convert.quoted-printable-encode/resource=文件
- 函数要求
filefile_get_contentsreadfile
所以直接 PHP LFI 包含临时文件+string.strip_tags过滤器导致出现php segment fault
1 | import requests |
之后用?file=tmp,这里功能是扫描临时文件目录,输出DAyj:
1 | foreach ($phpFiles as $name) { |
根据这里,可以知道,当输出长度高于4的时候输出后四位,那么,这里应该是两位是未知的php??DAyj,那么就得爆破
最后爆破出来:
1 | import requests |
1 | [!] 爆破成功!正确组合: jL |
成功RCE,之后就是RCE读取文件了:
红宝石的恶作剧(复现,先记录下来,细看)
ez_ssti
WP没看懂,先记录下来,之后研究下
输入1+1返回2,但是输入{{1*1}}就报错了,wp里说这个是Ruby环境,存在ERB 模板注入漏洞,所以用File.read('/flag'),返回fakeflag读取flag,但是返回了个假的flag:Hello, HECTF{TH1S_lS_a_FAKE_flag}!,过滤的东西通过动态常量绕过,Object.const_get("File").read("/flag")。
数据管理系统(复现)
开发小哥为了下班粗心buff叠满,够领导拿着板子追着揍半条街。
提示11.file参数存在日志泄露
提示22.图片包含
Hint1里说的是file参数存在日志泄露,结合主页的这里,推测可以通过这个读取到日志文件,可以先去读一下日志文件:
中间件是Nginx,然后随便登录下发现登录方式是GET并且没加密:/?username=admin&password=asd,可以读取下Nginx的日志文件,来获取账号密码,先用GET方式读下:?file=/var/log/nginx/access.log,并搜搜下=admin:
账号密码有了,但是可能有干扰,得一个个试试,最后试出来这个对了admin/bdsfasuaosdah42134223829@#!。
在个人资料那里找到了文件上传的点,做一个图片马,整一个比较小的png图片,然后用winhex修改里面的一些内容的十六进制为木马的文本对应的十六进制,能绕过getimagesize校验:
在解压调试界面,分析前端html,有个这个:
解压调试下面有个地方写了个png文件,那个就是我们传上去的木马文件,不过为啥最后一步的目录是这样的不知道,后面环境关了没来得及写,晚上宿舍断电,复现一半电脑没电关机了,第二天起来环境没了,所以贴一个wp里的截图:
最后尝试包含文件,getshell
Pwn:
nc一下~
小明从系统后台中发现了一段有问题的日志,你能从中找到奇怪点并且消除吗?
nc一下得到日志:
1 | 192.168.220.1 - - [02/Jul/2024:18:53:29 +0800] "GET /01 HTTP/1.1" 301 578 "http://192.168.220.132/"" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:144.0) Gecko/20100101 Firefox/144.0" |
经过分析,发现文件上传漏洞,且相关日志是:
1 | 192.168.220.1 - - [02/Jul/2024:18:53:57 +0800] "GET /01/data/upload/ HTTP/1.1" 200 1747 "http://192.168.220.132/01/index.php"" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:144.0) Gecko/20100101 Firefox/144.0" |
第二条很显然,POST传输文件,第三条直接出现了访问木马的情况,那么第三条就是木马(后面全是logout和login操作,所以估计就是这两条)那么输入02/Jul/2024:18:54:14+upd0te.php通过第一关,第二关是个游戏,没搞懂什么sum逻辑,但还是不小心过了:
1 | 输入正确!恭喜来到病毒的世界,通过数字对战游戏战胜病毒即可消除它... |
shop
1 | __int64 record_purchase() |
这里存在栈溢出漏洞,checksec一下看看没有保护,nx都没开,不需要尝试泄露elf_base,逆向到密码:shopadmin123,这个函数里存在整数溢出漏洞:
1 | int manage_inventory() |
最后exp:
1 | from pwn import * |
Crypto
下个棋吧
先别做题了,flag给你,过来陪我下把棋,对了,别忘了flag要大写,RERBVkFGR0RBWHtWR1ZHWEFYRFZHWEFYRFZWVkZWR1ZYVkdYQX0=
base64解密得到DDAVAFGDAX{VGVGXAXDVGXAXDVVVFVGVXVGXA},根据下棋内容搜索到是棋盘密码,得到flaghectf{1145145201314},棋盘类型是ADFGVX。最后flag为:HECTF{1145145201314}
simple_math
一道普通的数学题,我会做数学题,你会做数学题吗?
1 | from Crypto.Util.number import * |
有n,有c,试着分解n:
1 | from Crypto.Util.number import * |
有p,q,n,c,e,但是e和phi不互素,rabin进行三次二次开方,可以解:
1 | import gmpy2 |
MISC
签到
关注凌武科技微信公众号,关注公众号后发送“2025HECTF,启动!!!”,获得小惊喜!!!
做法就是题目描述字面意思。
Check_In
🎵 🍑🎲⚽🍉 🚃
1 | ctf i love u -> 🎹🏀🌺 🎵 🍑🎲⚽🍉 🚃 |
这几个是一一对应的,hectf就是🌹🍉🎹🏀🌺,可以得到一一对应的关系,之后得到:
1 | hectf{🚇elco⚾e_to_hectf_ho🏉e_💎ou_c🏓🌾_e🌾🍇o💎_it} |
看起来这个flag是可读的,找ai看着来来回回脑洞推测可能是welcome to hectf hope you can enjoy it,最后的flag:
1 | HECTF{welcome_to_hectf_hope_you_can_enjoy_it} |
Reverse
easyree
反编译之后,三个函数,先看第一个:
1 | __int64 __fastcall sub_1389(__int64 a1, __int64 a2, __int64 a3) |
一个异或,提取数据:
1 | unsigned char ida_chars[] = |
第二个函数,也是异或:
1 | __int64 __fastcall sub_143F(__int64 a1) |
提取数据:
1 | unsigned char ida_chars2[] = |
解密脚本:
1 |
|
输出结果:
1 | base64表异或后:ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210+/ |
根据输出结果看出来是base64变表,解码得到flag:
1 | HECTF{welc0m3_t0_rev3r3e_w0r1d_x1x1} |
































