Python安全運(yùn)維實(shí)戰(zhàn):針對幾種特定隱藏方式的Webshell查殺
Webshell一直都是網(wǎng)站管理員痛恨看到的東西,一旦在網(wǎng)站目錄里看到了陌生的webshell基本說明網(wǎng)站已經(jīng)被攻擊者拿下了。站在攻擊者的角度,要想滲透一臺網(wǎng)站服務(wù)器,第一個目標(biāo)也是想方設(shè)法的尋找漏洞上傳webshell。
對于webshell的防護(hù)通常基于兩點(diǎn):一是在攻擊者上傳和訪問時通過特征匹配進(jìn)行檢測攔截或限制文件類型阻止上傳;二就是日常基于webshell文件特征的靜態(tài)查殺(也有基于日志的,在這里不做討論)。第一種方法不是我們今天要討論的,waf、安全狗等一系列工具可以實(shí)現(xiàn)相應(yīng)的功能。第二種方式靜態(tài)查殺,通常會匹配一些關(guān)鍵字、危險函數(shù)、一些特征代碼及他們的各種加密形式,以遍歷文件的方式來進(jìn)行查殺。然而還有很多種通過破壞遍歷規(guī)則(使惡意文件無法被遍歷到)的隱藏方式,通常可以達(dá)到避免被查殺的目的。今天我們要說的就是: 如何利用python實(shí)現(xiàn)針對這幾種特定隱藏方式的webshell查殺。
一、ntfs交換數(shù)據(jù)流隱藏webshell
NTFS交換數(shù)據(jù)流(alternate data streams,簡稱ADS)是NTFS磁盤格式的一個特性,在NTFS文件系統(tǒng)下,每個文件都可以存在多個數(shù)據(jù)流,就是說除了主文件流之外還可以有許多非主文件流寄宿在主文件流中。它使用資源派生來維持與文件相關(guān)的信息,雖然我們無法看到數(shù)據(jù)流文件,但是它卻是真實(shí)存在于我們的系統(tǒng)中的。
利用ntfs交換數(shù)據(jù)流隱藏文件的方式很久以前就出現(xiàn)了,介紹利用這種方式來隱藏webshell的文章也不少。這種隱藏方式主要針對一句話木馬,因?yàn)槿绻话奈募榇篑R則失去了隱藏的意義(若被包含的文件為大馬,會直接跳轉(zhuǎn)到大馬頁面,原頁面也就相當(dāng)于被篡改了,很容易就會被發(fā)現(xiàn))。前兩天做了個測試,卻發(fā)現(xiàn)自己手頭經(jīng)常使用的幾個webshell查殺工具居然都檢測不出來,最好的結(jié)果只報了一個可疑文件包含,便考慮自己動手寫一寫。
整體邏輯很簡單,首先遍歷web應(yīng)用所在的文件夾,找出所有利用ntfs交換數(shù)據(jù)流隱藏的文件,組成一個list;其次遍歷所有.asp文件(以asp為例),找出所有采用了包含頭的.asp文件,將其路徑作為value,將被包含的文件路徑作為key,建立一個dict。與之前的list做對比,若在dict中發(fā)現(xiàn)了存在于list中的元素,則斷定它為webshell,最后將其路徑輸出,并同時將包含它的.asp文件路徑輸出。說白了就是以這個包含了ntfs交換數(shù)據(jù)流文件的動作來斷定它是否為webshell。
使用到的windows命令:dir /r #顯示文件的備用數(shù)據(jù)流
- # -*- coding: cp936 -*-
- import os,os.path
- import re
- def searchNTFS(catalog): #搜索所有ntfs ads文件目錄,返回list
- resultL= []
- forroot,dirs,files in os.walk(catalog): #利用os.walk()遞歸遍歷文件
- line= ''
- command = 'cd '+ root + '&' + 'dir /r'
- r =os.popen(command)
- info= r.readlines()
- forl in info:
- lineline = line + l
- reN= '\s(\S+)\:\$DATA'
- reres= re.findall(reN,line)
- forre1 in res:
- if re1 != '':
- result = root + '\\' + re1
- resultL.append(result)
- returnresultL
- def searchInclude(catalog): resultL= []
- resultD= {}
- forroot,dirs,files in os.walk(catalog):
- forf in files:
- dir = os.path.join(root,f)
- if dir[-4:] == '.asp':
- try :
- fp = open(dir,'r')
- for line in fp.readlines():
- reN = '<\!--#include\S+="(\S+)"-->'
- reres = re.findall(reN,line)
- for re1 in res:
- if re1 != '':
- result = root +'\\' + re1
- resultD[result]= root + '\\' +f
- except:
- print "File :" + dir + " can't be read"
- returnresultD
- if __name__ == "__main__":
- reD =searchInclude('C:\inetpub\wwwroot')
- reN =list(set(reD.keys()).intersection(set(searchNTFS('C:\inetpub\wwwroot'))))
- reI = []
- for re1in reN:
- reI.append(reD[re1])
- if reI!= []:
- forre2 in reN :
- print '###############################################################'
- print "[+]Suspicious ADS files found : " + re2
- for re3 in reI :
- print '###############################################################'
- print "[+]Including files: " + re3 + " \n Please check it."
- else :
- print "[+]No suspicious ADS files found."
二、畸形文件名、保留文件名隱藏webshell
簡單科普下,windows的畸形目錄名有很多種,通常是指文件名中存在多個.號,例如\a…\,圖形界面下無法訪問和刪除,命令行界面也只能通過windows的短文件名進(jìn)行訪問。
Windows的保留文件名,例如aux、prn、con、nul、com1~9、lpt1~9等等,windows不允許用戶以常規(guī)方式自行創(chuàng)建,但可以通過copy或者echo等命令加上網(wǎng)絡(luò)位置\\.\來創(chuàng)建,訪問也要在絕對路徑前加上\\.\來訪問(例如type \\.\C:\inetpub\wwwroot\aux.asp)。
利用的時候可以單種使用也可以一起使用,例如C:\inetpub\wwwroot\a…\aux.asp
我們需要用到命令:dir /x #顯示為非 8.3 文件名產(chǎn)生的短名稱
- # -*- coding: cp936 -*-
- import os,os.path
- import re
- def searchSFN(catalog):
- resultL = []
- resultL2 = []
- for root,dirs,files in os.walk(catalog): #利用os.walk()遞歸遍歷文件
- line = ''
- command = 'cd '+ root + '&' + 'dir/x'
- r = os.popen(command)
- info = r.readlines()
- for l in info:
- lineline = line + l
- reN1 = '\s+(\S+\~\S+)\s+\S+\.\.+'
- reres = re.findall(reN1,line)
- for re1 in res:
- if re1 != '':
- result = '\\\\.\\' + root + '\\'+ re1
- resultL.append(result)
- reN2
- ='\s+((aux|prn|con|nul|com1|com2|com3|com4|com5|com6|com7|com8|com9|lpt1|lpt2|lpt3|lpt4|lpt5|lpt6|lpt7|lpt8|lpt)\.\S+)\s+'
- reres2 = re.findall(reN2,line)
- for re2 in res2:
- if re2 != '':
- result2 = '\\\\.\\' + root +'\\' + re2[0]
- resultL2.append(result2)
- return resultL,resultL2
- defdeleteSFN(list,list2):
- for l1 in list :
- str = raw_input('Do you want to delete: ' + l1 + '? (y/n)')
- if str == 'y' :
- command = 'rd /s /q ' + l1
- r = os.popen(command)
- else :
- pass
- for l2 in list2 :
- str = raw_input('Do you want to delete: ' + l2 + '? (y/n)')
- if str == 'y' :
- command = 'del /f /q /a ' + l2
- r = os.popen(command)
- else :
- pass
- if __name__ =="__main__":
- list,list1 =searchSFN('C:\inetpub\wwwroot')
- deleteSFN(list,list1)
這里提供了兩個函數(shù),searchSFN()找出應(yīng)用目錄中所有畸形目錄名對應(yīng)的短文件名和所有windows保留文件名,返回兩個目錄列表,deleteSFN()決定是否刪除他們。
三、驅(qū)動隱藏webshell(Easy File Locker)
驅(qū)動隱藏的原理是在windows的指針遍歷到一個文件夾的時,增加一個文件夾大小的偏移量,直接跳過文件夾,從而達(dá)到隱藏的目的。現(xiàn)在最常見到的驅(qū)動隱藏通常是借助第三方軟件Easy File Locker實(shí)現(xiàn)的,幾年前也就存在了,但是說說我自己測試的結(jié)果吧,手頭的webshell查殺工具全軍覆沒,沒有一個能反映出一點(diǎn)痕跡的。簡單寫了個函數(shù)用于查看是否存在Easy File Locker的服務(wù)并刪除。利用了windows下的sc qc xlkfs、net stop xlkfs和sc delete xlkfs三條命令,xlkfs是Easy File Locker的服務(wù)名。(這里寫成腳本模式是為了方便后續(xù)寫成插件加入傳統(tǒng)查殺工具,否則直接使用命令即可)
- # -*- coding: cp936 -*-
- import os,os.path
- import re
- def searchEFL():
- line = ''
- command1 = 'sc qc xlkfs' #插看是否存在xlkfs服務(wù),返回1060則判定不存在
- command2 = 'net stop xlkfs' + '&' + 'sc delete xlkfs' #停止并刪除服務(wù)
- r =os.popen(command1)
- info = r.readlines()
- for l in info:
- lineline = line+ l
- if"1060" in line:
- print'[+]No XLKFS service found.'
- else :
- r =os.popen(command2)
- print'[+]XLKFS service found. Has been cleared.'
- if __name__ == "__main__":
- searchEFL()
四、總結(jié)
對于這幾種通過破壞遍歷規(guī)則的隱藏方式,其實(shí)都可以從其隱藏的動作直接判定它就是不懷好意的文件,不然為什么要做賊心虛的隱藏呢?但更可靠的方式就是先恢復(fù)遍歷,讓被隱藏的文件都能夠被遍歷到,然后再對文件進(jìn)行常規(guī)的查殺。第一部分和第二部分提供的函數(shù)的最終目的都是為了最后提供對應(yīng)的可訪問的目錄名列表,第三部分停止并刪除了Easy File Locker的服務(wù),文件自然就恢復(fù)了可遍歷性。這里提供的函數(shù)單獨(dú)也可以使用,但更推薦的做法是將其寫成插件的形式加入傳統(tǒng)查殺的工具中,使文件能夠被遍歷后,再對文件進(jìn)行常規(guī)查殺規(guī)則的匹配。Github上有很多python的webshell查殺項(xiàng)目,匹配的一些特征庫什么的已經(jīng)很全了,寫成插件加入后親測效果不錯,大家有興趣可以自己動動手去實(shí)現(xiàn)。
(以上實(shí)驗(yàn)環(huán)境基于windows server 2008r2 standard,iis 7.0)