什么是SSRF漏洞?

定义

SSRF(Server-Side Request Forgery:服务器端请求伪造) 是一种由攻击者构造形成由服务端发起请求的一个安全漏洞。一般情况下,SSRF攻击的目标是从外网无法访问的内部系统。(正是因为它是由服务端发起的,所以它能够请求到与它相连而与外网隔离的内部系统)

SSRF 形成的原因大都是由于服务端提供了从其他服务器应用获取数据的功能且没有对目标地址做过滤与限制。比如从指定URL地址获取网页文本内容,加载指定地址的图片,下载等等。

漏洞成因

Untitled Diagram.drawio (1)

由于业务需要,服务端程序需要从其他服务器应用中获取数据,例如获取图片、数据等,但是由于服务器没有对其请求的目标地址做过滤和限制,导致黑客可以利用此缺陷请求任意服务器资源,其中就包含隐匿在内网的应用。很多企业认为服务器在内网不会受到黑客攻击,便放任漏洞不管,不做补丁修复和版本升级,弱口令遍布内网。但是在SSRF漏洞面前这些漏洞都会成为黑客的“盘中餐”。

SSRF漏洞一般为HTTP/HTTPS方式出现,但类似TCP Connect方式也可以探测内网的IP活跃状态和端口的开放情况,但此类危害较小。

漏洞类型

根据是否显示攻击者的响应,SSRF可分为以下两种类型:

Basic型

回显SSRF。显示对攻击者的响应。在服务器获取攻击者要求的URL后,把响应发送回攻击者。返回结果到客户端,如传送一个网址,会返回这个网址的界面或对应的 html 代码。

Blind型

盲SSRF。不显示响应。在服务器获取攻击者要求的URL后,不会把响应内容发送给攻击者。通常,攻击者将提供url,但是该url中的数据将永远不会返回给攻击者。要在这种情况下确认漏洞,攻击者必须使用Burp,DNSLog等类似工具。这些工具可以通过强制服务器向攻击者控制的服务器发出DNS或HTTP请求来确认服务器是易受攻击的。这种SSRF通常易于验证,但难以利用。

Semi-Blind型

侧信息SSRF。与Blind型相似,这种SSRF不会返回相关结果请求的所有详细信息,但是会暴露一些数据。这可能是部分数据或错误信息,他们为攻击者提供了更多信息。有时,关于请求的元数据(例如响应时间)也可以视为Semi-Blind型,因为它们允许攻击者验证请求是否成功。这种SSRF通常足以验证漏洞,但并不总是能够提取敏感数据。

Bool型

严格意义上讲属于Semi-Blind型的一种。简单来说就是仅返回TrueFalse的SSRF。只有服务器端正确响应HTTP请求并且只有响应码为200的时候,返回Success,其余全部返回Failed。

SSRF漏洞可能出现的位置

能够对外发送网络请求的地方,就可能存在SSRF漏洞。

  • 社交分享功能:获取超链接的标题等内容进行显示

  • 转码服务:通过URL地址把原地址的网页内容调优使其适合手机屏幕浏览

  • 在线翻译:给网址翻译对应网页的内容

  • 图片加载/下载:例如富文本编辑器中的点击下载图片到本地;通过URL地址加载或下载图片

  • 图片/文章收藏功能:主要其会取URL地址中title以及文本的内容作为显示以求一个好的用具体验

  • 云服务厂商:它会远程执行一些命令来判断网站是否存活等,所以如果可以捕获相应的信息,就可以进行ssrf测试

  • 网站采集,网站抓取的地方:一些网站会针对你输入的url进行一些信息采集工作

  • 数据库内置功能:数据库的比如mongodb的copyDatabase函数

  • 邮件系统:比如接收邮件服务器地址

  • 编码处理, 属性信息处理,文件处理:比如ffpmg,ImageMagick,docx,pdf,xml处理器等

  • 未公开的api实现以及其他扩展调用URL的功能:可以利用google语法加上这些关键字去寻找SSRF漏洞

    一些的url中的关键字:sharewap、url、link、src、source、target、u、3g、display、sourceURl、imageURL、domain……

  • 从远程服务器请求资源(upload from url 如discuz!;import & expost rss feed 如web blog;使用了xml引擎对象的地方 如wordpress xmlrpc.php)

SSRF漏洞的危害

  • 对目标服务器所在的内网进行IP存活性扫描和端口扫描
  • 利用扫描的指纹信息判断开放的服务,从而对内网的主机进行攻击
  • 识别内网WEB应用指纹,判断应用类型进行攻击
  • 使用特定协议攻击应用(gopher、dict、file、FTP/SFTP等)
  • 拒绝服务攻击(请求大文件,始终保持连接)

SSRF漏洞的验证

  • 排除法:
    浏览器f12查看源代码看是否是在本地进行了请求
    比如:资源地址类型为 http://www.xxx.com/a.php?image=(地址) 的就可能存在SSRF漏洞

  • DNSLog等工具进行测试,看是否被访问:
    可以在盲打后台用例中将当前准备请求的uri 和参数编码成base64,这样盲打后台解码后就知道是哪台机器哪个cgi触发的请求。

  • 抓包分析发送的请求是不是由服务器的发送的:

    如果不是客户端发出的请求,则有可能是,接着找存在HTTP服务的内网地址。

    ——从漏洞平台中的历史漏洞寻找泄漏的存在web应用内网地址。

    ——通过二级域名暴力猜解工具模糊猜测内网地址。

  • 直接返回的Banner、title、content等信息

  • 留意bool型SSRF:

    一般的SSRF在应用识别阶段返回的信息相对较多,比如Banner信息,HTTP Title信息,更有甚的会将整个HTTP的Reponse完全返回。而Bool型SSRF的却永远只有True或者False。

    因为没有任何Response信息,所以对于攻击Payload的选择也是有很多限制的, 不能选择需要和Response信息交互的Payload。

产生SSRF漏洞的函数(PHP)

在PHP中如fsockopen()pfsockopen()file_get_contents()show_source()highlight_file()curl_exec()curl_multi_exec()fopen()readfile()mysqli_connect()include()require()file()copy()等函数使用过程中没有很好的对参数进行限制就可能导致SSRF漏洞。可以在php.net中搜索网络请求、套接字建立、数据库链接、文件操作相关的函数,部分函数使用的示例代码如下,代码审计时可根据关键字搜索函数进行分析。

file_get_contents函数

file_get_contents()函数把整个文件读入一个字符串中。

漏洞代码如下:

1
2
3
4
5
6
7
8
9
10
11
<?php
if(isset($_POST['url']))
{
$content=file_get_contents($_POST['url']);
$filename='./images/'.rand().'.txt';
file_put_contents($filename,$content);
echo $_POST['url'];
$img="<img src=\"".$filename."\"/>";
}
echo $img;
?>

以上代码是获取post参数url中的值地址,通过file_get_contents获取url中的图片内容,保存到image目录下,然后显示。

file_get_contents函数使用前需要将php.iniallow_url_fopen设置为ON。

以上的漏洞代码是存在SSRF漏洞的,可以控制url参数,自定义任意的URL,包括内网的URL,例如现在验证内网192.168.123.66是否开启了6379端口,可以做以下访问:

image-20211123221432540

根据上图的报错信息可以看出开启了6379,接下来测试是否开启了7777端口

image-20211123221814006

测试7777端口时,根据上图提示,显示错误信息,说明端口未开放。接下来测试80端口,看显示结果(因为80端口是开放的,所以测试下,看回显内容)

image-20211123221034055

访问80端口并未报错,此时,查看图片内容(选中图片,点复制链接),如下图:

image-20211123221258165

发现请求的80端口的数据被写入到了图片问题中,符合代码预期。除了可以探测端口,也可以做文件包含漏洞的利用,这里不再细说文件包含的知识。

fsockopen函数

fsockopen()函数用来打开一个网络连接或者一个Unix套接字连接。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
$host=$_GET['url'];
$port=$_GET['port'];
# fsockopen(主机名称,端口号码,错误号的接受变量,错误提示的接受变量,超时时间)
$fp = fsockopen($host, intval($port), $errno, $errstr, 30);
if (!$fp) {
echo "$errstr ($errno)<br />\n";
} else {
$out = "GET / HTTP/1.1\r\n";
$out .= "Host: $host\r\n";
$out .= "Connection: Close\r\n\r\n";
# fwrite() 函数将内容写入一个打开的文件中。
fwrite($fp, $out);
# 函数检测是否已到达文件末尾 ,文件末尾(EOF)
while (!feof($fp)) {
echo fgets($fp, 128);
}
fclose($fp);
}
?>

以上函数是接受url和port来制定socket访问的地址和端口,由于地址和端口用户可控,所以可以用来SSRF漏洞的利用。

例如可以做以下访问可以探测192.168.123.66的3306端口是否开放:

image-20211125224116389

做以下访问可以探测192.168.123.188的3306是否开放:

image-20211125224304557

未出现错误信息说明端口开放

curl_exec函数

curl_exec函数执行给定的cURL会话。这个函数应该在初始化一个cURL会话并且全部的选项都被设置后被调用。

1
2
3
4
5
<?php
$url = $_GET['url'];
$curlobj = curl_init($url);
echo curl_exec($curlobj);
?>

curl_exec函数是危害最大的函数,也是需要重点讲的函数。以上代码是获取参数url的值,使用curl进行访问。

curl_exec的使用需要3个条件:

  • PHP版本>=5.3
  • 开启extension=php_curl.dll
  • —wite-curlwrappers(编译PHP时用,此时不需要,可忽略)

使用dict协议探测80端口(后面会讲dict协议)

image-20211125225544944

使用dict协议探测6379端口

image-20211125225659371

可以根据回显的banner信息判断目标端口运行的服务为http和redis服务,dict协议还可以做到执行命令的功能,之后再进行讲解。

参考资料