Files
VulnDataExtr/gui.py
2025-03-18 11:26:39 +08:00

1104 lines
50 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import tkinter as tk
from tkinter import filedialog, messagebox, ttk
import json
import re
from pathlib import Path
import pandas as pd
from keyword_manager import KeywordManager
from keyword_dialog import KeywordDialog
from tkinterdnd2 import TkinterDnD, DND_FILES
import os
import datetime
from bs4 import BeautifulSoup
from openpyxl import Workbook
from openpyxl.styles import Font, PatternFill, Border, Side, Alignment
from openpyxl.utils import get_column_letter
import time
class JsonExtractorGUI:
def __init__(self, root):
self.root = root
self.root.title("漏扫数据分析工具 v1.0.1 - By Felix")
self.root.geometry("800x600")
# 创建标签页
self.notebook = ttk.Notebook(root)
self.notebook.pack(fill="both", expand=True, padx=5, pady=5)
# 创建漏洞分类标签页
self.vuln_class_frame = ttk.Frame(self.notebook)
self.notebook.add(self.vuln_class_frame, text="漏洞类型分类")
# 创建IP提取标签页
self.ip_extract_frame = ttk.Frame(self.notebook)
self.notebook.add(self.ip_extract_frame, text="漏洞明细表格IP提取")
# 创建漏扫结果提取标签页
self.vuln_export_frame = ttk.Frame(self.notebook)
self.notebook.add(self.vuln_export_frame, text="漏扫结果提取明细至Excel")
# 在漏洞分类标签页中添加组件
# 输入文件框
self.input_frame = tk.LabelFrame(self.vuln_class_frame, text="输入HTML文件", padx=10, pady=5)
self.input_frame.pack(fill="x", padx=10, pady=5)
self.input_path = tk.StringVar()
self.input_entry = tk.Entry(self.input_frame, textvariable=self.input_path, width=80)
self.input_entry.pack(side="left", padx=5)
self.browse_btn = tk.Button(self.input_frame, text="浏览", command=self.browse_input)
self.browse_btn.pack(side="left", padx=5)
# 拖放提示移到输入框右边
self.drop_label = tk.Label(self.input_frame, text="(支持拖放HTML文件)", pady=5)
self.drop_label.pack(side="left", padx=5)
# 输出文件框
self.output_frame = tk.LabelFrame(self.vuln_class_frame, text="输出JSON文件", padx=10, pady=5)
self.output_frame.pack(fill="x", padx=10, pady=5)
self.output_path = tk.StringVar()
self.output_entry = tk.Entry(self.output_frame, textvariable=self.output_path, width=80)
self.output_entry.pack(side="left", padx=5)
self.save_btn = tk.Button(self.output_frame, text="浏览", command=self.browse_output)
self.save_btn.pack(side="left", padx=5)
# 将提取JSON按钮移到输出文件框中
self.extract_btn = tk.Button(self.output_frame, text="提取JSON",
command=self.extract_json)
self.extract_btn.pack(side="left", padx=5)
# 添加关键词管理器
self.keyword_manager = KeywordManager()
# 创建漏洞分类框架
vuln_frame = tk.LabelFrame(self.vuln_class_frame, text="漏洞类型分类", padx=5, pady=5)
vuln_frame.pack(fill="x", padx=10, pady=5)
# 添加匹配模式选择
self.match_mode = tk.StringVar(value="both")
match_label = tk.Label(vuln_frame, text="匹配模式:")
match_label.pack(side="left", padx=5)
modes = [
("精准匹配", "exact"),
("模糊匹配", "fuzzy"),
("精准+模糊", "both")
]
for text, mode in modes:
tk.Radiobutton(vuln_frame, text=text, variable=self.match_mode,
value=mode).pack(side="left", padx=5)
# 添加关键词管理按钮
self.keyword_btn = tk.Button(vuln_frame, text="关键词管理",
command=self.show_keyword_dialog)
self.keyword_btn.pack(side="left", padx=15)
# 添加导出按钮
self.vuln_btn = tk.Button(vuln_frame, text="导出分类",
command=self.export_vuln_types)
self.vuln_btn.pack(side="left", padx=5)
# 日志框
self.log_frame = tk.LabelFrame(self.vuln_class_frame, text="运行日志", padx=10, pady=5)
self.log_frame.pack(fill="both", expand=True, padx=10, pady=5)
# 创建文本框和滚动条
self.log_text = tk.Text(self.log_frame, height=15, width=80)
self.scrollbar = tk.Scrollbar(self.log_frame, orient="vertical", command=self.log_text.yview)
self.log_text.configure(yscrollcommand=self.scrollbar.set)
# 放置文本框和滚动条
self.scrollbar.pack(side="right", fill="y")
self.log_text.pack(side="left", fill="both", expand=True)
# 状态显示
self.status_var = tk.StringVar()
self.status_label = tk.Label(self.vuln_class_frame, textvariable=self.status_var)
self.status_label.pack(pady=5)
# 绑定拖放事件
self.root.drop_target_register(DND_FILES)
self.root.dnd_bind('<<Drop>>', self.handle_drop)
# 初始化IP提取标签页
self.init_ip_extract_tab()
# 初始化漏扫结果提取标签页
self.init_lvmeng_export_tab()
def log_message(self, message, level="INFO"):
"""添加日志消息到日志框"""
timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
self.log_text.insert("end", f"[{timestamp}] [{level}] {message}\n")
self.log_text.see("end") # 自动滚动到最新消息
def browse_input(self):
filename = filedialog.askopenfilename(
filetypes=[("HTML文件", "*.html"), ("所有文件", "*.*")]
)
if filename:
self.input_path.set(filename)
# 自动设置输出文件名
output_path = Path(filename).with_suffix('.json')
self.output_path.set(str(output_path))
def browse_output(self):
filename = filedialog.asksaveasfilename(
defaultextension=".json",
filetypes=[("JSON文件", "*.json"), ("所有文件", "*.*")]
)
if filename:
self.output_path.set(filename)
def browse_ip_file(self):
filename = filedialog.askopenfilename(
filetypes=[("Excel文件", "*.xlsx"), ("所有文件", "*.*")]
)
if filename:
self.ip_file_path_var.set(filename)
def handle_drop(self, event):
file_path = event.data
self.log_message(f"收到拖放文件: {file_path}")
# 移除花括号并转换为Path对象
file_path = Path(file_path.strip('{}'))
# 检查文件扩展名(不区分大小写)
if file_path.suffix.lower() in ('.html', '.htm'):
self.input_path.set(str(file_path))
output_path = file_path.with_suffix('.json')
self.output_path.set(str(output_path))
self.log_message(f"已设置输入文件: {file_path}")
self.log_message(f"已设置输出文件: {output_path}")
else:
self.log_message(f"无效的文件类型: {file_path}", "ERROR")
def handle_ip_file_drop(self, event):
file_path = event.data
self.log_message(f"收到拖放文件: {file_path}")
# 移除花括号并转换为Path对象
file_path = Path(file_path.strip('{}'))
# 检查文件扩展名(不区分大小写)
if file_path.suffix.lower() == '.xlsx':
self.ip_file_path_var.set(str(file_path))
self.log_message(f"已设置IP提取Excel文件: {file_path}")
else:
self.log_message(f"无效的文件类型: {file_path}", "ERROR")
def extract_json(self):
input_file = self.input_path.get()
output_file = self.output_path.get()
if not input_file or not output_file:
self.log_message("请选择输入和输出文件!", "ERROR")
return
try:
# 尝试多种编码格式
encodings = ['utf-8', 'gbk', 'gb2312', 'iso-8859-1']
file_content = None
for encoding in encodings:
try:
with open(input_file, encoding=encoding) as f:
file_content = f.read()
self.log_message(f"成功使用 {encoding} 编码读取文件")
break
except UnicodeDecodeError:
continue
if file_content is None:
self.log_message(f"无法读取文件,已尝试以下编码: {', '.join(encodings)}", "ERROR")
return
self.log_message(f"正在处理文件: {input_file}")
pat_list = re.findall(r'<script>window.data = (.*?);</script>', file_content)
if not pat_list:
self.log_message("未在HTML文件中找到匹配的JSON数据", "ERROR")
return
data_json = json.loads(pat_list[0])
with open(output_file, 'w', encoding='utf8') as f:
json.dump(data_json, f, ensure_ascii=False, indent=4)
success_msg = f"JSON数据已成功保存到: {output_file}"
self.log_message(success_msg, "SUCCESS")
self.status_var.set("JSON提取成功")
except Exception as e:
error_msg = f"处理过程中出现错误:{str(e)}"
self.log_message(error_msg, "ERROR")
self.status_var.set(f"错误: {str(e)}")
def show_keyword_dialog(self):
KeywordDialog(self.root, self.keyword_manager)
def export_vuln_types(self):
try:
# 先让用户选择保存位置
output_excel = filedialog.asksaveasfilename(
title="保存漏洞分类Excel",
defaultextension=".xlsx",
initialfile="漏洞类型分类.xlsx",
filetypes=[
("Excel文件", "*.xlsx"),
("所有文件", "*.*")
]
)
if not output_excel: # 用户取消选择
return
# 读取JSON文件
with open(self.output_path.get(), 'r', encoding='utf-8') as f:
data = json.load(f)
# 获取漏洞列表
vuln_list = data['categories'][3]['children'][0]['data']['vulns_info']['vuln_distribution']['vuln_list']
# 准备Excel数据
excel_data = []
for i, vuln in enumerate(vuln_list, 1):
# 处理描述和解决方案
description = '\n'.join(filter(None, vuln.get('i18n_description', [])))
solution = '\n'.join(filter(None, vuln.get('i18n_solution', [])))
# 获取漏洞等级中文
level_map = {'high': '高危', 'middle': '中危', 'low': '低危'}
level = level_map.get(vuln.get('vuln_level', ''), '未知')
# 获取漏洞类型
vuln_type = self.keyword_manager.get_type(
vuln.get('i18n_name', ''),
self.match_mode.get()
)
excel_data.append({
'序号': i,
'漏洞名称': vuln.get('i18n_name', ''),
'类型': vuln_type,
'漏洞等级': level,
'影响主机个数': vuln.get('vuln_count', 0),
'受影响主机': vuln.get('target', ''),
'详细描述': description,
'解决办法': solution
})
# 创建DataFrame并导出到Excel
df = pd.DataFrame(excel_data)
df.to_excel(output_excel, index=False)
self.log_message(f"漏洞类型分类已导出到: {output_excel}", "SUCCESS")
except Exception as e:
self.log_message(f"导出漏洞类型分类时出错: {str(e)}", "ERROR")
def init_ip_extract_tab(self):
# 添加说明文字
description = "本功能旨在漏洞明细Excel表格按漏洞等级、漏洞类型进行提取IP地址支持IP去重请先将漏洞明细Excel表格进行漏洞分类后再使用本功能"
desc_label = tk.Label(self.ip_extract_frame, text=description, wraplength=700, justify="left")
desc_label.pack(fill="x", padx=10, pady=10)
# 文件选择框架
file_frame = tk.LabelFrame(self.ip_extract_frame, text="选择Excel文件", padx=10, pady=5)
file_frame.pack(fill="x", padx=10, pady=5)
self.ip_file_path_var = tk.StringVar()
self.ip_file_entry = tk.Entry(file_frame, textvariable=self.ip_file_path_var, width=80)
self.ip_file_entry.pack(side="left", padx=5)
browse_btn = tk.Button(file_frame, text="浏览", command=self.browse_ip_file)
browse_btn.pack(side="left", padx=5)
# Excel类型选择框架
type_frame = tk.LabelFrame(self.ip_extract_frame, text="Excel类型", padx=10, pady=5)
type_frame.pack(fill="x", padx=10, pady=5)
self.excel_type_var = tk.StringVar(value="complex")
simple_radio = tk.Radiobutton(type_frame, text="简单表格(一列一个字段)",
variable=self.excel_type_var, value="simple")
simple_radio.pack(side="left", padx=5)
complex_radio = tk.Radiobutton(type_frame, text="复杂表格(混合布局字段)",
variable=self.excel_type_var, value="complex")
complex_radio.pack(side="left", padx=5)
# 去重选项
self.ip_deduplicate_var = tk.BooleanVar()
dedup_check = tk.Checkbutton(self.ip_extract_frame, text="去重IP地址",
variable=self.ip_deduplicate_var)
dedup_check.pack(pady=10)
# 提取按钮
extract_btn = tk.Button(self.ip_extract_frame, text="提取IP地址",
command=self.extract_ip_addresses)
extract_btn.pack(pady=10)
# IP提取状态标签
self.ip_status_var = tk.StringVar()
ip_status_label = tk.Label(self.ip_extract_frame,
textvariable=self.ip_status_var, fg="green")
ip_status_label.pack(pady=5)
# 文件拖放支持
self.ip_file_entry.drop_target_register(DND_FILES)
self.ip_file_entry.dnd_bind('<<Drop>>', self.handle_ip_file_drop)
def extract_ip_addresses(self):
file_path = self.ip_file_path_var.get()
if not file_path:
messagebox.showwarning("警告", "请选择一个有效的Excel文件。")
return
# 让用户选择导出目录
output_dir = filedialog.askdirectory(title="选择IP地址文件保存目录")
if not output_dir: # 用户取消选择
return
try:
df = pd.read_excel(file_path)
# 初始化文件句柄字典和IP集合字典
file_handles = {}
ip_sets = {}
if self.excel_type_var.get() == "simple":
# 处理简单表格格式
for _, row in df.iterrows():
risk_level = str(row['漏洞等级']).strip()
category = str(row['类型']).strip()
ip_addresses = str(row['受影响主机']).strip()
if ip_addresses and ip_addresses != 'nan':
# 构建完整的文件路径
filename = Path(output_dir) / f"{risk_level}-{category}.txt"
# 如果文件句柄不存在则创建新的文件句柄和IP集合
if filename not in file_handles:
file_handles[filename] = open(filename, 'w', encoding='utf-8')
if self.ip_deduplicate_var.get():
ip_sets[filename] = set()
# 如果有多个IP使用";"分隔
for ip in ip_addresses.split(';'):
ip = ip.strip()
if ip:
if self.ip_deduplicate_var.get():
if ip not in ip_sets[filename]:
file_handles[filename].write(ip + '\n')
ip_sets[filename].add(ip)
else:
file_handles[filename].write(ip + '\n')
else:
# 处理复杂表格格式
for i in range(1, len(df), 4):
if i + 1 < len(df):
risk_level = str(df.iloc[i-1, 2]).strip()
category = str(df.iloc[i-1, 4]).strip()
# 构建完整的文件路径
filename = Path(output_dir) / f"{risk_level}-{category}.txt"
# 如果文件句柄不存在则创建新的文件句柄和IP集合
if filename not in file_handles:
file_handles[filename] = open(filename, 'w', encoding='utf-8')
if self.ip_deduplicate_var.get():
ip_sets[filename] = set()
ip_addresses = str(df.iloc[i, 3]).strip()
if ip_addresses and ip_addresses != 'nan':
# 如果有多个IP使用";"分隔
for ip in ip_addresses.split(';'):
ip = ip.strip()
if ip:
if self.ip_deduplicate_var.get():
if ip not in ip_sets[filename]:
file_handles[filename].write(ip + '\n')
ip_sets[filename].add(ip)
else:
file_handles[filename].write(ip + '\n')
# 确保所有文件都被正确关闭
for fh in file_handles.values():
fh.close()
self.ip_status_var.set(f"IP地址已成功提取并保存到目录: {output_dir}")
messagebox.showinfo("完成", f"IP地址已成功提取并保存到目录: {output_dir}")
except Exception as e:
self.ip_status_var.set(f"发生错误: {str(e)}")
messagebox.showerror("错误", f"发生错误: {str(e)}")
def init_lvmeng_export_tab(self):
"""初始化漏扫结果提取明细至Excel标签页"""
# 添加说明文字
description = "本功能用于将绿盟漏扫系统生成的漏洞扫描结果HTML源文件提取至Excel文件支持新旧版绿盟的漏洞扫描结果的解析和导出并提供不同的导出样式。"
desc_label = tk.Label(self.vuln_export_frame, text=description, wraplength=700, justify="left")
desc_label.pack(fill="x", padx=10, pady=10)
# 输入文件框架
input_frame = tk.LabelFrame(self.vuln_export_frame, text="输入HTML文件", padx=10, pady=5)
input_frame.pack(fill="x", padx=10, pady=5)
self.lvmeng_input_path = tk.StringVar()
self.lvmeng_input_entry = tk.Entry(input_frame, textvariable=self.lvmeng_input_path, width=80)
self.lvmeng_input_entry.pack(side="left", padx=5)
browse_btn = tk.Button(input_frame, text="浏览", command=self.browse_lvmeng_input)
browse_btn.pack(side="left", padx=5)
# 拖放提示
drop_label = tk.Label(input_frame, text="(支持拖放HTML文件)", pady=5)
drop_label.pack(side="left", padx=5)
# 版本选择框架
version_frame = tk.LabelFrame(self.vuln_export_frame, text="绿盟版本", padx=10, pady=5)
version_frame.pack(fill="x", padx=10, pady=5)
self.lvmeng_version = tk.StringVar(value="new")
new_radio = tk.Radiobutton(version_frame, text="新版绿盟",
variable=self.lvmeng_version, value="new")
new_radio.pack(side="left", padx=5)
old_radio = tk.Radiobutton(version_frame, text="旧版绿盟",
variable=self.lvmeng_version, value="old")
old_radio.pack(side="left", padx=5)
# 样式选择框架
style_frame = tk.LabelFrame(self.vuln_export_frame, text="导出样式", padx=10, pady=5)
style_frame.pack(fill="x", padx=10, pady=5)
self.lvmeng_style = tk.StringVar(value="style1")
style1_radio = tk.Radiobutton(style_frame, text="样式一(简单表格)",
variable=self.lvmeng_style, value="style1")
style1_radio.pack(side="left", padx=5)
style2_radio = tk.Radiobutton(style_frame, text="样式二(复杂表格)",
variable=self.lvmeng_style, value="style2")
style2_radio.pack(side="left", padx=5)
# 输出文件框架
output_frame = tk.LabelFrame(self.vuln_export_frame, text="输出Excel文件", padx=10, pady=5)
output_frame.pack(fill="x", padx=10, pady=5)
self.lvmeng_output_path = tk.StringVar()
self.lvmeng_output_entry = tk.Entry(output_frame, textvariable=self.lvmeng_output_path, width=80)
self.lvmeng_output_entry.pack(side="left", padx=5)
browse_output_btn = tk.Button(output_frame, text="浏览", command=self.browse_lvmeng_output)
browse_output_btn.pack(side="left", padx=5)
# 导出按钮
export_btn = tk.Button(self.vuln_export_frame, text="导出Excel",
command=self.export_lvmeng_to_excel)
export_btn.pack(pady=10)
# 漏扫提取状态标签
self.lvmeng_status_var = tk.StringVar()
lvmeng_status_label = tk.Label(self.vuln_export_frame,
textvariable=self.lvmeng_status_var, fg="green")
lvmeng_status_label.pack(pady=5)
# 日志框
log_frame = tk.LabelFrame(self.vuln_export_frame, text="运行日志", padx=10, pady=5)
log_frame.pack(fill="both", expand=True, padx=10, pady=5)
# 创建文本框和滚动条
self.lvmeng_log_text = tk.Text(log_frame, height=12, width=80)
scrollbar = tk.Scrollbar(log_frame, orient="vertical", command=self.lvmeng_log_text.yview)
self.lvmeng_log_text.configure(yscrollcommand=scrollbar.set)
# 放置文本框和滚动条
scrollbar.pack(side="right", fill="y")
self.lvmeng_log_text.pack(side="left", fill="both", expand=True)
# 文件拖放支持
self.lvmeng_input_entry.drop_target_register(DND_FILES)
self.lvmeng_input_entry.dnd_bind('<<Drop>>', self.handle_lvmeng_file_drop)
def browse_lvmeng_input(self):
"""浏览并选择HTML输入文件"""
filename = filedialog.askopenfilename(
filetypes=[("HTML文件", "*.html"), ("所有文件", "*.*")]
)
if filename:
self.lvmeng_input_path.set(filename)
# 自动设置输出文件名
output_path = Path(filename).with_suffix('.xlsx')
self.lvmeng_output_path.set(str(output_path))
self.lvmeng_log("已选择输入文件: " + filename)
def browse_lvmeng_output(self):
"""浏览并选择Excel输出文件"""
filename = filedialog.asksaveasfilename(
defaultextension=".xlsx",
filetypes=[("Excel文件", "*.xlsx"), ("所有文件", "*.*")]
)
if filename:
self.lvmeng_output_path.set(filename)
self.lvmeng_log("已选择输出文件: " + filename)
def handle_lvmeng_file_drop(self, event):
"""处理拖放HTML文件"""
file_path = event.data
self.lvmeng_log(f"收到拖放文件: {file_path}")
# 移除花括号并转换为Path对象
file_path = Path(file_path.strip('{}'))
# 检查文件扩展名(不区分大小写)
if file_path.suffix.lower() in ('.html', '.htm'):
self.lvmeng_input_path.set(str(file_path))
output_path = file_path.with_suffix('.xlsx')
self.lvmeng_output_path.set(str(output_path))
self.lvmeng_log(f"已设置输入文件: {file_path}")
self.lvmeng_log(f"已设置输出文件: {output_path}")
else:
self.lvmeng_log(f"无效的文件类型: {file_path}", "ERROR")
def lvmeng_log(self, message, level="INFO"):
"""添加日志消息到漏扫提取日志框"""
timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
self.lvmeng_log_text.insert("end", f"[{timestamp}] [{level}] {message}\n")
self.lvmeng_log_text.see("end") # 自动滚动到最新消息
def export_lvmeng_to_excel(self):
"""导出漏扫结果到Excel"""
input_file = self.lvmeng_input_path.get()
output_file = self.lvmeng_output_path.get()
version = self.lvmeng_version.get()
style = self.lvmeng_style.get()
if not input_file or not output_file:
messagebox.showwarning("警告", "请选择输入和输出文件。")
self.lvmeng_log("请选择输入和输出文件!", "ERROR")
return
if not Path(input_file).exists():
messagebox.showwarning("警告", "输入文件不存在。")
self.lvmeng_log("输入文件不存在!", "ERROR")
return
try:
self.lvmeng_log(f"开始处理文件: {input_file}")
self.lvmeng_log(f"导出版本: {'新版绿盟' if version == 'new' else '旧版绿盟'}")
self.lvmeng_log(f"导出样式: {'样式一' if style == 'style1' else '样式二'}")
# 根据版本和样式选择处理方法
if version == "new":
self.process_new_lvmeng(input_file, output_file, style)
else:
self.process_old_lvmeng(input_file, output_file, style)
self.lvmeng_status_var.set(f"漏洞数据已成功导出到: {output_file}")
messagebox.showinfo("完成", f"漏洞数据已成功导出到: {output_file}")
except Exception as e:
error_msg = f"导出过程中出现错误: {str(e)}"
self.lvmeng_log(error_msg, "ERROR")
self.lvmeng_status_var.set("导出失败")
messagebox.showerror("错误", error_msg)
def process_new_lvmeng(self, input_file, output_file, style):
"""处理新版绿盟漏扫结果并导出到Excel"""
try:
# 读取HTML文件
with open(input_file, 'r', encoding='utf-8') as f:
file_content = f.read()
# 提取JSON数据
self.lvmeng_log("正在提取JSON数据...")
pat_list = re.findall(r'<script>window.data = (.*?);</script>', file_content)
if not pat_list:
raise Exception("未在HTML文件中找到匹配的JSON数据")
data_json = json.loads(pat_list[0])
# 获取漏洞列表
vuln_list = data_json["categories"][3]["children"][0]["data"]["vulns_info"]["vuln_distribution"]["vuln_list"]
self.lvmeng_log(f"成功提取到 {len(vuln_list)} 个漏洞")
# 创建Excel工作簿
wb = Workbook()
ws = wb.active
ws.title = "漏洞信息"
# 定义样式
# 定义对齐方式(水平居中,垂直居中)
alignment = Alignment(horizontal='center', vertical='center', wrap_text=True)
# 定义对齐方式(水平左对齐,垂直居中)
horLeft_alignment = Alignment(horizontal='left', vertical='center', wrap_text=True)
# 单独定义字体加粗
font_bold = Font(bold=True)
# 单独定义颜色字体
font_red = Font(bold=False, color='E42B00')
font_orange = Font(bold=False, color='AF6100')
font_gray = Font(bold=False, color='737373')
# 定义边框样式
thin_border = Border(
left=Side(style='thin'),
right=Side(style='thin'),
top=Side(style='thin'),
bottom=Side(style='thin')
)
if style == 'style1':
# 样式一:简单表格
self.lvmeng_log("使用样式一(简单表格)导出...")
# 添加表头
headers = ['序号', '漏洞名称', '漏洞等级', '影响主机个数', '受影响主机', '详细描述', '解决办法']
for col_idx, header in enumerate(headers, 1):
cell = ws.cell(row=1, column=col_idx, value=header)
cell.alignment = alignment
cell.font = font_bold
cell.border = thin_border
# 设置列宽
ws.column_dimensions['A'].width = 7
ws.column_dimensions['B'].width = 45
ws.column_dimensions['C'].width = 15
ws.column_dimensions['D'].width = 15
ws.column_dimensions['E'].width = 40
ws.column_dimensions['F'].width = 50
ws.column_dimensions['G'].width = 50
# 添加数据
for idx, vuln in enumerate(vuln_list, 1):
# 获取漏洞等级中文
level_map = {'high': '高危', 'middle': '中危', 'low': '低危'}
level = level_map.get(vuln.get('vuln_level', ''), '未知')
# 处理描述和解决方案
description = '\n'.join(filter(None, vuln.get('i18n_description', [])))
solution = '\n'.join(filter(None, vuln.get('i18n_solution', [])))
# 添加行数据
row_data = [
idx,
vuln.get('i18n_name', ''),
level,
vuln.get('vuln_count', 0),
vuln.get('target', ''),
description,
solution
]
row_idx = idx + 1
for col_idx, value in enumerate(row_data, 1):
cell = ws.cell(row=row_idx, column=col_idx, value=value)
cell.border = thin_border
# 设置对齐方式
if col_idx in [1, 3, 4]: # 序号、漏洞等级、影响主机个数居中
cell.alignment = alignment
else: # 其他左对齐
cell.alignment = horLeft_alignment
# 设置漏洞等级颜色
if col_idx in [2, 3]: # 漏洞名称和等级
if level == '高危':
cell.font = font_red
elif level == '中危':
cell.font = font_orange
elif level == '低危':
cell.font = font_gray
else:
# 样式二:复杂表格
self.lvmeng_log("使用样式二(复杂表格)导出...")
# 添加表头
headers = ['序号', '漏洞名称', '漏洞等级', '影响主机个数']
for col_idx, header in enumerate(headers, 1):
cell = ws.cell(row=1, column=col_idx, value=header)
cell.alignment = alignment
cell.font = font_bold
cell.border = thin_border
# 设置列宽
ws.column_dimensions['A'].width = 7
ws.column_dimensions['B'].width = 45
ws.column_dimensions['C'].width = 15
ws.column_dimensions['D'].width = 50
current_row = 2
for idx, vuln in enumerate(vuln_list, 1):
# 获取漏洞等级中文
level_map = {'high': '高危', 'middle': '中危', 'low': '低危'}
level = level_map.get(vuln.get('vuln_level', ''), '未知')
# 处理描述和解决方案
description = '\n'.join(filter(None, vuln.get('i18n_description', [])))
solution = '\n'.join(filter(None, vuln.get('i18n_solution', [])))
# 添加主行数据
row_data = [
idx,
vuln.get('i18n_name', ''),
level,
vuln.get('vuln_count', 0)
]
for col_idx, value in enumerate(row_data, 1):
cell = ws.cell(row=current_row, column=col_idx, value=value)
cell.border = thin_border
# 设置对齐方式
if col_idx == 2: # 漏洞名称左对齐
cell.alignment = horLeft_alignment
else: # 其他居中
cell.alignment = alignment
# 设置漏洞等级颜色
if col_idx in [2, 3]: # 漏洞名称和等级
if level == '高危':
cell.font = font_red
elif level == '中危':
cell.font = font_orange
elif level == '低危':
cell.font = font_gray
# 添加详细信息行
details = [
('受影响主机', vuln.get('target', '')),
('详细描述', description),
('解决办法', solution)
]
# 计算实际需要的行数
total_rows = len(details)
# 合并第一列的单元格
if total_rows > 0:
ws.merge_cells(f'A{current_row+1}:A{current_row+total_rows}')
merged_cell = ws.cell(row=current_row+1, column=1)
merged_cell.border = thin_border
# 添加所有详细信息
for i, (label, value) in enumerate(details):
row_num = current_row + i + 1
# 添加标签(第二列)
label_cell = ws.cell(row=row_num, column=2)
label_cell.value = label
label_cell.font = font_bold
label_cell.border = thin_border
label_cell.alignment = alignment # 标签居中对齐
# 添加值(第三列)- 保持为空
value_cell = ws.cell(row=row_num, column=3)
value_cell.border = thin_border
# 第四列设置值和自动换行
value_cell = ws.cell(row=row_num, column=4)
value_cell.value = value
value_cell.border = thin_border
value_cell.alignment = horLeft_alignment # 自动换行
current_row += total_rows + 1 # 更新行号加1是为了下一组数据之间留空
# 保存Excel文件
wb.save(output_file)
self.lvmeng_log(f"成功导出到Excel文件: {output_file}", "SUCCESS")
return True
except Exception as e:
self.lvmeng_log(f"处理新版绿盟漏扫结果时出错: {str(e)}", "ERROR")
raise
def process_old_lvmeng(self, input_file, output_file, style):
"""处理旧版绿盟漏扫结果并导出到Excel"""
try:
# 读取HTML文件
with open(input_file, 'r', encoding='utf-8') as f:
html_content = f.read()
# 使用BeautifulSoup解析HTML
self.lvmeng_log("正在解析HTML文件...")
soup = BeautifulSoup(html_content, 'html.parser')
# 查找漏洞分布表格
vuln_table = soup.find('table', id='vuln_distribution')
if not vuln_table:
raise Exception("未找到漏洞分布表格,请确认是否为旧版绿盟漏扫结果")
# 查找所有漏洞行
vuln_rows = vuln_table.find_all('tr', class_=re.compile(r'(odd|even) vuln_(high|middle|low)'))
self.lvmeng_log(f"成功提取到 {len(vuln_rows)} 个漏洞")
# 存储漏洞数据
vulnerability_data = []
for row in vuln_rows:
# 提取基本信息
tds = row.find_all('td')
number = tds[0].text.strip()
name = row.find('span').text.strip()
affected_hosts_count = tds[2].text.strip()
# 确定漏洞等级
if 'vuln_high' in row['class']:
level = '高危'
elif 'vuln_middle' in row['class']:
level = '中危'
else:
level = '低危'
# 获取详细信息所在的下一个tr
detail_row = row.find_next('tr', class_=re.compile(r'more hide (odd|even)'))
# 初始化变量
affected_hosts_str = ""
description = ""
solution = ""
additional_fields = {
'威胁分值': '',
'危险插件': '',
'发现日期': '',
'CVE编号': '',
'CNNVD编号': '',
'CNCVE编号': '',
'CVSS评分': '',
'CNVD编号': ''
}
if detail_row:
# 提取受影响主机
affected_hosts = []
# 先查找所有<a>标签的主机
host_links = detail_row.find_all('a', href=re.compile(r'host/.*\.html'))
if host_links: # 如果找到<a>标签
for link in host_links:
affected_hosts.append(link.text.strip())
else: # 如果没有<a>标签查找特定的td标签
# 查找class为report_table的table下的width为80%的td
report_table = detail_row.find('table', class_='report_table')
if report_table:
host_td = report_table.find('td', attrs={'width': '80%'})
if host_td:
# 替换所有&nbsp;为空格,然后获取文本
hosts_text = host_td.text.replace('&nbsp;', ' ').strip()
# 如果文本不为空,添加到列表
if hosts_text:
affected_hosts.append(hosts_text)
# 将所有主机信息合并,用逗号分隔
affected_hosts_str = ', '.join(affected_hosts)
# 提取详细描述
description_row = detail_row.find('tr', class_='even')
description = description_row.find('td').text.strip() if description_row else ''
# 提取解决办法
solution_row = description_row.find_next('tr', class_='odd') if description_row else None
solution = solution_row.find('td').text.strip() if solution_row else ''
# 查找所有可能包含新字段的行
info_rows = detail_row.find_all('tr', class_=re.compile(r'(odd|even)'))
for info_row in info_rows:
field_name = info_row.find('th')
if field_name and field_name.text.strip() in additional_fields:
field_value = info_row.find('td')
if field_value:
additional_fields[field_name.text.strip()] = field_value.text.strip()
# 添加到漏洞数据列表
vulnerability_data.append({
'序号': number,
'漏洞名称': name,
'漏洞等级': level,
'影响主机个数': affected_hosts_count,
'受影响主机': affected_hosts_str,
'详细描述': description,
'解决办法': solution,
**additional_fields # 添加新字段
})
# 创建Excel工作簿
wb = Workbook()
ws = wb.active
ws.title = "漏洞信息"
# 设置边框样式
border = Border(
left=Side(style='thin'),
right=Side(style='thin'),
top=Side(style='thin'),
bottom=Side(style='thin')
)
# 设置表头字体
header_font = Font(bold=True)
# 设置不同等级的字体颜色
high_font = Font(color='FF0000') # 红色
middle_font = Font(color='FFA500') # 橙色
low_font = Font(color='808080') # 灰色
if style == 'style1':
# 样式一:简单表格
self.lvmeng_log("使用样式一(简单表格)导出...")
# 添加表头
headers = ['序号', '漏洞名称', '漏洞等级', '影响主机个数', '受影响主机', '详细描述', '解决办法']
ws.append(headers)
# 设置表头格式
for cell in ws[1]:
cell.font = header_font
cell.border = border
# 添加数据并设置格式
for row_data in vulnerability_data:
row = [row_data[h] for h in headers]
ws.append(row)
row_num = ws.max_row
# 设置单元格边框和字体颜色
for col in range(1, len(headers) + 1):
cell = ws.cell(row=row_num, column=col)
cell.border = border
# 设置漏洞名称和等级的字体颜色
if col in [2, 3]: # 漏洞名称和漏洞等级列
level = row_data['漏洞等级']
if level == '高危':
cell.font = high_font
elif level == '中危':
cell.font = middle_font
else:
cell.font = low_font
# 调整列宽
for column in ws.columns:
max_length = 0
column = [cell for cell in column]
for cell in column:
try:
if len(str(cell.value)) > max_length:
max_length = len(str(cell.value))
except:
pass
adjusted_width = (max_length + 2)
ws.column_dimensions[get_column_letter(column[0].column)].width = adjusted_width
else:
# 样式二:复杂表格
self.lvmeng_log("使用样式二(复杂表格)导出...")
# 设置居中对齐
center_alignment = Alignment(horizontal='center', vertical='center')
# 设置自动换行对齐
wrap_alignment = Alignment(horizontal='left', vertical='center', wrap_text=True)
# 添加表头
headers = ['序号', '漏洞名称', '漏洞等级', '影响主机个数']
ws.append(headers)
# 设置表头格式
for cell in ws[1]:
cell.font = header_font
cell.border = border
cell.alignment = center_alignment
# 设置固定列宽
ws.column_dimensions['A'].width = 7 # 第一列
ws.column_dimensions['B'].width = 45 # 第二列
ws.column_dimensions['C'].width = 15 # 第三列
ws.column_dimensions['D'].width = 50 # 第四列
current_row = 2
for row_data in vulnerability_data:
# 添加主行
row = [row_data['序号'], row_data['漏洞名称'], row_data['漏洞等级'], row_data['影响主机个数']]
for col, value in enumerate(row, 1):
cell = ws.cell(row=current_row, column=col)
cell.value = value
cell.border = border
cell.alignment = center_alignment # 第一行所有单元格居中
# 第一行第二个单元格设置自动换行
if col == 2:
cell.alignment = wrap_alignment
# 设置漏洞名称和等级的字体颜色
if col in [2, 3]: # 漏洞名称和漏洞等级列
level = row_data['漏洞等级']
if level == '高危':
cell.font = high_font
elif level == '中危':
cell.font = middle_font
else:
cell.font = low_font
# 添加详细信息行
details = [
('受影响主机', row_data['受影响主机']),
('详细描述', row_data['详细描述']),
('解决办法', row_data['解决办法'])
]
# 添加新字段(只添加有值的字段)
additional_fields = [
('威胁分值', row_data['威胁分值']),
('危险插件', row_data['危险插件']),
('发现日期', row_data['发现日期']),
('CVE编号', row_data['CVE编号']),
('CNNVD编号', row_data['CNNVD编号']),
('CNCVE编号', row_data['CNCVE编号']),
('CVSS评分', row_data['CVSS评分']),
('CNVD编号', row_data['CNVD编号'])
]
# 过滤掉空值的字段
details.extend([(label, value) for label, value in additional_fields if value])
# 计算实际需要的行数
total_rows = len(details)
# 合并第一列的单元格
if total_rows > 0:
ws.merge_cells(f'A{current_row+1}:A{current_row+total_rows}')
merged_cell = ws.cell(row=current_row+1, column=1)
merged_cell.border = border
# 添加所有详细信息
for i, (label, value) in enumerate(details):
row_num = current_row + i + 1
# 添加标签(第二列)
label_cell = ws.cell(row=row_num, column=2)
label_cell.value = label
label_cell.font = header_font
label_cell.border = border
label_cell.alignment = center_alignment # 标签居中对齐
# 添加值(第三列)- 保持为空
value_cell = ws.cell(row=row_num, column=3)
value_cell.border = border
# 第四列设置值和自动换行
value_cell = ws.cell(row=row_num, column=4)
value_cell.value = value
value_cell.border = border
value_cell.alignment = wrap_alignment # 自动换行
current_row += total_rows + 1 # 更新行号加1是为了下一组数据之间留空
# 保存Excel文件
wb.save(output_file)
self.lvmeng_log(f"成功导出到Excel文件: {output_file}", "SUCCESS")
return True
except Exception as e:
self.lvmeng_log(f"处理旧版绿盟漏扫结果时出错: {str(e)}", "ERROR")
raise
if __name__ == "__main__":
root = TkinterDnD.Tk()
app = JsonExtractorGUI(root)
root.mainloop()