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