代碼審計變成CTF


0x01 代碼審計中的信息收集

一個cms代碼量確實不少,通讀代碼耗時長,效果也不一定好.而一個功能點如果之前出過漏洞,特別是多次出現漏洞的地方,證明開發者對這個漏洞的理解不充分,很容易再次繞過補丁.這樣,一整個CMS的代碼審計就可以降維到一道ctf題目.特別是對於經常參加ctf的各位大佬來說,這樣的代碼審計更加簡單休閑.我記得之前也有機構統計過,出過漏洞的地方更容易再次出現漏洞,普通CMS的開發者通常不是專業的安全人員,也不一定有專業的安全專家協助修復,再次出現漏洞的可能性就更大了.

我以github上的一個百星icms為例.

icms github鏈接: https://github.com/idreamsoft/iCMS \

在issue中搜索SSRF https://github.com/idreamsoft/iCMS/issues?utf8=%E2%9C%93&q=is%3Aissue+ssrf

在cve列表中查找,應該對應的就是這三個cve了

可以看到這個功能點已經出現了三次的繞過與過濾.

大致了解下這個功能點,是一個自動更新文章的爬蟲,多處都可以控制url參數.

點開issue查看具體信息,我們從最早出現漏洞的版本看起.

通過查看具體的commits,可以找到開發者修復漏洞的思路.這給我們代碼審計帶來很大的便利.

CVE-2018-14514 漏洞分析

commit: https://github.com/idreamsoft/iCMS/issues/29

提交者詳細描述了漏洞信息,只指出了一個點,但根據作者修復的commit,有兩處都存在SSRF漏洞.

SSRF:

public static function postUrl($url, $data) {
 is_array($data) && $data = http_build_query($data);  $options = array(  CURLOPT_URL => $url,  ...  );  $ch = curl_init();  curl_setopt_array($ch,$options);  $responses = curl_exec($ch);  curl_close ($ch);  return $responses;  } 

icms7.0.9\app\spider\spider_tools.class.php 604行,關鍵代碼:

public static function remote($url, $_count = 0) {
 $url = str_replace('&', '&', $url);  if(empty(spider::$referer)){  $uri = parse_url($url);  spider::$referer = $uri['scheme'] . '://' . $uri['host'];  }  self::$curl_info = array();  $options = array(  CURLOPT_URL => $url,  ...  );  spider::$cookie && $options[CURLOPT_COOKIE] = spider::$cookie;  if(spider::$curl_proxy){  $proxy = self::proxy_test();  $proxy && $options = iHttp::proxy($options,$proxy);  }  if(spider::$PROXY_URL){  $options[CURLOPT_URL] = spider::$PROXY_URL.urlencode($url);  }  $ch = curl_init();  curl_setopt_array($ch,$options);  $responses = curl_exec($ch);  ... } 

兩處都是因為使用了curl,且無安全措施,只需要url參數可控即可進行SSRF攻擊.

可以看到icms7.0.9版本沒有做任何的驗證,並且可以使用任意協議訪問任意ip與端口.因此如果有redis或無密碼的mysql或者一些其他容易被攻擊的服務,可以getshell.因為這里重點不是通過SSRF如何getshell ,因此不做getshell的驗證.

我們找一處漏洞點測試,有很多處都調用了remote函數,全局搜索即可,我們找一個能即時回顯的點測試.

payload:http://ip/admincp.php?app=spider&do=testdata&url=dict://127.0.0.1:8000&rid=2&pid=0&title=m09ic

監聽端口觀察是否有數據過來.

很明顯收到了.

CVE-2018-14514 補丁分析

我們再來看看作者是如何修復的,commit: https://github.com/idreamsoft/iCMS/commit/64bb0bdf77febbd6ac0ccb6658ee1ddc71530bb1

public static function remote($url, $_count = 0) {
 if(!iHttp::is_url($url,true)){  if (spider::$dataTest || spider::$ruleTest) {  echo "<b>{$url} 請求錯誤:非正常URL格式,因安全問題只允許抓取 http:// 或 https:// 開頭的鏈接</b>";  }  return false;  } 

作者添加了一個判斷函數

public static function is_url($url,$strict=false) {
 $url = trim($url);  if($strict){  return (stripos($url, 'http://') === 0 || stripos($url, 'https://') === 0);  }  if (stripos($url, 'http://') === false && stripos($url, 'https://') === false) {  return false;  } else {  return true;  } } 

先判斷url是否以http://開頭,才開始解析,這樣就限制了危險的協議,減輕了危害程度,大部分情況很難getshell.但是SSRF漏洞依然存在.

可以發現,作者對SSRF漏洞的認識並不到位,認為不能getshell就可以了.但是實際上,用HTTP協議也並非完全不可能getshell,內網有可能存在一些可以被GET請求getshell的服務,比如thinkphp的幾個RCE,就算不能RCE,SSRF也可以直接被用來進行內網信息收集,同樣是不可忽視的漏洞.

CVE-2018-14858 補丁分析

顯然只允許http與https開頭的url訪問,SSRF依然存在,於是在icms7.0.11版本,又有人提交了SSRF漏洞,並獲得了一個CVE編號.漏洞成因與上一個漏洞一致,作者的過濾措施雖然緩解了該漏洞的危害,但是漏洞依然存在.下面是作者在issue中的回復:

提交者除了提交漏洞,還簡單說明了幾種常見的ssrf繞過手法,比如不同格式的ip地址.

這是作者在issue33下的回復.

I know this question, but if the IP format is banned, the website using the IP format will not be collected. Although it is not used a lot, it will still be encountered. There is no better way to think about it now.

然后過了幾天,作者意識到了這樣並不算修復了SSRF漏洞,再次commit了一個補丁.

具體更新內容: https://github.com/idreamsoft/iCMS/commit/62de04e57a67f2690dbf88b7d381af61a0969ef3

添加了過濾代碼,關鍵代碼如下:

public static function remote($url, $_count = 0) {
 if(!iHttp::is_url($url,true)){  $parsed = parse_url($url);//解析url  $validate_ip = true;  preg_match('/\d+/', $parsed['host']) && $parsed['host'] = long2ip($parsed['host']);//獲取host部分,如果是十進制或其他進制的ip地址,轉化成標准的ip地址  if(preg_match('/\d+\.\d+\.\d+\.\d+/', $parsed['host'])){  $validate_ip = filter_var($parsed['host'], FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE);//匹配正確的ip格式,過濾非法ip地址字符與內外地址  }  if(!in_array($parsed['scheme'],array('http','https')) || !$validate_ip|| strtolower($parsed['host'])=='localhost'){  if (spider::$dataTest || spider::$ruleTest) {  echo "<b>{$url} 請求錯誤:非正常URL格式,因安全問題只允許抓取 http:// 或 https:// 開頭的鏈接</b>";  echo "<b>{$url} 請求錯誤:非正常URL格式,因安全問題只允許抓取 http:// 或 https:// 開頭的鏈接或私有IP地址</b>";  }  return false;  }  }  ... } 

可以看到,這次添加了檢查ip地址的格式,以及是否是內網ip.

以普通開發者的角度思考,很多情況都是哪里出了問題就修哪里,什么東西能繞過就過濾什么.也很難要求他們完全了解安全漏洞,因此也導致了修復再次被繞過.

CVE-2018-15895補丁分析

與上個漏洞提交者是同一個人. https://github.com/idreamsoft/iCMS/issues/40

然而,普通開發人員通常是哪里有問題就去解決哪里的問題,並不一定能對某個漏洞有深入的認識,更不用說了解全部攻擊與繞過手段,但是只要漏了一種,修復補丁就等於完全沒有.

我們都知道,SSRF的常用繞過手法還有302重定向與DNS重綁定.漏洞提交者也演示了這兩種方式.具體POC可以看issue內容.

關鍵代碼如下:

public static function safe_url($url) {
 $parsed = parse_url($url);  $validate_ip = true;  if($parsed['port'] && is_array(self::$safe_port) && !in_array($parsed['port'],self::$safe_port)){  if (spider::$dataTest || spider::$ruleTest) {  echo "<b>請求錯誤:非正常端口,因安全問題只允許抓取80,443端口的鏈接,如有特殊需求請自行修改程序</b>".PHP_EOL;  }  return false;  }else{  preg_match('/^\d+$/', $parsed['host']) && $parsed['host'] = long2ip($parsed['host']);  $long = ip2long($parsed['host']);  if($long===false){  $ip = null;  if(self::$safe_url){  @putenv('RES_OPTIONS=retrans:1 retry:1 timeout:1 attempts:1');  $ip = gethostbyname($parsed['host']);  $long = ip2long($ip);  $long===false && $ip = null;  @putenv('RES_OPTIONS');  }  }else{  $ip = $parsed['host'];  }  $ip && $validate_ip = filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE);  }  if(!in_array($parsed['scheme'],array('http','https')) || !$validate_ip){  if (spider::$dataTest || spider::$ruleTest) {  echo "<b>{$url} 請求錯誤:非正常URL格式,因安全問題只允許抓取 http:// 或 https:// 開頭的鏈接或公有IP地址</b>".PHP_EOL;  }  return false;  }else{  return $url;  }  } 

可以看到,使用了第17行使用了gethostbyname 確定parse_url解析后的host部分,來防護DNS rebinding 攻擊.

並且在curl的options中,注釋了// CURLOPT_FOLLOWLOCATION => 1,// 使用自動跳轉,來防護302重定向繞過.

經常打ctf的小伙伴可能就會注意到了,攻擊的思路可以針對parse_url的解析問題.歷代parse_url存在不少方式缺陷,比如scheme,host,port,path等均有過繞過的記錄.而這些細節,是開發者很難注意到的.如果想要再次繞過,這里就是個很好的突破點.

新的繞過

光黑名單和檢查host真實ip來說,基本上是萬無一失.但是作者萬萬沒想到,來自php自身的背后一刀.在2017年blackhat上orange師傅演講的 A New Era of SSRF 中,有一個新的攻擊方式,利用php中的parse_url函數和libcurl對url的解析差異,導致了對host的過濾失效,成功繞過.

從orange師傅的ppt中偷一張圖來解釋.

php-curl拓展解析url的host在第二個@之后,而parse_url則是最后一個@之后.

因此我們可以使用如下payload繞過:

http://ip/admincp.php?app=spider_project&do=test&url=http://m09ic@127.0.0.1:81@baidu.com/&rid=2&pid=1&title=

可以看到,同時繞過了port和host的限制,訪問到了只對本地開放的81端口的phpinfo內容.成功繞過過濾實現SSRF.

這里有一個小坑,在較新版本的php-curl中,已經修復了多個@的解析問題,使用多個@會報錯,不知道為啥不是調整到與parse_url一致,這種修復顯然影響了可用性.

該漏洞也不單是cms的問題,也有curl的問題.不管所使用的所有開源組件是不是安全的,在常見漏洞上cms中再加一層過濾是必要的.

大多數linux發行版並沒有使用最新版本的curl.可以在 https://curl.haxx.se/download.html 這里查詢linux發行版與curl版本的對應關系,應該少有公司會實時更新操作系統版本,只要不是最新版本的操作系統,基本都存在該漏洞.

我只測試了ubuntu,在ubuntu16.04及以下均可以使用該方式繞過.而在ubuntu18.04中,已經不再可以.exec_curl函數執行會直接返回false.

ubuntu16.04的curl版本是:

# curl -V
curl 7.47.0 (x86_64-pc-linux-gnu) libcurl/7.47.0 GnuTLS/3.4.10 zlib/1.2.8 libidn/1.32 librtmp/2.3
Protocols: dict file ftp ftps gopher http https imap imaps ldap ldaps pop3 pop3s rtmp rtsp smb smbs smtp smtps telnet tftp 
Features: AsynchDNS IDN IPv6 Largefile GSS-API Kerberos SPNEGO NTLM NTLM_WB SSL libz TLS-SRP UnixSockets

已經提交了issue,坐等作者的修復,期待是否還有被繞過的可能:D

最后

(面向github代碼審計)

一個開發人員很難有精力去了解一個攻擊方式的方方面面,也很難讓開發者緊跟攻擊手法的趨勢.在剛才的例子看到,雖然開發者積極的解決漏洞,但是並不能有效緩解漏洞,總有普通開發者不知道的方式再次繞過.

總得來說,初嘗代碼審計時,可以多翻翻issue中多次出現漏洞的點,這種地方再次出現漏洞的幾率相對來說較高.

另外,這個漏洞在利用要進入后台,又過濾了各種敏感協議,實際上危害並不大,僅僅用來學習代碼審計的思路以及常見SSRF的繞過與防護方式.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM