先來看下反引號可以成功執行命名的程式碼片段。代碼如下:
複製代碼 代碼如下:`ls -al`;
`ls -al`;
echo "sss"; `ls -al`;
$sql = "SELECT `username` FROM `table` WHERE 1";
$sql = 'SELECT `username` FROM `table` WHERE 1'
/*
無非是 前面有空白字元,或者在一行代碼的結束之後,後面接著寫,下面兩行為意外情況,也就是SQL命令裡的反引號,要排除的就是它。
*/
Regex該如何寫?
分析:
對於可移植性的部分共同點是什嗎?與其他正常的包含反引號的部分,區別是什嗎?
他們前面可以有空格,tab鍵等空白字元。也可以有程式碼,前提是如果有引號(單雙)必須是閉合的。才是危險有隱患的。遂CFC4N給出的正則如下:【(?:(?:^(?:\s+)?)|(?:(?P<quote>["'])[^(?P=quote)]+?(?P=quote)[^`]*?))`(?P<shell>[^`]+)`】。
解釋一下:
【(?:(?:^(?:\s+)?)|(?:(?P<quote>["'])[^(?P=quote)]+?(?P=quote)[^`]*?))】
匹配開始位置或者開始位置之後有空白字元或者前面有代碼,且代碼有閉合的單雙引號。(這段PYTHON的正則中用了捕獲命名以及反向引用)
【`(?P<shell>[^`]+)`】這個就比較簡單了,匹配反引號中間的字串。
某檢測PHP webshell的python指令碼考慮欠佳。
再看看下一個列表的第一個元素。【(system|shell_exec|exec|popen)】,這個正則的意思是只要字串裡包含“system”、“shell_exec”、“exec”、“popen”這四組字串即判定為危險字元。很明顯,這個方法太不嚴謹。如果程式員寫的代碼中,包含了這四組字元,即可被判定為危險函數。很不準確,誤判率極高。見
某檢測PHP webshell的python指令碼考慮欠佳。
到底什麼樣的代碼是可疑的代碼?關鍵詞是什嗎?
可疑的代碼肯定是由可以執行危險操作的函數構成,可以執行危險操作的PHP函數最重要的就是“eval”函數了,對於加密的PHP代碼(僅變形字串,非zend等方式加密),肯定要用到“eval”函數,所以,對於不管是用哪種加密方法的代碼,肯定要用到“eval”函數。其次就是可以執行系統命令的函數了,比如上面某牛的代碼中提到的四個“system”、“shell_exec”、“exec”、“popen”。當然還有其他的,比如passthru等。PHP還支援“·”字元(ESC鍵下面那個)直接執行系統命令。我們可以把正則寫成這樣【\b(?P<function>eval|proc_open|popen|shell_exec|exec|passthru|system)\b\s*\(】。
檢測PHP webshell的python指令碼相對較為嚴謹的匹配
解釋一下:
大家都知道【\b\b】用來匹配單詞兩邊的位置的。要保證【\b\b】中間的是單詞,即使函數名前面加特殊字元,也一樣通過匹配,比如加@來屏蔽錯誤。後面的【\s*】用來匹配空白字元的,包括空格,tab鍵,次數為0到無數次。前面的【(?P)】是捕獲命名組。用來當作python代碼直接引用匹配結果的key。
還有的網友提到了,如果我把代碼放到圖片拓展名的檔案裡呢?那你只檢測.php,.inc的檔案,還是找不到我的呀。嗯,是的,如果惡意代碼在gif、jpg、png、aaa等亂七八糟的拓展名檔案裡,是不能被apache、IIS等web Services解析的,必須通過include/require(_once)來引入。那麼,我們只要匹配include/require(_once)後面的檔案名稱是不是常規的“.php”、“.inc”檔案。如果不是,則為可疑檔案。正則如下【(?P<function>\b(?:include|require)(?:_once)?\b)\s*\(?\s*["'](?P<filename>.*?(?<!\.(?:php|inc)))["']】。
檢測PHP WEBSHELL的python指令碼較為嚴謹做法
解釋一下:
先看【(?P<function>\b(?:include|require)(?:_once)?\b)】,【(?P<name>)】為Regex的“命名捕獲”,PHP中有同樣的用法。也就是說,在這括弧內的捕獲的資料,會分配到結果數組的key為“name”的value中。再看裡面的【\b(?:include|require)(?:_once)?\b】,【\b\b】不解釋了,為單詞邊界位置。裡面的【(?:include|require)】匹配字串“include”、“require”兩個單詞,其中前面的【(?:)】未不分配組,用於提高效率,可以去掉【?:】變成【(include|require)】。在後面一個【(?:_once)】也是做不分配組的操作,便於提高Regex效率。同樣,後面的量詞是“?”代表這個組可有可無。就滿足了“include”、“include_once”、“require”、“require_once”四種情況。有的朋友可能這樣寫【(include|include_once|require|require_once)】也能實現目的。但是,為了更搞的效率,我們對這個正則做最佳化,針對部分字串做分支更改,改成上面那個【\b(?:include|require)(?:_once)?\b】。
再看下面的【\s*\(?\s*["'](?P<filename>.+?(?<!\.(?:php|inc)))["']】中,【\s*】匹配空白字元,包括空格,tab鍵等。後面的【\(?】,匹配字元“(”,後面的量詞“?”表示這半個小酷括弧可有可無。防止“incude “123.php””這種沒有括弧的情況。再後面【["']】匹配雙引號,單引號的。最後的也是。再看看這個【(?P<filename>.+?(?<!\.(?:php|inc)))】,其中【(?P<filename>)】上面介紹了,為命名捕獲,把結果放到match.group(“filename”)裡。【.*?】為任一字元,後面的量詞是“忽略優先量詞”,也就是平常說的“非貪婪”。這裡最少匹配零個,(防止.aa、.htaccess這種沒有檔案名稱,只有檔案拓展名的檔案被引入)。後面的【(?<!\.(?:php|inc))】,這裡用到了反向零寬斷言(環視)的非操作(只匹配位置,不匹配字串,跟【^$\b】等一樣)。這個運算式是針對這個位置的後面字元起作用的,也就是說後面的【["']】的前面不能是“.php”、“.inc”,這裡也就是取了檔案名稱的最後的拓展名。(正則裡,可以用【^】對字元取非,但是不能對“字串組”取非,這裡用了零寬斷言來實現。)
綜上所述,最後,鄙人給出的python代碼如下: 複製代碼 代碼如下:#!/usr/bin/python
#-*- encoding:UTF-8 -*-
###
## @package
##
## @author CFC4N <cfc4nphp@gmail.com>
## @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<function>\b(?:include|require)(?:_once)?\b)\s*\(?\s*["'](?P<filename>.*?(?<!\.(?:php|inc)))["']''', file_contents, re.IGNORECASE| re.MULTILINE)
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(?P<function>eval|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()
## 最新代碼在文章結尾的連結裡給出了。2010/07/31 更新。
僅供參考,歡迎斧正。
下面為掃描Discuz7.2的,當然,也有誤判。相對網上流傳的python指令碼,誤判更少,更精確了。
檢測PHP WEBSHELL的python指令碼的檢測結果
問:這個方法完美了嗎?可以尋找目前已知的所有危險函數檔案了嗎?
答:不能,如果include等引入的檔案沒有拓展名,這裡就匹配不到了。
問:如何解決?
答:留給你解決,聰明的你,肯定可以搞定。
PS:“`”反引號 執行命令的還沒寫,暫時沒好的辦法。容易跟SQL語句中的反引號混淆。不太好匹配。如果光匹配反引號就提示的話,那誤判太大了。待定吧。(術業有專攻,請勿因為一處不好的代碼,否定一個人的能力。你懂的。再次重申,此文只針對代碼,不針對人。其次,鄙人給出的python代碼隨便複製,隨便傳播,愛留著作權就留著作權,不愛留就刪了相關字元,也就是您愛幹嗎幹嗎。)
我先休息一會,明天再說。(前半句為三國殺曹仁的台詞,哈。)