243 lines
9.8 KiB
Python
243 lines
9.8 KiB
Python
|
|
import tkinter as tk
|
|||
|
|
from tkinter import filedialog, messagebox
|
|||
|
|
import json
|
|||
|
|
import re
|
|||
|
|
from pathlib import Path
|
|||
|
|
import pandas as pd
|
|||
|
|
from keyword_manager import KeywordManager
|
|||
|
|
from keyword_dialog import KeywordDialog
|
|||
|
|
|
|||
|
|
class JsonExtractorGUI:
|
|||
|
|
def __init__(self, root):
|
|||
|
|
self.root = root
|
|||
|
|
self.root.title("HTML JSON提取器")
|
|||
|
|
self.root.geometry("800x600")
|
|||
|
|
|
|||
|
|
# 输入文件框
|
|||
|
|
self.input_frame = tk.LabelFrame(root, 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(root, 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)
|
|||
|
|
|
|||
|
|
# 添加关键词管理器
|
|||
|
|
self.keyword_manager = KeywordManager()
|
|||
|
|
|
|||
|
|
# 添加按钮框架
|
|||
|
|
self.button_frame = tk.Frame(root)
|
|||
|
|
self.button_frame.pack(pady=5)
|
|||
|
|
|
|||
|
|
# 提取按钮移到按钮框架
|
|||
|
|
self.extract_btn = tk.Button(self.button_frame, text="提取JSON", command=self.extract_json)
|
|||
|
|
self.extract_btn.pack(side="left", padx=5)
|
|||
|
|
|
|||
|
|
# 添加漏洞分类按钮
|
|||
|
|
self.vuln_btn = tk.Button(self.button_frame, text="漏洞类型分类", command=self.export_vuln_types)
|
|||
|
|
self.vuln_btn.pack(side="left", padx=5)
|
|||
|
|
|
|||
|
|
# 添加关键词管理按钮
|
|||
|
|
self.keyword_btn = tk.Button(self.button_frame, text="关键词管理", command=self.show_keyword_dialog)
|
|||
|
|
self.keyword_btn.pack(side="left", padx=5)
|
|||
|
|
|
|||
|
|
# 添加匹配模式选择
|
|||
|
|
self.match_mode = tk.StringVar(value="both")
|
|||
|
|
match_frame = tk.LabelFrame(root, text="匹配模式", padx=5, pady=5)
|
|||
|
|
match_frame.pack(fill="x", padx=10, pady=5)
|
|||
|
|
|
|||
|
|
modes = [
|
|||
|
|
("精准匹配", "exact"),
|
|||
|
|
("模糊匹配", "fuzzy"),
|
|||
|
|
("精准+模糊", "both")
|
|||
|
|
]
|
|||
|
|
|
|||
|
|
for text, mode in modes:
|
|||
|
|
tk.Radiobutton(match_frame, text=text, variable=self.match_mode,
|
|||
|
|
value=mode).pack(side="left", padx=5)
|
|||
|
|
|
|||
|
|
# 日志框
|
|||
|
|
self.log_frame = tk.LabelFrame(root, 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(root, 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)
|
|||
|
|
|
|||
|
|
def log_message(self, message, level="INFO"):
|
|||
|
|
"""添加日志消息到日志框"""
|
|||
|
|
import datetime
|
|||
|
|
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 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 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:
|
|||
|
|
# 读取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)
|
|||
|
|
output_excel = Path(self.output_path.get()).with_suffix('.xlsx')
|
|||
|
|
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")
|
|||
|
|
|
|||
|
|
if __name__ == "__main__":
|
|||
|
|
# 需要安装tkinterdnd2包来支持拖放功能
|
|||
|
|
from tkinterdnd2 import *
|
|||
|
|
|
|||
|
|
root = TkinterDnD.Tk()
|
|||
|
|
app = JsonExtractorGUI(root)
|
|||
|
|
root.mainloop()
|