975 lines
33 KiB
Python
975 lines
33 KiB
Python
import re
|
||
import os
|
||
import json
|
||
import shutil
|
||
import time
|
||
from pathlib import Path
|
||
import zipfile
|
||
import nmap
|
||
import subprocess
|
||
import datetime
|
||
import platform
|
||
from collections import defaultdict
|
||
from urllib.parse import unquote
|
||
|
||
import requests
|
||
|
||
|
||
def test_func():
|
||
print('这里是test_func')
|
||
|
||
|
||
"""
|
||
通用部分
|
||
"""
|
||
|
||
|
||
def normal_pass_init(self):
|
||
pass
|
||
|
||
|
||
"""
|
||
文件操作
|
||
---------------------------------------------------------------------------------------------------------------------
|
||
"""
|
||
|
||
|
||
@staticmethod
|
||
def read_file(path, use_json=False):
|
||
with open(path) as f:
|
||
if use_json:
|
||
return json.load(f)
|
||
else:
|
||
return f.read()
|
||
|
||
|
||
@staticmethod
|
||
def write_file(path, content, use_json=False):
|
||
with open(path, 'w') as f:
|
||
if use_json:
|
||
json.dump(content, f)
|
||
else:
|
||
f.write(content)
|
||
|
||
|
||
@staticmethod
|
||
def create_folder(path):
|
||
path = Path(path)
|
||
if not path.exists():
|
||
path.mkdir(parents=True, exist_ok=True)
|
||
return f"文件夹已创建: {path}"
|
||
else:
|
||
return f"文件夹已创建: {path}"
|
||
|
||
|
||
@staticmethod
|
||
def copy_folder(src, dst):
|
||
"""
|
||
复制文件夹到指定位置
|
||
|
||
:param src: 源文件夹路径
|
||
:param dst: 目标文件夹路径
|
||
"""
|
||
try:
|
||
# 如果目标文件夹已存在,先删除
|
||
if not os.path.exists(dst):
|
||
# 复制文件夹
|
||
shutil.copytree(src, dst)
|
||
return f"文件夹已成功复制到: {dst}"
|
||
else:
|
||
return f"文件夹{dst}已经存在,跳过"
|
||
except Exception as e:
|
||
raise e
|
||
|
||
|
||
@staticmethod
|
||
def copy_all_files(src_folder, dst_folder):
|
||
"""
|
||
复制源文件夹下的所有文件到目标文件夹(不包含子文件夹)
|
||
|
||
:param src_folder: 源文件夹路径
|
||
:param dst_folder: 目标文件夹路径
|
||
"""
|
||
try:
|
||
# 确保目标文件夹存在
|
||
if not os.path.exists(dst_folder):
|
||
os.makedirs(dst_folder)
|
||
|
||
# 遍历源文件夹中的所有文件
|
||
for filename in os.listdir(src_folder):
|
||
src_file = os.path.join(src_folder, filename)
|
||
dst_file = os.path.join(dst_folder, filename)
|
||
|
||
# 如果是文件,则复制
|
||
if os.path.isfile(src_file):
|
||
if not os.path.exists(dst_file):
|
||
shutil.copy2(src_file, dst_file)
|
||
except Exception as e:
|
||
raise e
|
||
|
||
|
||
@staticmethod
|
||
def delete_all_files_in_folder(folder_path):
|
||
"""
|
||
删除文件夹下的所有文件,但保留文件夹本身
|
||
|
||
:param folder_path: 文件夹路径
|
||
"""
|
||
try:
|
||
# 遍历文件夹中的所有文件
|
||
for filename in os.listdir(folder_path):
|
||
file_path = os.path.join(folder_path, filename)
|
||
# 如果是文件,则删除
|
||
if os.path.isfile(file_path) or os.path.islink(file_path):
|
||
os.unlink(file_path)
|
||
# 如果是子文件夹,则递归删除(可选)
|
||
elif os.path.isdir(file_path):
|
||
shutil.rmtree(file_path)
|
||
except Exception as e:
|
||
raise e
|
||
|
||
|
||
@staticmethod
|
||
def handle_uploaded_zip(zip_file, extract_to):
|
||
"""
|
||
处理上传的 ZIP 文件并解压到指定目录
|
||
|
||
参数:
|
||
zip_file: Django 的 UploadedFile 对象
|
||
extract_to: 解压目标路径
|
||
|
||
返回:
|
||
解压后的文件列表
|
||
"""
|
||
try:
|
||
# 确保目标目录存在
|
||
os.makedirs(extract_to, exist_ok=True)
|
||
|
||
# 使用 zipfile 解压
|
||
with zipfile.ZipFile(zip_file, 'r') as zip_ref:
|
||
# 获取 ZIP 内所有文件名
|
||
file_list = zip_ref.namelist()
|
||
|
||
# 解压所有文件到目标目录
|
||
zip_ref.extractall(extract_to)
|
||
|
||
# 返回解压后的文件绝对路径列表
|
||
return [os.path.join(extract_to, f) for f in file_list]
|
||
|
||
except zipfile.BadZipFile:
|
||
raise ValueError("上传的文件不是有效的 ZIP 文件")
|
||
except Exception as e:
|
||
raise RuntimeError(f"解压文件时出错: {str(e)}")
|
||
|
||
|
||
"""
|
||
前端操作
|
||
---------------------------------------------------------------------------------------------------------------------
|
||
"""
|
||
|
||
|
||
@staticmethod
|
||
def vuetojson(**kwargs):
|
||
patten = r'<[^>]*\bv-if="[^"]*".*?>|<[^>]+>'
|
||
html_content = kwargs["fileContent"]
|
||
all_tags = re.finditer(patten, html_content)
|
||
last_end = 0
|
||
i = 0
|
||
html_dict_list = []
|
||
for item in all_tags:
|
||
html_dict = {
|
||
"name": "",
|
||
"name_cn": "",
|
||
"include_tag_attribute": [],
|
||
"include_tag_visibleword": [],
|
||
"file": kwargs["file_path"],
|
||
"type": "",
|
||
}
|
||
i = i + 1
|
||
start = item.start()
|
||
# 提取可视化文字
|
||
if last_end == 0:
|
||
last_end = item.end()
|
||
else:
|
||
middle_content = html_content[last_end:start]
|
||
# 替换掉为空的行
|
||
middle_content = re.sub("\s", "", middle_content)
|
||
if middle_content != "":
|
||
html_dict["include_tag_visibleword"] = [{"name": middle_content, "name_cn": middle_content}]
|
||
last_end = item.end()
|
||
# 判断是否单标签
|
||
judge_single_html_tag = r'/>'
|
||
is_single_html_tag = re.search(judge_single_html_tag, html_content[item.start():item.end()])
|
||
if is_single_html_tag is None:
|
||
html_dict["type"] = "normalsgin"
|
||
else:
|
||
html_dict["type"] = "singlesgin"
|
||
# 去掉空格等准备提取属性和标签名
|
||
expel_multiply_space_ = r'\s+'
|
||
new_html_content = re.sub('/>', ">", html_content[item.start():item.end()])
|
||
expel_multiply_space = re.sub(expel_multiply_space_, " ", new_html_content)
|
||
expel_newline_ = r'\n'
|
||
expel_newline = re.sub(expel_newline_, "", expel_multiply_space)
|
||
# 提取标签名称
|
||
expel_jian_ = r'<|>(?=(?:[^"]*"[^"]*")*[^"]*$)'
|
||
expel_jian = re.sub(expel_jian_, '', expel_newline)
|
||
if " " in expel_jian:
|
||
split_expel_jian = re.search(r'\s', expel_jian)
|
||
html_dict["name"] = expel_jian[:split_expel_jian.start()]
|
||
html_dict["name_cn"] = expel_jian[:split_expel_jian.start()]
|
||
# 确认标签中的属性
|
||
attrs_str = expel_jian[split_expel_jian.end():]
|
||
# 匹配并去除当前属性句子中无等号属性
|
||
single_attrs = re.finditer(r'[A-Za-z-]+(?=\s)(?![^"]*"(?=\s))|(?<=\s)([A-Za-z-]+)(?![^"]*")', attrs_str)
|
||
for single_attr in single_attrs:
|
||
html_dict["include_tag_attribute"].append(
|
||
{"name": single_attr.group(), "name_cn": single_attr.group(),
|
||
"attribute_value": [{"name": single_attr.group(), "name_cn": single_attr.group()}]})
|
||
# 剔除特殊属性
|
||
expel_single_attr_attrs_str = re.sub('[A-Za-z-]+\s(?![^"]*"(?=\s))|\s([A-Za-z-]+)(?![^"]*")', '',
|
||
attrs_str)
|
||
if len(expel_single_attr_attrs_str) != 0:
|
||
split_attr = re.split(r'(?<=[\"])\s', expel_single_attr_attrs_str)
|
||
for attr_item in split_attr:
|
||
# key_value_list = re.split(r'=(?=(?:[^"]*"[^"]*")*[^"]*$)', attr_item)
|
||
key_value_list = re.split(r'(?<=[A-Za-z])=(?=")', attr_item)
|
||
if len(key_value_list) == 2:
|
||
html_dict["include_tag_attribute"].append(
|
||
{"name": key_value_list[0], "name_cn": key_value_list[0],
|
||
"attribute_value": [{"name": key_value_list[1], "name_cn": key_value_list[1]}]})
|
||
else:
|
||
html_dict["name"] = expel_jian
|
||
html_dict["name_cn"] = expel_jian
|
||
html_dict_list.append(html_dict)
|
||
if kwargs["is_save_to_file"]:
|
||
jsonname = kwargs["projectpath"]
|
||
jsonanmesearch = re.search('\/', kwargs["projectpath"])
|
||
if not jsonanmesearch == None:
|
||
jsonname = kwargs["projectpath"][jsonanmesearch.end():]
|
||
file_name = f'{jsonname}.json'
|
||
jsonpath = os.path.join(kwargs['temp_folder'] + file_name)
|
||
with open(jsonpath, 'w') as file_object:
|
||
json.dump(html_dict_list, file_object)
|
||
return html_dict_list
|
||
|
||
|
||
@staticmethod
|
||
def jsontovue(**kwargs):
|
||
json_data = kwargs["json_data"]
|
||
text = ""
|
||
for item in json_data:
|
||
# 重构标签
|
||
html_tag = "<" + item["name"]
|
||
if item["type"] == "normalsgin":
|
||
if not "/" in item["name"]:
|
||
for value in item["include_tag_attribute"]:
|
||
if value["name"] == value["attribute_value"][0]["name"]:
|
||
html_tag = html_tag + " " + value["name"]
|
||
else:
|
||
html_tag = html_tag + " " + value["name"] + '=' + value["attribute_value"][0]["name"]
|
||
html_tag = html_tag + ">" + "\n"
|
||
else:
|
||
html_tag = html_tag + ">" + "\n"
|
||
else:
|
||
for value in item["include_tag_attribute"]:
|
||
if value["name"] == value["attribute_value"][0]["name"]:
|
||
html_tag = html_tag + " " + value["name"]
|
||
else:
|
||
html_tag = html_tag + " " + value["name"] + '=' + value["attribute_value"][0]["name"]
|
||
html_tag = html_tag + "/>" + "\n"
|
||
if not item["name"] == "":
|
||
if not len(item["include_tag_visibleword"]) == 0:
|
||
text = text + item["include_tag_visibleword"][0]["name"]
|
||
text = text + html_tag
|
||
else:
|
||
text = text + html_tag
|
||
return text
|
||
|
||
|
||
def read_interaction_data_tree(self, item, container=None):
|
||
return_data = {}
|
||
for child in item:
|
||
if child['data_type'] == 'list':
|
||
container = []
|
||
if len(child["children"]) > 0:
|
||
res = self.read_interaction_data_tree(child["children"], container)
|
||
add_list = []
|
||
add_list.append(res)
|
||
return_data.update({child["key"]: add_list})
|
||
elif child["value_type"] == 'variable':
|
||
return_data.update({child['key']: child['value']})
|
||
elif child['data_type'] == 'dict':
|
||
container = {}
|
||
if len(child["children"]) > 0:
|
||
res = self.read_interaction_data_tree(child["children"], container)
|
||
return_data.update({child['key']: res})
|
||
elif child["value_type"] == 'variable':
|
||
return_data.update({child['key']: child['value']})
|
||
else:
|
||
return_data.update({child['key']: child['value']})
|
||
return return_data
|
||
|
||
|
||
"""
|
||
API部分
|
||
---------------------------------------------------------------------------------------------------------------------
|
||
"""
|
||
|
||
|
||
def group_tree_data(self, *args, **kwargs):
|
||
deepest = []
|
||
# 分组,确定序号
|
||
group = {}
|
||
for syntax in kwargs["code_list"]:
|
||
# 处理字符串
|
||
# 提取代码
|
||
pattern = r'(\t{4}|\s{4})'
|
||
matches = re.findall(pattern, syntax)
|
||
# 确定深度
|
||
indentation = len(matches) + 1
|
||
# 确定总深度进行分组
|
||
deepest.append(indentation)
|
||
# 去除重复的层级
|
||
deepest = set(deepest)
|
||
# 确认分组数量
|
||
for deep in deepest:
|
||
group.update({f"group{deep}": []})
|
||
# 输出详细分组信息
|
||
for deep in deepest:
|
||
serial_number = 0
|
||
for i, syntax in enumerate(kwargs["code_list"]):
|
||
pattern = r'(\t{4}|\s{4})'
|
||
matches = re.findall(pattern, syntax)
|
||
# 确定深度
|
||
indentation = len(matches) + 1
|
||
syntax = re.sub(pattern, "", syntax)
|
||
|
||
lines_data = {
|
||
"syntax_statement": syntax, # 代码
|
||
"syntax_indentation": indentation * 4, # python代码缩进
|
||
}
|
||
|
||
if not syntax:
|
||
continue # Skip empty lines
|
||
|
||
# 生成数据后进行分组
|
||
if indentation == deep:
|
||
node_info = {
|
||
"lines_data": lines_data, # 内容
|
||
"line_number": i, # 行号
|
||
"indentation": indentation, # 深度
|
||
"serial_number": serial_number # 序号
|
||
}
|
||
# 上层问题
|
||
group[f"group{indentation}"].append(node_info)
|
||
serial_number = serial_number + 1
|
||
return group
|
||
|
||
|
||
def api_code_transform_to_tree(self, *args, **kwargs):
|
||
# 进行代码树的初始化
|
||
root = kwargs["PythonCodeTreeNode"]({
|
||
"syntax_statement": "Root", # 代码
|
||
"syntax_indentation": 0, # python代码缩进
|
||
}, indentation=0, serial_number=0)
|
||
parent = {}
|
||
for group_key, group_value in kwargs["group_data"].items():
|
||
for node in group_value:
|
||
min_difference = float('inf')
|
||
if f"group{node['indentation'] - 1}" in kwargs["group_data"]:
|
||
for item in kwargs["group_data"][f"group{node['indentation'] - 1}"]:
|
||
difference = node["line_number"] - item['line_number']
|
||
if 1 <= difference < min_difference:
|
||
min_difference = difference
|
||
parent = {
|
||
"depth": item["indentation"],
|
||
"serial_number": item["serial_number"],
|
||
}
|
||
node.update({"test": parent})
|
||
# 至此完整树数据整理完成,进入结构体
|
||
for item in kwargs["group_data"].values():
|
||
for ss in item:
|
||
if ss["indentation"] == 1:
|
||
node = kwargs["PythonCodeTreeNode"](ss["lines_data"], parent=root, indentation=ss["indentation"],
|
||
serial_number=ss["serial_number"])
|
||
else:
|
||
parent = kwargs["PythonCodeTreeController"].find_data(root, ss["test"]["depth"],
|
||
ss["test"]["serial_number"])
|
||
node = kwargs["PythonCodeTreeNode"](ss["lines_data"], parent=parent, indentation=ss["indentation"],
|
||
serial_number=ss["serial_number"])
|
||
kwargs["PythonCodeTreeController"].add_data(node)
|
||
return kwargs["PythonCodeTreeController"].to_array(root)
|
||
|
||
|
||
def api_transform_to_json(self, *args, **kwargs):
|
||
code_string = kwargs["code_string"]
|
||
code_string = re.sub(r'\s+#.*$', "", code_string, flags=re.M)
|
||
code_string = re.sub(r'\'\'\'(.*?)\'\'\'|"""(.*?)"""', "", code_string, flags=re.M and re.S)
|
||
code_string = re.sub(r'\n\s*\n', '\n', code_string)
|
||
filter_str = r',\n\s+|\{\n\s+|\",\n\s+|\n\s+\"|\n\s+\}|\[\n\s+|\n\s+\]|\}\n\}\)|\(\n\s+[A-z0-9]|[A-z0-9]\n\}|({)'
|
||
filter_code_list = re.finditer(filter_str, code_string)
|
||
offset = 0
|
||
for ss in filter_code_list:
|
||
start_index = ss.start() - offset
|
||
end_index = ss.end() - offset
|
||
match_pattern = r'\n|\s+'
|
||
new_code_string = code_string[:start_index] + re.sub(match_pattern, "",
|
||
code_string[start_index:end_index]) + code_string[
|
||
end_index:]
|
||
offset += len(code_string) - len(new_code_string)
|
||
code_string = new_code_string
|
||
# try:
|
||
# exec(code_string)
|
||
# except IndentationError:
|
||
# return "缩进错误"
|
||
# 形成配置文件
|
||
code_setting = code_string.split("\n")
|
||
progress_data = self.group_tree_data(code_list=code_setting)
|
||
syntax_config = self.api_code_transform_to_tree(group_data=progress_data,
|
||
PythonCodeTreeNode=kwargs['PythonCodeTreeNode'],
|
||
PythonCodeTreeController=kwargs['PythonCodeTreeController'])
|
||
return syntax_config
|
||
|
||
|
||
"""
|
||
python树操作
|
||
---------------------------------------------------------------------------------------------------------------------
|
||
"""
|
||
|
||
|
||
def py_tree_node_init(self, data, parent=None, last_node=None, indentation=0, serial_number=0, extent_data=None):
|
||
if extent_data is None:
|
||
extent_data = {}
|
||
self.data = data
|
||
self.parent = parent
|
||
self.children = []
|
||
self.indentation = indentation
|
||
self.extent_data = extent_data
|
||
self.serial_number = serial_number
|
||
self.last_node = last_node
|
||
|
||
|
||
# 增
|
||
@classmethod
|
||
def add_data(cls, node):
|
||
node.parent.children.append(node)
|
||
|
||
|
||
# 删
|
||
@classmethod
|
||
def del_data(cls):
|
||
pass
|
||
|
||
|
||
# 改
|
||
@classmethod
|
||
def edit_data(cls):
|
||
pass
|
||
|
||
|
||
# 查
|
||
@classmethod
|
||
def find_data(cls, root_node, i, s):
|
||
if root_node.indentation == i and root_node.serial_number == s:
|
||
return root_node
|
||
for child in root_node.children:
|
||
result = cls.find_data(child, i, s)
|
||
if result:
|
||
return result
|
||
return None
|
||
|
||
|
||
@classmethod
|
||
def find_data_by_path(cls, root_node, path, depth=0):
|
||
if root_node is None:
|
||
return None
|
||
|
||
components = path.split('/')
|
||
current_node = root_node
|
||
for component in components:
|
||
if not component: # 跳过空的组件(例如,路径开头为 '/')
|
||
continue
|
||
|
||
found = False
|
||
for child in current_node.children:
|
||
if child.data["name"] == component:
|
||
current_node = child
|
||
found = True
|
||
break
|
||
|
||
if not found:
|
||
return None # 如果未找到路径中的某个组件,返回None
|
||
|
||
return current_node
|
||
|
||
|
||
# 输出
|
||
@classmethod
|
||
def to_array(cls, node):
|
||
result = {}
|
||
if not isinstance(node, str):
|
||
result = node.data.copy()
|
||
result["indentation"] = node.indentation
|
||
result["serial_number"] = node.serial_number
|
||
result["children"] = [cls.to_array(child) for child in node.children]
|
||
return result
|
||
|
||
|
||
# 输出
|
||
@classmethod
|
||
def to_array2(cls, node):
|
||
result = {}
|
||
if not isinstance(node, str):
|
||
result = node.data.copy()
|
||
result["indentation"] = node.indentation
|
||
result["serial_number"] = node.serial_number
|
||
return result
|
||
|
||
|
||
@classmethod
|
||
def to_string(cls, code_json):
|
||
code_string = ""
|
||
# 输出当前节点的值
|
||
if code_json['syntax_statement'] != 'Root':
|
||
code_string += " " * (code_json["syntax_indentation"] - 4) + code_json['syntax_statement'] + "\n"
|
||
|
||
# 递归遍历子节点
|
||
for child in code_json['children']:
|
||
code_string += cls.to_string(child)
|
||
|
||
return code_string
|
||
|
||
|
||
"""
|
||
信息安全模块
|
||
"""
|
||
|
||
|
||
@staticmethod
|
||
def enhanced_service_scan(target, ports):
|
||
nm = nmap.PortScanner()
|
||
return_dict = {}
|
||
try:
|
||
# 使用增强版扫描参数
|
||
scan_args = '''
|
||
-sV
|
||
--script=mysql-info,ssh-hostkey,http-headers
|
||
--script-args=ssh-hostkey=all
|
||
'''
|
||
nm.scan(hosts=target, ports=ports, arguments=scan_args)
|
||
|
||
for host in nm.all_hosts():
|
||
return_dict.update({host: host})
|
||
for proto in nm[host].all_protocols():
|
||
return_dict.update({"Protocol": proto})
|
||
return_dict.update(
|
||
{"columns": [{"label": "端口", "prop": "port"}, {"label": "状态", "prop": "state"},
|
||
{"label": "服务", "prop": "Service"}, {"label": "产品", "prop": "Product"},
|
||
{"label": "版本", "prop": "Version"}]})
|
||
data_list = []
|
||
for port, data in nm[host][proto].items():
|
||
# 处理空版本信息
|
||
product = data.get('product', '').strip() or 'N/A'
|
||
version = data.get('version', '').strip() or 'N/A'
|
||
|
||
data_list.append({
|
||
"port": port,
|
||
"state": data['state'],
|
||
"Service": data['name'],
|
||
"Product": product,
|
||
"Version": version
|
||
})
|
||
return_dict.update({"result": data_list})
|
||
|
||
except nmap.nmap.PortScannerError as e:
|
||
raise e
|
||
except Exception as e:
|
||
raise e
|
||
return return_dict
|
||
|
||
|
||
@staticmethod
|
||
def sqlapi(options):
|
||
header = {
|
||
'Content-Type': 'application/json'
|
||
}
|
||
data = {
|
||
"url": options["url"],
|
||
"cookie": options["cookie"],
|
||
"getCurrentDb": options["getCurrentDb"],
|
||
"getCurrentUser": options["getCurrentUser"],
|
||
"getDbs": options["getDbs"],
|
||
"getTables": options["getTables"],
|
||
"getColumns": options["getColumns"]
|
||
}
|
||
task_new_url = "http://127.0.0.1:8775/task/new"
|
||
res = requests.get(task_new_url)
|
||
taskId = res.json()['taskid']
|
||
if 'success' in res.content.decode('utf-8'):
|
||
task_set_url = 'http://127.0.0.1:8775/option/' + taskId + '/set'
|
||
task_set_res = requests.post(task_set_url, data=json.dumps(data), headers=header)
|
||
if 'success' in task_set_res.content.decode('utf-8'):
|
||
task_start_url = 'http://127.0.0.1:8775/scan/' + taskId + '/start'
|
||
task_start_res = requests.post(task_start_url, data=json.dumps(data), headers=header)
|
||
if ('success' in task_start_res.content.decode('utf-8')):
|
||
while 1:
|
||
task_status_url = 'http://127.0.0.1:8775/scan/' + taskId + '/status'
|
||
time.sleep(5)
|
||
task_status_res = requests.get(task_status_url)
|
||
if ('running' in task_status_res.content.decode('utf-8')):
|
||
pass
|
||
else:
|
||
task_data_url = 'http://127.0.0.1:8775/scan/' + taskId + '/data'
|
||
task_data_res = requests.get(task_data_url)
|
||
return task_data_res.json()
|
||
else:
|
||
return "sql未安装"
|
||
|
||
|
||
"""
|
||
数据库模块
|
||
"""
|
||
|
||
|
||
@staticmethod
|
||
def get_data_from_db(host, user, pwd, name, pack_dir):
|
||
# 生成带时间戳的文件名
|
||
timestamp = datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
|
||
backup_file = os.path.join(pack_dir, f"{name}_{timestamp}.sql")
|
||
try:
|
||
# 使用 mysqldump 命令备份
|
||
command = [
|
||
"mysqldump",
|
||
f"--host={host}",
|
||
f"--user={user}",
|
||
f"--password={pwd}",
|
||
name
|
||
]
|
||
|
||
with open(backup_file, "w") as output_file:
|
||
process = subprocess.Popen(command, stdout=output_file, stderr=subprocess.PIPE)
|
||
_, stderr = process.communicate()
|
||
|
||
if process.returncode != 0:
|
||
raise Exception(f"Backup failed: {stderr.decode()}")
|
||
|
||
print(f"Backup successful! File saved to: {backup_file}")
|
||
except Exception as e:
|
||
raise e
|
||
|
||
|
||
@staticmethod
|
||
def check_modsecurity_config():
|
||
config_paths = [
|
||
"/etc/apache2/mods-enabled/security2.conf", # Debian/Ubuntu 路径
|
||
"/etc/apache2/mods-available/mod-security.conf", # 可能的自定义配置路径
|
||
"/etc/httpd/conf.d/mod_security.conf" # CentOS/RHEL 路径
|
||
]
|
||
|
||
for path in config_paths:
|
||
try:
|
||
with open(path, "r") as f:
|
||
content = f.read()
|
||
# 检查关键配置项(如规则加载)
|
||
if "Include /etc/modsecurity/*.conf" in content or "SecRuleEngine On" in content:
|
||
print(f"[+] ModSecurity 配置文件已加载: {path}")
|
||
return True
|
||
except FileNotFoundError:
|
||
print(f"[-] 配置文件不存在: {path}")
|
||
except Exception as e:
|
||
print(f"读取配置文件失败: {e}")
|
||
|
||
print("[-] 未找到有效的 ModSecurity 配置")
|
||
return False
|
||
|
||
|
||
@staticmethod
|
||
def check_modsecurity_installed():
|
||
try:
|
||
# 执行 Apache 模块列表命令(根据系统调整命令)
|
||
result = subprocess.run(
|
||
["apache2ctl", "-M"], # Debian/Ubuntu 使用 apache2ctl
|
||
# ["httpd", "-M"], # CentOS/RHEL 使用 httpd
|
||
capture_output=True,
|
||
text=True,
|
||
check=True
|
||
)
|
||
# 检查输出中是否包含 mod_security
|
||
if "security2_module" in result.stdout:
|
||
print("[+] ModSecurity 模块已安装")
|
||
return True
|
||
else:
|
||
print("[-] ModSecurity 模块未安装")
|
||
return False
|
||
except subprocess.CalledProcessError as e:
|
||
print(f"检查失败: {e}")
|
||
return False
|
||
|
||
|
||
@staticmethod
|
||
def check_owasp_crs():
|
||
crs_paths = [
|
||
"/etc/modsecurity/crs-setup.conf", # OWASP CRS 配置文件
|
||
"/etc/modsecurity/ors_rules/rules/REQUEST-942-APPLICATION-ATTACK-SQLI.conf" # SQL 注入规则文件
|
||
]
|
||
|
||
for path in crs_paths:
|
||
try:
|
||
with open(path, "r") as f:
|
||
print(f"[+] OWASP CRS 规则文件存在: {path}")
|
||
return True
|
||
except FileNotFoundError:
|
||
print(f"[-] OWASP CRS 规则文件未找到: {path}")
|
||
except Exception as e:
|
||
print(f"读取规则文件失败: {e}")
|
||
|
||
return False
|
||
|
||
|
||
def detect_os_by_file(self):
|
||
"""
|
||
检测操作系统类型(兼容 Python 3.7)
|
||
返回 'debian' 或 'centos',否则抛出异常
|
||
"""
|
||
# 如果是非 Linux 系统直接报错
|
||
if platform.system().lower() != "linux":
|
||
raise NotImplementedError("仅支持 Linux 系统")
|
||
|
||
# 尝试读取 /etc/os-release 文件
|
||
os_release_path = "/etc/os-release"
|
||
if not os.path.exists(os_release_path):
|
||
os_release_path = "/usr/lib/os-release" # 备用路径
|
||
|
||
try:
|
||
with open(os_release_path, "r") as f:
|
||
content = f.read()
|
||
|
||
# 解析键值对(例如 ID=ubuntu)
|
||
os_info = {}
|
||
for line in content.splitlines():
|
||
if "=" in line and not line.startswith("#"):
|
||
key, value = line.split("=", 1)
|
||
os_info[key.strip().lower()] = value.strip().strip('"')
|
||
|
||
os_id = os_info.get("id", "")
|
||
if "debian" in os_id or "ubuntu" in os_id:
|
||
return "debian"
|
||
elif "centos" in os_id or "rhel" in os_id:
|
||
return "centos"
|
||
else:
|
||
raise ValueError(f"不支持的操作系统: {os_id}")
|
||
|
||
except Exception as e:
|
||
raise RuntimeError(f"无法检测操作系统: {str(e)}")
|
||
|
||
|
||
# 系统检测
|
||
def detect_os(self):
|
||
os_info = self.detect_os_by_file()
|
||
if 'ubuntu' in os_info.lower() or 'debian' in os_info.lower():
|
||
return 'debian'
|
||
elif 'centos' in os_info.lower() or 'rhel' in os_info.lower():
|
||
return 'centos'
|
||
else:
|
||
print("[-] 不支持的操作系统")
|
||
|
||
|
||
# 检查并安装 ModSecurity
|
||
def install_modsecurity(self, os_type):
|
||
try:
|
||
if os_type == 'debian':
|
||
subprocess.run(["apt-get", "update"], check=True)
|
||
subprocess.run(["apt-get", "install", "-y", "libapache2-mod-security2"], check=True)
|
||
elif os_type == 'centos':
|
||
subprocess.run(["yum", "install", "-y", "mod_security"], check=True)
|
||
print("[+] ModSecurity 安装成功")
|
||
return True
|
||
except subprocess.CalledProcessError as e:
|
||
print(f"[-] 安装失败: {e}")
|
||
return False
|
||
|
||
|
||
# 配置 ModSecurity
|
||
def configure_modsecurity(self, os_type):
|
||
config_path = ""
|
||
if os_type == 'debian':
|
||
config_path = "/etc/apache2/mods-enabled/security2.conf"
|
||
elif os_type == 'centos':
|
||
config_path = "/etc/httpd/conf.d/mod_security.conf"
|
||
|
||
# 写入基本配置
|
||
config_content = """
|
||
<IfModule mod_security2.c>
|
||
SecRuleEngine On
|
||
SecAuditLog /var/log/modsec_audit.log
|
||
SecDebugLog /var/log/modsec_debug.log
|
||
SecDebugLogLevel 0
|
||
|
||
Include /etc/modsecurity/ors_rules/crs-setup.conf
|
||
Include /etc/modsecurity/ors_rules/rules/*.conf
|
||
</IfModule>
|
||
"""
|
||
try:
|
||
with open(config_path, "w") as f:
|
||
f.write(config_content)
|
||
print(f"[+] 配置文件已写入: {config_path}")
|
||
return True
|
||
except Exception as e:
|
||
print(f"[-] 写入配置失败: {e}")
|
||
return False
|
||
|
||
|
||
# 安装 OWASP CRS 规则集
|
||
def install_owasp_crs(self):
|
||
crs_dir = "/etc/modsecurity/ors_rules"
|
||
crs_repo = "https://github.com/coreruleset/coreruleset"
|
||
try:
|
||
if os.path.exists(crs_dir):
|
||
print(f"检测到已存在的CRS目录 {crs_dir},执行清理...")
|
||
shutil.rmtree(crs_dir)
|
||
print("旧CRS目录已成功删除")
|
||
|
||
# 创建新目录
|
||
os.makedirs(crs_dir, exist_ok=True)
|
||
print(f"创建CRS目录 {crs_dir}")
|
||
subprocess.run(["git", "clone", crs_repo, crs_dir], check=True)
|
||
subprocess.run(["cp", crs_dir + "/crs-setup.conf.example", crs_dir + "/crs-setup.conf"], check=True)
|
||
print("[+] OWASP CRS 规则集安装成功")
|
||
return True
|
||
except Exception as e:
|
||
print(f"[-] 安装 OWASP CRS 失败: {e}")
|
||
return False
|
||
|
||
|
||
def detect_main(self):
|
||
os_type = self.detect_os()
|
||
|
||
# 1. 检查 ModSecurity 是否安装
|
||
try:
|
||
if os_type == 'debian':
|
||
result = subprocess.run(["apache2ctl", "-M"], capture_output=True, text=True)
|
||
else:
|
||
result = subprocess.run(["httpd", "-M"], capture_output=True, text=True)
|
||
|
||
if "security2_module" not in result.stdout:
|
||
print("[-] ModSecurity 未安装,开始安装...")
|
||
if not self.install_modsecurity(os_type):
|
||
raise Exception("[-] ModSecurity 安装失败")
|
||
except FileNotFoundError:
|
||
print("[-] Apache 未安装,请先安装 Apache")
|
||
|
||
# 2. 检查配置文件
|
||
if not self.configure_modsecurity(os_type):
|
||
print("[-] 未配置")
|
||
|
||
# 3. 检查 OWASP CRS
|
||
crs_rule_file = "/etc/modsecurity/rules/REQUEST-942-APPLICATION-ATTACK-SQLI.conf"
|
||
if not os.path.exists(crs_rule_file):
|
||
print("[-] OWASP CRS 未安装,开始安装...")
|
||
if not self.install_owasp_crs():
|
||
print("[-] OWASP CRS 安装失败")
|
||
|
||
# 重启 Apache
|
||
try:
|
||
if os_type == 'debian':
|
||
subprocess.run(["systemctl", "restart", "apache2"], check=True)
|
||
else:
|
||
subprocess.run(["systemctl", "restart", "httpd"], check=True)
|
||
print("[+] Apache 已重启,配置生效")
|
||
return True
|
||
except subprocess.CalledProcessError as e:
|
||
print(f"[-] 重启 Apache 失败: {e}")
|
||
return False
|
||
|
||
|
||
def check_ufw_status(self):
|
||
try:
|
||
result = subprocess.check_output(
|
||
["sudo", "ufw", "status"],
|
||
stderr=subprocess.STDOUT,
|
||
text=True
|
||
)
|
||
return "Status: active" in result
|
||
except subprocess.CalledProcessError:
|
||
return False
|
||
except FileNotFoundError:
|
||
return None # ufw未安装
|
||
|
||
|
||
def get_ufw_rules(self):
|
||
try:
|
||
result = subprocess.check_output(
|
||
["sudo", "ufw", "status", "verbose"],
|
||
stderr=subprocess.STDOUT,
|
||
text=True
|
||
)
|
||
return result
|
||
except subprocess.CalledProcessError as e:
|
||
return f"错误: {e.output}"
|
||
except FileNotFoundError:
|
||
return "ufw 未安装"
|
||
|
||
|
||
def block_ip_port(self, ip, port):
|
||
try:
|
||
cmd = ["sudo", "ufw", "deny", "from", ip, "to", "any", "port", str(port)]
|
||
subprocess.check_call(cmd)
|
||
return True
|
||
except subprocess.CalledProcessError as e:
|
||
print(f"失败: {e}")
|
||
return False
|
||
|
||
|
||
def open_ufw(self):
|
||
try:
|
||
cmd = ["sudo", "ufw", "enable"]
|
||
subprocess.check_call(cmd)
|
||
return True
|
||
except subprocess.CalledProcessError as e:
|
||
print(f"失败: {e}")
|
||
return False
|
||
|
||
|
||
# 精准的Apache组合日志格式正则
|
||
LOG_REGEX = re.compile(
|
||
r'(?P<remote>[\.0-9]+) (?P<identity>[\w\d\-]+) (?P<user>[\w\d\-]+) \[(?P<date>.*?)\] "(?P<method>\w+) (?P<url>.*?) (?P<protocol>[\w\/\.\d]+)" (?P<status>\d+) (?P<bytes>[\d-]+) "(?P<referer>.*?)" "(?P<agent>.*?)"')
|
||
|
||
# 增强的注入检测模式
|
||
INJECTION_PATTERNS = re.compile(
|
||
r"(\s*([\0\b\'\"\n\r\t\%\_\\]*\s*(((select\s*.+\s*from\s*.+)|(insert\s*.+\s*into\s*.+)|(update\s*.+\s*set\s*.+)|(delete\s*.+\s*from\s*.+)|(drop\s*.+)|(truncate\s*.+)|(alter\s*.+)|(exec\s*.+)|(\s*(all|any|not|and|between|in|like|or|some|contains|containsall|containskey)\s*.+[\=\>\<=\!\~]+.+)|(let\s+.+[\=]\s*.*)|(begin\s*.*\s*end)|(\s*[\/\*]+\s*.*\s*[\*\/]+)|(\s*(\-\-)\s*.*\s+)|(\s*(contains|containsall|containskey)\s+.*)))(\s*[\;]\s*)*)+)"
|
||
)
|
||
|
||
|
||
def decode_url(self, encoded_url):
|
||
"""处理多重URL编码"""
|
||
decoded = unquote(encoded_url)
|
||
while '%' in decoded:
|
||
new_decoded = unquote(decoded)
|
||
if new_decoded == decoded:
|
||
break
|
||
decoded = new_decoded
|
||
return decoded.lower()
|
||
|
||
|
||
def analyze_log(self, log_path):
|
||
attack_ips = defaultdict(int)
|
||
|
||
with open(log_path, 'r', errors='ignore') as f:
|
||
for line in f:
|
||
match = LOG_REGEX.match(line.strip())
|
||
if not match:
|
||
continue # 跳过格式错误行
|
||
|
||
# 提取关键字段
|
||
ip = match.group('remote')
|
||
method = match.group('method')
|
||
url = match.group('url')
|
||
|
||
# 解码并拼接检测内容
|
||
check_str = self.decode_url(url)
|
||
|
||
if INJECTION_PATTERNS.search(check_str):
|
||
attack_ips[ip] += 1
|
||
|
||
return sorted(attack_ips.items(), key=lambda x: x[1], reverse=True)
|