今天发现phpinfo_scanner的一个问题,当遇到证书不匹配等问题就爬不出来,然后我就加了个忽略证书错误。没啥大变动,只是备份一下

    1. #-*- coding:utf-8 -*-
    2. import re
    3. import requests
    4. import sys
    5. from bs4 import BeautifulSoup, element
    6. from tabulate import tabulate
    7. import wcwidth
    8. import pprint
    9. #将重要信息取出,转化为dict
    10. def info_to_dict(html):
    11. phpinfo_dict = {}
    12. #基本信息
    13. phpinfo_dict["Baseinfo"] = {}
    14. base_info_table = html.select("body > div > table:nth-child(2)")[0] if len(html.select("body > div > table:nth-child(2)")) != 0 else html.select("body > div > table:nth-child(3)")[0]
    15. for tr in base_info_table.find_all("tr"):
    16. key = tr.select("td.e")[0].string.strip()
    17. value = tr.select("td.v")[0].string.strip()
    18. phpinfo_dict["Baseinfo"][key] = value
    19. for h2 in html.find_all("h2"):
    20. module_name = h2.string.strip()
    21. phpinfo_dict[module_name] = {}
    22. #每一个配置模块是从h2标题开始的,向下寻找所有的table标签
    23. #有一个特殊情况PHP Credits,它在h1标签中,其内容是php及其sapi、module等的作者,对脚本功能没有意义,所以不解析
    24. for sibling in h2.next_siblings:
    25. #使用next_siblings会匹配到许多\n \t等,需特殊处理,官方文档明确提到
    26. if sibling.name != "table" and type(sibling) != element.NavigableString and sibling.name != "br":
    27. break
    28. if sibling.name == "table":
    29. for tr in sibling.find_all("tr"):
    30. key_elements = tr.select("td.e")
    31. if len(key_elements) == 0:
    32. continue
    33. key = key_elements[0].string.strip()
    34. value_elements = tr.select("td.v")
    35. if len(value_elements) == 0:
    36. value = ''
    37. elif len(value_elements) == 2:
    38. # 有些配置的value分为Local Value和Master Value
    39. # local value是当前目录的设置,会受.htaccess、.user.ini、代码中ini_set()等的影响
    40. # master value是php.ini中的值
    41. value = [value_elements[0].string.strip(), value_elements[1].string.strip()]
    42. else:
    43. value = "no value" if value_elements[0].string == None else value_elements[0].string.strip()
    44. phpinfo_dict[module_name][key] = value
    45. #windos _SERVER["xx"]
    46. #linux $_SERVER['xx']
    47. #消除这种差异
    48. php_var_dict = {}
    49. if list(phpinfo_dict["PHP Variables"].keys())[0][0] == "_":
    50. for key in phpinfo_dict["PHP Variables"].keys():
    51. new_key = "$" + key.replace('"', "'")
    52. php_var_dict[new_key] = phpinfo_dict["PHP Variables"][key]
    53. phpinfo_dict["PHP Variables"] = php_var_dict
    54. return phpinfo_dict
    55. #获取关键信息
    56. def get_important_info(phpinfo_dict):
    57. result = []
    58. #web目录
    59. result.append(["Web Path", phpinfo_dict["PHP Variables"]["$_SERVER['SCRIPT_FILENAME']"]])
    60. #真实ip
    61. server_ip = phpinfo_dict["PHP Variables"]["$_SERVER['LOCAL_ADDR']"] if "$_SERVER['LOCAL_ADDR']" in phpinfo_dict["PHP Variables"] else phpinfo_dict["PHP Variables"]["$_SERVER['SERVER_ADDR']"]
    62. result.append(["Server IP", server_ip])
    63. #中间件
    64. result.append(["Software", phpinfo_dict["PHP Variables"]["$_SERVER['SERVER_SOFTWARE']"]])
    65. #php版本
    66. phpversion = phpinfo_dict["Core"]["PHP Version"]
    67. result.append(["PHP Version", phpversion])
    68. #系统信息
    69. result.append(["System", phpinfo_dict["Baseinfo"]["System"]])
    70. #SAPI
    71. result.append(["Server API", phpinfo_dict["Baseinfo"]["Server API"]])
    72. #Registered PHP Streams
    73. result.append(["Registered PHP Streams", phpinfo_dict["Baseinfo"]["Registered PHP Streams"]])
    74. #allow_url_include
    75. result.append(["Allow Url Include", ", ".join(phpinfo_dict["Core"]["allow_url_include"])])
    76. #asp_tags
    77. #php7之后已移除
    78. if "asp_tags" in phpinfo_dict["Core"]:
    79. result.append(["Asp Tags", ", ".join(phpinfo_dict["Core"]["asp_tags"])])
    80. #short_open_tag
    81. result.append(["Short Open Tag", ", ".join(phpinfo_dict["Core"]["short_open_tag"])])
    82. #enable_dl
    83. result.append(["Enable Dl", ", ".join(phpinfo_dict["Core"]["enable_dl"])])
    84. #open_basedir
    85. result.append(["Open Basedir", ", ".join(phpinfo_dict["Core"]["open_basedir"])])
    86. #session
    87. ser_handler = ",".join(phpinfo_dict["session"]["session.serialize_handler"])
    88. upload_progress_enabled = ",".join(phpinfo_dict["session"]["session.upload_progress.enabled"])
    89. upload_progress_cleanup = ",".join(phpinfo_dict["session"]["session.upload_progress.cleanup"])
    90. upload_progress_name = ",".join(phpinfo_dict["session"]["session.upload_progress.name"])
    91. session_info = "session.serialize_handler: %s\nsession.upload_progress.enabled: %s\nsession.upload_progress.cleanup: %s\nsession.upload_progress.name: %s" % (ser_handler, upload_progress_enabled, upload_progress_cleanup, upload_progress_name)
    92. result.append(["Session", session_info])
    93. #curl
    94. #if "curl" in phpinfo_dict:
    95. #result.append(["Curl Protocols", phpinfo_dict["curl"]["Protocols"].replace(" ", "")])
    96. #libxml
    97. if "libxml" in phpinfo_dict:
    98. result.append(["Libxml Version", phpinfo_dict["libxml"]["libXML Compiled Version"]])
    99. #disable function
    100. disable_func = phpinfo_dict["Core"]["disable_functions"][0]
    101. result.append(["Disable Function", disable_func.replace(",", "\n")])
    102. #extentions
    103. extentions = ["imagick", "memcache", "redis", "xdebug", "opcache", "imap"]
    104. exist_ext = []
    105. for ext in extentions:
    106. if ext in phpinfo_dict:
    107. exist_ext.append(ext)
    108. intresting_exts = "No Intrestring Ext" if len(exist_ext) == 0 else ", ".join(exist_ext)
    109. result.append(["Extentions", intresting_exts])
    110. return tabulate(result, tablefmt="psql")
    111. #解析获取到的信息,如bypass_disable_function、php版本特性等
    112. def get_parsed_info(phpinfo_dict):
    113. result = []
    114. #php version
    115. suggestion = get_version_feature(phpinfo_dict["Core"]["PHP Version"])
    116. if suggestion:
    117. result.append([suggestion])
    118. #sapi
    119. sapi = phpinfo_dict["Baseinfo"]["Server API"]
    120. if "FPM" in sapi:
    121. result.append(["SAPI为fpm,可能存在未授权访问漏洞"])
    122. #phar
    123. if "phar" in phpinfo_dict["Baseinfo"]["Registered PHP Streams"]:
    124. result.append(["支持phar协议,可扩展反序列化攻击面"])
    125. #ssrf curl php_wrapper
    126. protocols = ["gopher", "dict"]
    127. available_protocols = []
    128. if "curl" in phpinfo_dict:
    129. for protocol in protocols:
    130. if protocol in phpinfo_dict["curl"]["Protocols"]:
    131. available_protocols.append(protocol)
    132. result.append(["libcurl支持%s协议" % (", ".join(available_protocols))])
    133. #libxml版本
    134. if "libxml" in phpinfo_dict and phpinfo_dict["libxml"]["libXML Compiled Version"] < "2.9":
    135. result.append(["libxml版本 < 2.9 xxe可利用"])
    136. #session upload progress
    137. if phpinfo_dict["session"]["session.upload_progress.enabled"][0] == "On":
    138. suggestion = "可利用session.upload_progress上传临时文件然后包含"
    139. if phpinfo_dict["session"]["session.upload_progress.cleanup"][0] == "On":
    140. suggestion += "\n临时文件会立刻删除,需用条件竞争getshell"
    141. result.append([suggestion])
    142. #session ser handler
    143. if phpinfo_dict["session"]["session.serialize_handler"][0] != phpinfo_dict["session"]["session.serialize_handler"][1]:
    144. result.append(["ser handler不一致,存在反序列化风险"])
    145. #imagick
    146. if "imagick" in phpinfo_dict:
    147. result.append(["可利用imagick相关漏洞"])
    148. #xdebug
    149. if "xdebug" in phpinfo_dict and phpinfo_dict["xdebug"]["xdebug.remote_connect_back"][0] == "On" and phpinfo_dict["xdebug"]["xdebug.remote_enable"][0] == "On":
    150. result.append(["存在xdebug rce https://github.com/vulhub/vulhub/tree/master/php/xdebug-rce\nxdebug idekey: " + phpinfo_dict["xdebug"]["xdebug.idekey"][0]])
    151. #opcache
    152. if "opcache" in phpinfo_dict:
    153. result.append(["可上传opcache覆盖源文件"])
    154. #imap
    155. if "imap" in phpinfo_dict:
    156. result.append(["可能存在imap rce https://github.com/vulhub/vulhub/blob/master/php/CVE-2018-19518/README.md"])
    157. #disable function
    158. if phpinfo_dict["Core"]["disable_functions"][0] != "no value":
    159. result.append([bypass_disable_function(phpinfo_dict["Core"]["disable_functions"][0], phpinfo_dict)])
    160. return tabulate(result, tablefmt="grid")
    161. #根据版本获取版本特性
    162. def get_version_feature(version):
    163. suggestion = ""
    164. if "7.2" in version:
    165. suggestion = "php 7.2: assert从函数变为语法结构,无法动态调用; 移除create_function"
    166. if "7.0" in version:
    167. suggestion = "php 7.0: 移除dl; 不再支持asp_tag、<script language=\"php\">"
    168. return suggestion
    169. #如果存在disable_function,寻找可能的bypass
    170. def bypass_disable_function(disable_func, phpinfo_dict):
    171. disable_func = disable_func.split(",")
    172. suggestion = ""
    173. bypass_func = []
    174. if "dl" not in disable_func and phpinfo_dict["Core"]["enable_dl"] == "On":
    175. bypass_func.append("dl")
    176. if "pcntl_exec" not in disable_func and "--enable-pcntl" in phpinfo_dict["Baseinfo"]["Configure Command"]:
    177. bypass_func.append("pcntl_exec")
    178. common_funcs = ['exec', 'system', 'passthru', 'popen', 'proc_open', 'shell_exec']
    179. for func in common_funcs:
    180. if func not in disable_func:
    181. bypass_func.append(func)
    182. suggestion += "可用函数:" + ", ".join(bypass_func) + "\n"
    183. if "Linux" in phpinfo_dict["Baseinfo"]["System"] and "putenv" not in disable_func and "mail" not in disable_func:
    184. suggestion += "使用LD_PRELOAD https://github.com/yangyangwithgnu/bypass_disablefunc_via_LD_PRELOAD\n"
    185. if "imap" in phpinfo_dict:
    186. suggestion += "使用imap https://github.com/vulhub/vulhub/blob/master/php/CVE-2018-19518/README.md\n"
    187. if "imagemagick" in phpinfo_dict:
    188. suggestion += "使用 ImageMagick\n"
    189. suggestion += "disable function bypass合集 https://github.com/l3m0n/Bypass_Disable_functions_Shell"
    190. return suggestion
    191. def parse_phpinfo(url):
    192. r = requests.get(url,verify=False)
    193. #print(r.text)
    194. html = BeautifulSoup(r.text, "lxml")
    195. phpinfo_dict = info_to_dict(html)
    196. #pprint.pprint(phpinfo_dict)
    197. print(get_important_info(phpinfo_dict))
    198. print(get_parsed_info(phpinfo_dict))
    199. if __name__ == "__main__":
    200. url = sys.argv[1]
    201. parse_phpinfo(url)

    requirements.txt文件内容

    requests
    bs4
    tabulate
    wcwidth