Files
yakpanel-core/class_v2/projectModelV2/wordpress_scan.py

278 lines
10 KiB
Python
Raw Normal View History

2026-04-07 02:04:22 +05:30
# coding: utf-8
# +-------------------------------------------------------------------
# | YakPanel
# +-------------------------------------------------------------------
# | Copyright (c) 2015-2099 YakPanel(www.yakpanel.com) All rights reserved.
# +-------------------------------------------------------------------
# | Wordpress 安全扫描
# +--------------------------------------------------------------------
import re
import os
#进入到
from projectModelV2 import totle_db
class wordpress_scan:
#默认插件的头部信息
plugin_default_headers = {
"Name": "Plugin Name",
"PluginURI": "Plugin URI",
"Version": "Version",
"Description": "Description",
"Author": "Author",
"AuthorURI": "Author URI",
"TextDomain": "Text Domain",
"DomainPath": "Domain Path",
"Network": "Network",
"RequiresWP": "Requires at least",
"RequiresPHP": "Requires PHP",
"UpdateURI": "Update URI",
"RequiresPlugins": "Requires Plugins",
"_sitewide": "Site Wide Only"
}
#默认主题的头部信息
theme_default_headers = {
"Name": "Theme Name",
"Title": "Theme Name",
"Version": "Version",
"Author": "Author",
"AuthorURI": "Author URI",
"UpdateURI": "Update URI",
"Template": "Theme Name",
"Stylesheet": "Theme Name",
}
def M(self, table):
'''
@name 获取数据库对象
@param table 表名
@param db 数据库名
'''
with totle_db.Sql().dbfile("../wordpress") as sql:
return sql.table(table)
def get_plugin_data(self, plugin_file, default_headers, context=''):
'''
@参考/wp-admin/includes/plugin.php get_plugin_data 代码
@name 获取插件信息
@param plugin_file 插件文件
@return dict
@auther lkq
@time 2024-10-08
'''
# 读取文件内容
if not os.path.exists(plugin_file): return {}
# 定义8KB大小
max_length = 8 * 1024 # 8 KB
try:
# 读取文件的前8KB
with open(plugin_file, 'r', encoding='utf-8') as file:
file_data = file.read(max_length)
except Exception as e:
return {}
# 替换CR为LF
file_data = file_data.replace('\r', '\n')
# 处理额外的headers
extra_headers = {}
if context:
extra_context_headers = []
# 假设有一个函数可以获取额外的headers
# extra_context_headers = get_extra_headers(context)
extra_headers = dict.fromkeys(extra_context_headers, '') # 假设额外的headers
all_headers = {**extra_headers, **default_headers}
# 检索所有headers
for field, regex in all_headers.items():
if field.startswith('_'): # 跳过以_开头的内部字段
continue
match = re.search(f'{regex}:(.*)$', file_data, re.IGNORECASE | re.MULTILINE)
if match:
all_headers[field] = match.group(1).strip()
else:
all_headers[field] = ''
if all_headers.get("Network") and not all_headers['Network'] and all_headers['_sitewide']:
all_headers['Network'] = all_headers['_sitewide']
if all_headers.get("Network"):
all_headers['Network'] = 'true' == all_headers['Network'].lower()
if all_headers.get("_sitewide"):
del all_headers['_sitewide']
if all_headers.get("TextDomain") and not all_headers['TextDomain']:
plugin_slug = os.path.dirname(os.path.basename(plugin_file))
if '.' != plugin_slug and '/' not in plugin_slug:
all_headers['TextDomain'] = plugin_slug
all_headers['Title'] = all_headers['Name']
all_headers['AuthorName'] = all_headers['Author']
# 返回插件的信息
return all_headers
def Md5(self,strings):
"""
@name 生成MD5
@author hwliang <hwliang@yakpanel.com>
@param strings 要被处理的字符串
@return string(32)
"""
if type(strings) != bytes:
strings = strings.encode()
import hashlib
m = hashlib.md5()
m.update(strings)
return m.hexdigest()
def FileMd5(self,filename):
"""
@name 生成文件的MD5
@author hwliang <hwliang@yakpanel.com>
@param filename 文件名
@return string(32) or False
"""
if not os.path.isfile(filename): return False
import hashlib
my_hash = hashlib.md5()
f = open(filename, 'rb')
while True:
b = f.read(8096)
if not b:
break
my_hash.update(b)
f.close()
return my_hash.hexdigest()
def get_plugin(self, path,one=''):
'''
@name 获取WordPress插件信息
@param path 插件路径
@return dict
@auther lkq
@time 2024-10-08
'''
plugin_path = path + "/wp-content/plugins"
if not os.path.exists(plugin_path): return {}
tmp_list = []
for file in os.listdir(plugin_path):
if one:
if file!=one:continue
plugin_file = os.path.join(plugin_path, file)
# if os.path.isfile(plugin_file) and plugin_file.endswith(".php"):
# tmp_list.append(file)
if os.path.isdir(plugin_file):
# 读取文件夹中的第一层文件
for file2 in os.listdir(plugin_file):
plugin_file2 = os.path.join(plugin_file, file2)
if os.path.isfile(plugin_file2) and plugin_file2.endswith(".php"): tmp_list.append(
file + "/" + file2)
if len(tmp_list) == 0: return {}
result = {}
for i in tmp_list:
plugin_file = plugin_path + "/" + i
# 判断文件是否可读
if not os.access(plugin_file, os.R_OK): continue
plugin_data = self.get_plugin_data(plugin_file, self.plugin_default_headers)
if not plugin_data: continue
if plugin_data["Name"] == "": continue
#如果 name 中没/ 的话
if "/" not in i:
#则判断一下
if 'wordpress.org/plugins/' in plugin_data["PluginURI"]:
plugin_data["PluginURI"] = plugin_data["PluginURI"].replace('http://wordpress.org/plugins/', '').replace("http://wordpress.org/plugins/","")
#去掉最后的/
if plugin_data["PluginURI"][-1]=="/":
plugin_data["PluginURI"]=plugin_data["PluginURI"][:-1]
i=plugin_data["PluginURI"]
else:
continue
result[i] = plugin_data
return result
def compare_versions(self,version1, version2):
'''
@name 对比版本号
@param version1 版本1
@param version2 版本2
@return int 0 相等 1 大于 -1 小于
'''
# 分割版本号为整数列表
v1 = [int(num) for num in version1.split('.')]
v2 = [int(num) for num in version2.split('.')]
# 逐个比较版本号的每个部分
for num1, num2 in zip(v1, v2):
if num1 > num2:
return 1 # version1 > version2
elif num1 < num2:
return -1 # version1 < version2
# 如果所有部分都相同,比较长度(处理像'1.0'和'1.0.0'这样的情况)
if len(v1) > len(v2):
return 1 if any(num > 0 for num in v1[len(v2):]) else 0
elif len(v1) < len(v2):
return -1 if any(num > 0 for num in v2[len(v1):]) else 0
# 如果完全相同
return 0
def let_identify(self,version,vlun_infos):
'''
@name 对比版本号判断是否存在漏洞
@param version 当前版本
@param vlun_infos 漏洞信息
@return list
'''
for i in vlun_infos:
i["vlun_status"] = False
#如果是小于等于的话
if i["let"]=="<=":
if self.compare_versions(version,i["vlun_version"])<=0:
i["vlun_status"]=True
#小于
if i["let"]=="<":
if self.compare_versions(version,i["vlun_version"])<0:
i["vlun_status"]=True
if i['let']=='-':
#从某个版本开始、到某个版本结束
version_list=i["vlun_version"].split("-")
if len(version_list)!=2:continue
if self.compare_versions(version,version_list[0])>=0 and self.compare_versions(version,version_list[1])<=0:
i["vlun_status"]=True
return vlun_infos
def scan(self,path):
'''
@name 扫描WordPress
@param path WordPress路径
@return dict
@auther lkq
@time 2024-10-10
@msg 通过扫描WordPress的版本插件主题来判断是否存在漏洞
'''
vlun_list = []
#判断文件是否存在
import os
if not os.path.exists(path):
return vlun_list
result = {}
result["plugins"] = self.get_plugin(path)
#扫描插件是否存在漏洞
for i in result["plugins"]:
plguin=i.split("/")[0]
Name=result["plugins"][i]["Name"]
if result["plugins"][i]["Version"]=="":continue
#检查插件是否存在漏洞
if self.M("vulnerabilities").where("plugin=?",(plguin,)).count()>0:
vlun_infos=self.M("vulnerabilities").where("plugin=?",(plguin)).select()
vlun_infos=self.let_identify(result["plugins"][i]["Version"],vlun_infos)
for j2 in vlun_infos:
if j2["vlun_status"]:
vlun = {"name": "", "vlun_info": "", "css": "", "type": "plugin", "load_version": "","cve": "","time":""}
vlun["load_version"]=result["plugins"][i]["Version"]
vlun["cve"]=j2["cve"]
vlun["slug"]=plguin
vlun["name"] = Name
vlun["vlun_info"]=j2["msg"]
vlun["css"]=j2["css"]
vlun["time"] = j2["time"]
vlun_list.append(vlun)
return vlun_list