["'])[^(?P=quote)]+?(?P=quote)[^`]*?))`(?P[^`]+)`】。
解釋一下:
【(?:(?:^(?:\s+)?)|(?:(?P["'])[^(?P=quote)]+?(?P=quote)[^`]*?))】
匹配開(kāi)始位置或者開(kāi)始位置之后有空白字符或者前面有代碼,且代碼有閉合的單雙引號(hào)。(這段PYTHON的正則中用了捕獲命名以及反向引用)
【`(?P[^`]+)`】這個(gè)就比較簡(jiǎn)單了,匹配反引號(hào)中間的字符串。
某檢測(cè)PHP webshell的python腳本考慮欠佳。
再看看下一個(gè)列表的第一個(gè)元素。【(system|shell_exec|exec|popen)】,這個(gè)正則的意思是只要字符串里包含“system”、“shell_exec”、“exec”、“popen”這四組字符串即判定為危險(xiǎn)字符。很明顯,這個(gè)方法太不嚴(yán)謹(jǐn)。如果程序員寫的代碼中,包含了這四組字符,即可被判定為危險(xiǎn)函數(shù)。很不準(zhǔn)確,誤報(bào)率極高。見(jiàn)下圖
某檢測(cè)PHP webshell的python腳本考慮欠佳。
到底什么樣的代碼是可疑的代碼?關(guān)鍵詞是什么?可疑的代碼肯定是由可以執(zhí)行危險(xiǎn)操作的函數(shù)構(gòu)成,可以執(zhí)行危險(xiǎn)操作的PHP函數(shù)最重要的就是“eval”函數(shù)了,對(duì)于加密的PHP代碼(僅變形字符串,非zend等方式加密),肯定要用到“eval”函數(shù),所以,對(duì)于不管是用哪種加密方法的代碼,肯定要用到“eval”函數(shù)。其次就是可以執(zhí)行系統(tǒng)命令的函數(shù)了,比如上面某牛的代碼中提到的四個(gè)“system”、“shell_exec”、“exec”、“popen”。當(dāng)然還有其他的,比如passthru等。PHP還支持“·”字符(ESC鍵下面那個(gè))直接執(zhí)行系統(tǒng)命令。我們可以把正則寫成這樣【\b(?P
eval|proc_open|popen|shell_exec|exec|passthru|system)\b\s*\(】。
檢測(cè)PHP webshell的python腳本相對(duì)較為嚴(yán)謹(jǐn)?shù)钠ヅ?
解釋一下:大家都知道【\b\b】用來(lái)匹配單詞兩邊的位置的。要保證【\b\b】中間的是單詞,即使函數(shù)名前面加特殊字符,也一樣通過(guò)匹配,比如加@來(lái)屏蔽錯(cuò)誤。后面的【\s*】用來(lái)匹配空白字符的,包括空格,tab鍵,次數(shù)為0到無(wú)數(shù)次。前面的【(?P)】是捕獲命名組。用來(lái)當(dāng)作python代碼直接引用匹配結(jié)果的key。
還有的網(wǎng)友提到了,如果我把代碼放到圖片拓展名的文件里呢?那你只檢測(cè).php,.inc的文件,還是找不到我的呀。嗯,是的,如果惡意代碼在gif、jpg、png、aaa等亂七八糟的拓展名文件里,是不能被apache、IIS等web Services解析的,必須通過(guò)include/require(_once)來(lái)引入。那么,我們只要匹配include/require(_once)后面的文件名是不是常規(guī)的“.php”、“.inc”文件。如果不是,則為可疑文件。正則如下【(?P
\b(?:include|require)(?:_once)?\b)\s*\(?\s*["'](?P .*?(?
檢測(cè)PHP WEBSHELL的python腳本較為嚴(yán)謹(jǐn)做法
解釋一下:先看【(?P
\b(?:include|require)(?:_once)?\b)】,【(?P )】為正則表達(dá)式的“命名捕獲”,PHP中有同樣的用法。也就是說(shuō),在這括號(hào)內(nèi)的捕獲的數(shù)據(jù),會(huì)分配到結(jié)果數(shù)組的key為“name”的value中。再看里面的【\b(?:include|require)(?:_once)?\b】,【\b\b】不解釋了,為單詞邊界位置。里面的【(?:include|require)】匹配字符串“include”、“require”兩個(gè)單詞,其中前面的【(?:)】未不分配組,用于提高效率,可以去掉【?:】變成【(include|require)】。在后面一個(gè)【(?:_once)】也是做不分配組的操作,便于提高正則表達(dá)式效率。同樣,后面的量詞是“?”代表這個(gè)組可有可無(wú)。就滿足了“include”、“include_once”、“require”、“require_once”四種情況。有的朋友可能這樣寫【(include|include_once|require|require_once)】也能實(shí)現(xiàn)目的。但是,為了更搞的效率,我們對(duì)這個(gè)正則做優(yōu)化,針對(duì)部分字符串做分支更改,改成上面那個(gè)【\b(?:include|require)(?:_once)?\b】。 再看下面的【\s*\(?\s*["'](?P
.+?(?.+?(?)】上面介紹了,為命名捕獲,把結(jié)果放到match.group(“filename”)里?!?*?】為任意字符,后面的量詞是“忽略優(yōu)先量詞”,也就是平常說(shuō)的“非貪婪”。這里最少匹配零個(gè),(防止.aa、.htaccess這種沒(méi)有文件名,只有文件拓展名的文件被引入)。后面的【(?
綜上所述,最后,鄙人給出的python代碼如下:
代碼如下:
#!/usr/bin/python
#-*- encoding:UTF-8 -*-
###
## @package
##
## @author CFC4N
## @copyright copyright (c) Www.cnxct.Com
## @Version $Id: check_php_shell.py 37 2010-07-22 09:56:28Z cfc4n $
###
import os
import sys
import re
import time
def listdir(dirs,liston='0'):
flog = open(os.getcwd()+"/check_php_shell.log","a+")
if not os.path.isdir(dirs):
print "directory %s is not exist"% (dirs)
return
lists = os.listdir(dirs)
for list in lists:
filepath = os.path.join(dirs,list)
if os.path.isdir(filepath):
if liston == '1':
listdir(filepath,'1')
elif os.path.isfile(filepath):
filename = os.path.basename(filepath)
if re.search(r"\.(?:php|inc|html?)$", filename, re.IGNORECASE):
i = 0
iname = 0
f = open(filepath)
while f:
file_contents = f.readline()
if not file_contents:
break
i += 1
match = re.search(r'''(?P\b(?:include|require)(?:_once)?\b)\s*\(?\s*["'](?P .*?(?if match:
function = match.group("function")
filename = match.group("filename")
if iname == 0:
info = '\n[%s] :\n'% (filepath)
else:
info = ''
info += '\t|-- [%s] - [%s] line [%d] \n'% (function,filename,i)
flog.write(info)
print info
iname += 1
match = re.search(r'\b(?Peval|proc_open|popen|shell_exec|exec|passthru|system)\b\s*\(', file_contents, re.IGNORECASE| re.MULTILINE)
if match:
function = match.group("function")
if iname == 0:
info = '\n[%s] :\n'% (filepath)
else:
info = ''
info += '\t|-- [%s] line [%d] \n'% (function,i)
flog.write(info)
print info
iname += 1
f.close()
flog.close()
if '__main__' == __name__:
argvnum = len(sys.argv)
liston = '0'
if argvnum == 1:
action = os.path.basename(sys.argv[0])
print "Command is like:\n %s D:\wwwroot\ \n %s D:\wwwroot\ 1 -- recurse subfolders"% (action,action)
quit()
elif argvnum == 2:
path = os.path.realpath(sys.argv[1])
listdir(path,liston)
else:
liston = sys.argv[2]
path = os.path.realpath(sys.argv[1])
listdir(path,liston)
flog = open(os.getcwd()+"/check_php_shell.log","a+")
ISOTIMEFORMAT='%Y-%m-%d %X'
now_time = time.strftime(ISOTIMEFORMAT,time.localtime())
flog.write("\n----------------------%s checked ---------------------\n"% (now_time))
flog.close()
## 最新代碼在文章結(jié)尾的鏈接里給出了。2010/07/31 更新。
僅供參考,歡迎斧正。下面截圖為掃描Discuz7.2的效果圖,當(dāng)然,也有誤報(bào)。相對(duì)網(wǎng)上流傳的python腳本,誤報(bào)更少,更精確了。
檢測(cè)PHP WEBSHELL的python腳本的檢測(cè)結(jié)果
問(wèn):這個(gè)方法完美了嗎?可以查找目前已知的所有危險(xiǎn)函數(shù)文件了嗎?
答:不能,如果include等引入的文件沒(méi)有拓展名,這里就匹配不到了。
問(wèn):如何解決?
答:留給你解決,聰明的你,肯定可以搞定。
PS:“`”反引號(hào) 執(zhí)行命令的還沒(méi)寫,暫時(shí)沒(méi)好的辦法。容易跟SQL語(yǔ)句中的反引號(hào)混淆。不太好匹配。如果光匹配反引號(hào)就提示的話,那誤報(bào)太大了。待定吧。(術(shù)業(yè)有專攻,請(qǐng)勿因?yàn)橐惶幉缓玫拇a,否定一個(gè)人的能力。你懂的。再次重申,此文只針對(duì)代碼,不針對(duì)人。其次,鄙人給出的python代碼隨便復(fù)制,隨便傳播,愛(ài)留版權(quán)就留版權(quán),不愛(ài)留就刪了相關(guān)字符,也就是您愛(ài)干嗎干嗎。)
我先休息一會(huì),明天再說(shuō)。(前半句為三國(guó)殺曹仁的臺(tái)詞,哈。)聲明:本網(wǎng)頁(yè)內(nèi)容旨在傳播知識(shí),若有侵權(quán)等問(wèn)題請(qǐng)及時(shí)與本網(wǎng)聯(lián)系,我們將在第一時(shí)間刪除處理。TEL:177 7030 7066 E-MAIL:11247931@qq.com