Discuz教程网

精确查找PHP WEBSHELL木马

[复制链接]
authicon 星火燎原 发表于 2011-5-19 23:50:02 | 显示全部楼层 |阅读模式
上篇提到了关于网上流传查找PHP webshell的python脚本中,不严谨的代码,并且给出了一个python的检测代码,同时,下文里也提到不能检测到反引号的命令执行的地方。今天,我想了下,现在把思路发出来。




先来看下反引号可以成功执行命名的代码片段。代码如下:
代码如下:

  1. `ls -al`;
  2. `ls -al`;
  3. echo "sss"; `ls -al`;

  4. $sql = "SELECT `username` FROM `table` WHERE 1";

  5. $sql = 'SELECT `username` FROM `table` WHERE 1'
复制代码

/*
无非是 前面有空白字符,或者在一行代码的结束之后,后面接着写,下面两行为意外情况,也就是SQL命令里的反引号,要排除的就是它。
*/


正则表达式该如何写?
分析:
对于可移植性的部分共同点是什么?与其他正常的包含反引号的部分,区别是什么?
他们前面可以有空格,tab键等空白字符。也可以有程序代码,前提是如果有引号(单双)必须是闭合的。才是危险有隐患的。遂CFC4N给出的正则如下:【(?:(?:^(?:\s+)?)|(?:(?P<quote>["'])[^(?P=quote)]+?(?P=quote)[^`]*?))`(?P<shell>[^`]+)`】。

解释一下:

【(?:(?:^(?:\s+)?)|(?:(?P<quote>["'])[^(?P=quote)]+?(?P=quote)[^`]*?))】匹配开始位置或者开始位置之后有空白字符或者前面有代码,且代码有闭合的单双引号。(这段PYTHON的正则中用了捕获命名以及反向引用)

【`(?P<shell>[^`]+)`】这个就比较简单了,匹配反引号中间的字符串。

python脚本检测PHP WEBSHELL
然后我将这段代码写入程序中,测试跑了一下discuz的程序。结果有一个误报。误报的位置为“config.inc.php”中的“define(‘UC_DBTABLEPRE', ‘`ucenter`.uc_');”,什么原因造成的?这行代码符合了前面有闭合的引号,也有反引号的使用,所以,符合要求,被检测到了。如何再排除这种情况呢?这个有什么特殊的?前面有逗号“,”?如果是字符串连接的点号“.”呢?再排除逗号?

好吧,我错了,我不该用我的思维来误导你。换个思路。找下反引号可执行的代码的前面字符串的情况,他们只能是行的开始,或者有空白字符(包括空格,tab键等),再前面也可以有代码的结束标识分号“;”,其他的情况,都是不可以执行的吧?嗯,应该是这样。(如有错误,欢迎斧正)既然思路有了,那正则代码更好写了。如下【(^|(?<=;))\s*`[^`]+`】,解释一下,【(^|(?<=;))】匹配位置,是行的开始,或者前面有分号“;”。【\s*`[^`]+`】空白字符任一个,然后是….(你懂的)。OK,写好之后,检测,又发现一个问题。

匹配引入文件的正则也匹配了“require_once ‘./include/db_'.$database.'.class.php';”这种代码,什么原因造成的,您自己分析吧。
给出修复之后的python代码,如下:
代码如下:

  1. #!/usr/bin/python
  2. #-*- encoding:UTF-8 -*-
  3. ###
  4. ## @package
  5. ##
  6. ## @author CFC4N <cfc4nphp@gmail.com>
  7. ## @copyright copyright (c) Www.cnxct.Com
  8. ## @Version $Id: check_php_shell.py 37 2010-07-22 09:56:28Z cfc4n $
  9. ###
  10. import os
  11. import sys
  12. import re
  13. import time
  14. def listdir(dirs,liston='0'):
  15. flog = open(os.getcwd()+"/check_php_shell.log","a+")
  16. if not os.path.isdir(dirs):
  17. print "directory %s is not exist"% (dirs)
  18. return
  19. lists = os.listdir(dirs)
  20. for list in lists:
  21. filepath = os.path.join(dirs,list)
  22. if os.path.isdir(filepath):
  23. if liston == '1':
  24. listdir(filepath,'1')
  25. elif os.path.isfile(filepath):
  26. filename = os.path.basename(filepath)
  27. if re.search(r"\.(?:php|inc|html?)$", filename, re.IGNORECASE):
  28. i = 0
  29. iname = 0
  30. f = open(filepath)
  31. while f:
  32. file_contents = f.readline()
  33. if not file_contents:
  34. break
  35. i += 1
  36. match = re.search(r'''(?P<function>\b(?:include|require)(?:_once)?\b)\s*\(?\s*["'](?P<filename>[^;]*(?<!\.(?:php|inc)))["']\)?\s*;''', file_contents, re.IGNORECASE| re.MULTILINE)
  37. if match:
  38. function = match.group("function")
  39. filename = match.group("filename")
  40. if iname == 0:
  41. info = '\n[%s] :\n'% (filepath)
  42. else:
  43. info = ''
  44. info += '\t|-- [%s] - [%s] line [%d] \n'% (function,filename,i)
  45. flog.write(info)
  46. print info
  47. iname += 1
  48. match = re.search(r'\b(?P<function>eval|proc_open|popen|shell_exec|exec|passthru|system)\b\s*\(', file_contents, re.IGNORECASE| re.MULTILINE)
  49. if match:
  50. function = match.group("function")
  51. if iname == 0:
  52. info = '\n[%s] :\n'% (filepath)
  53. else:
  54. info = ''
  55. info += '\t|-- [%s] line [%d] \n'% (function,i)
  56. flog.write(info)
  57. print info
  58. iname += 1
  59. match = re.search(r'(^|(?<=;))\s*`(?P<shell>[^`]+)`\s*;', file_contents, re.IGNORECASE)
  60. if match:
  61. shell = match.group("shell")
  62. if iname == 0:
  63. info = '\n[%s] :\n'% (filepath)
  64. else:
  65. info = ''
  66. info += '\t|-- [``] command is [%s] in line [%d] \n'% (shell,i)
  67. flog.write(info)
  68. print info
  69. iname += 1
  70. f.close()
  71. flog.close()
  72. if '__main__' == __name__:
  73. argvnum = len(sys.argv)
  74. liston = '0'
  75. if argvnum == 1:
  76. action = os.path.basename(sys.argv[0])
  77. print "Command is like:\n %s D:\wwwroot\ \n %s D:\wwwroot\ 1 -- recurse subfolders"% (action,action)
  78. quit()
  79. elif argvnum == 2:
  80. path = os.path.realpath(sys.argv[1])
  81. listdir(path,liston)
  82. else:
  83. liston = sys.argv[2]
  84. path = os.path.realpath(sys.argv[1])
  85. listdir(path,liston)
  86. flog = open(os.getcwd()+"/check_php_shell.log","a+")
  87. ISOTIMEFORMAT='%Y-%m-%d %X'
  88. now_time = time.strftime(ISOTIMEFORMAT,time.localtime())
  89. flog.write("\n----------------------%s checked ---------------------\n"% (now_time))
  90. flog.close()
复制代码



稍微检测了一下Discuz7.2的代码,还是有误报的,误报的为这种包含sql的代码:
代码如下:

  1. $query = $db->query("SELECT `status`,`threads`,`posts`
  2. FROM `{$tablepre}forums` WHERE
  3. `status`='1';
  4. ");
复制代码




稍微检测了一下Discuz7.2的代码,还是有误报的,误报的为这种包含sql的代码:
代码如下:

  1. $query = $db->query("SELECT `status`,`threads`,`posts`
  2. FROM `{$tablepre}forums` WHERE
  3. `status`='1';
  4. ");
复制代码




由于这个脚本是按照一行一行的代码来处理的,所以,有这种误报。您自己去修复吧。相对网上流传的脚本来说,还是比较准确的。
欢迎转载。转载请注明来源,以及留下博客链接,同时,不能用于商业用途。(已经修复,增加了反引号后面【\s*;】的判断。2010-07-27 17:06)

PS:如果说上传文件也算是危险的、值得注意的操作的话,建议加上move_uploaded_file函数的检测。你知道在哪里加的。^_^

2010-12-17 关于这些代码,已经放到google 的代码托管上了。SVN地址为 http://code.google.com/p/cnxct/ 大家个获得最新版。

我是一个PHPer,写的python有点憋,有点懒,还请各位安全界的大牛,程序界的前辈不要鄙视,要给建议,谢谢。php版的以后在写吧。同时,也欢迎各位安全爱好者反馈最新的web shell特征代码,我尽力增加到程序中区。
完整的代码
代码如下:

  1. #!/usr/bin/python
  2. #-*- encoding:UTF-8 -*-
  3. ###
  4. ## @package
  5. ##
  6. ## @author CFC4N <cfc4nphp@gmail.com>
  7. ## @copyright copyright (c) Www.cnxct.Com
  8. ## @Version $Id$
  9. ###
  10. import os
  11. import sys
  12. import re
  13. import time
  14. def listdir(dirs,liston='0'):
  15. flog = open(os.getcwd()+"/check_php_shell.log","a+")
  16. if not os.path.isdir(dirs):
  17. print "directory %s is not exist"% (dirs)
  18. return
  19. lists = os.listdir(dirs)
  20. for list in lists:
  21. filepath = os.path.join(dirs,list)
  22. if os.path.isdir(filepath):
  23. if liston == '1':
  24. listdir(filepath,'1')
  25. elif os.path.isfile(filepath):
  26. filename = os.path.basename(filepath)
  27. if re.search(r"\.(?:php|inc|html?)$", filename, re.IGNORECASE):
  28. i = 0
  29. iname = 0
  30. f = open(filepath)
  31. while f:
  32. file_contents = f.readline()
  33. if not file_contents:
  34. break
  35. i += 1
  36. match = re.search(r'''(?P<function>\b(?:include|require)(?:_once)?\b)\s*\(?\s*["'](?P<filename>[^;]*(?<!\.(?:php|inc)))["']\)?\s*;''', file_contents, re.IGNORECASE| re.MULTILINE)
  37. if match:
  38. function = match.group("function")
  39. filename = match.group("filename")
  40. if iname == 0:
  41. info = '\n[%s] :\n'% (filepath)
  42. else:
  43. info = ''
  44. info += '\t|-- [%s] - [%s] line [%d] \n'% (function,filename,i)
  45. flog.write(info)
  46. print info
  47. iname += 1
  48. match = re.search(r'\b(?P<function>eval|proc_open|popen|shell_exec|exec|passthru|system|assert|fwrite|create_function)\b\s*\(', file_contents, re.IGNORECASE| re.MULTILINE)
  49. if match:
  50. function = match.group("function")
  51. if iname == 0:
  52. info = '\n[%s] :\n'% (filepath)
  53. else:
  54. info = ''
  55. info += '\t|-- [%s] line [%d] \n'% (function,i)
  56. flog.write(info)
  57. print info
  58. iname += 1
  59. match = re.search(r'(^|(?<=;))\s*`(?P<shell>[^`]+)`\s*;', file_contents, re.IGNORECASE)
  60. if match:
  61. shell = match.group("shell")
  62. if iname == 0:
  63. info = '\n[%s] :\n'% (filepath)
  64. else:
  65. info = ''
  66. info += '\t|-- [``] command is [%s] in line [%d] \n'% (shell,i)
  67. flog.write(info)
  68. print info
  69. iname += 1
  70. match = re.search(r'(?P<shell>\$_(?:POS|GE|REQUES)T)\s*\[[^\]]+\]\s*\(', file_contents, re.IGNORECASE)
  71. if match:
  72. shell = match.group("shell")
  73. if iname == 0:
  74. info = '\n[%s] :\n'% (filepath)
  75. else:
  76. info = ''
  77. info += '\t|-- [``] command is [%s] in line [%d] \n'% (shell,i)
  78. flog.write(info)
  79. print info
  80. iname += 1
  81. f.close()
  82. flog.close()
  83. if '__main__' == __name__:
  84. argvnum = len(sys.argv)
  85. liston = '0'
  86. if argvnum == 1:
  87. action = os.path.basename(sys.argv[0])
  88. print "Command is like:\n %s D:\wwwroot\ \n %s D:\wwwroot\ 1 -- recurse subfolders"% (action,action)
  89. quit()
  90. elif argvnum == 2:
  91. path = os.path.realpath(sys.argv[1])
  92. listdir(path,liston)
  93. else:
  94. liston = sys.argv[2]
  95. path = os.path.realpath(sys.argv[1])
  96. listdir(path,liston)
  97. flog = open(os.getcwd()+"/check_php_shell.log","a+")
  98. ISOTIMEFORMAT='%Y-%m-%d %X'
  99. now_time = time.strftime(ISOTIMEFORMAT,time.localtime())
  100. flog.write("\n----------------------%s checked ---------------------\n"% (now_time))
  101. flog.close()
复制代码



20110413005813333.jpg



上一篇:PHP中正则表达式对UNICODE字符码的匹配方法
下一篇:PHP正则表达式的效率 回溯与固化分组
authicon nmzc 发表于 2011-5-20 00:29:38 | 显示全部楼层
哦哦,发财了啊,看到好东西啦
authicon shakesxia 发表于 2011-5-20 01:29:53 | 显示全部楼层
继续来索要
authicon D_hong 发表于 2011-5-20 05:29:46 | 显示全部楼层
好东西要顶的。
authicon 蓝色天空k 发表于 2011-5-20 10:59:58 | 显示全部楼层
好辛苦才找到啊
authicon melody0721 发表于 2011-5-20 13:29:58 | 显示全部楼层
不错,谢谢分享
authicon 21585151 发表于 2011-5-20 15:30:04 | 显示全部楼层
不错,我喜欢
authicon 乐娃娃 发表于 2011-5-21 00:29:48 | 显示全部楼层
支持楼主,顶一下
authicon TRACYFLYING 发表于 2011-5-21 10:53:12 | 显示全部楼层
顶的就是你
authicon Pianissimo 发表于 2011-5-21 11:29:54 | 显示全部楼层
真的有意思!
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

1314学习网 ( 浙ICP备10214163号 )

GMT+8, 2025-5-2 20:08

Powered by Discuz! X3.4

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表