/
manokuzCS
/
Web_Scanner_SQL_XSS_CSRF
Обзор
Документация
Войти
/
manokuzCS
/
Web_Scanner_SQL_XSS_CSRF
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
ui/vulnerability_viewer.py
597 строк
28 KB
Manokuz
29.10.2025 (Fixes_2)
30 окт 2025, 17:35
30 окт 2025, 17:35
a5b5a8c
Код
Авторство
О чём код?
""" Интерфейс для просмотра и анализа уязвимостей. """ import json from datetime import datetime from typing import Any, Dict, List, Optional, Union from PyQt5.QtWidgets import ( QWidget, QVBoxLayout, QHBoxLayout, QTreeWidget, QTreeWidgetItem, QLabel, QTextEdit, QPushButton, QComboBox, QSplitter, QFrame, QGroupBox, QTabWidget, QTableWidget, QTableWidgetItem, QHeaderView, QLineEdit, QDialog, QMessageBox ) from PyQt5.QtCore import Qt, pyqtSignal, QTimer from PyQt5.QtGui import QColor from utils.logger import logger from utils.database import db class VulnerabilityViewer(QWidget): """ Виджет для просмотра и анализа уязвимостей, найденных при сканировании. """ # Сигналы vulnerability_selected = pyqtSignal(dict) export_requested = pyqtSignal(list, str) fix_requested = pyqtSignal(dict) # Сигнал для запроса исправления уязвимости def __init__(self, parent: Optional[QWidget] = None): super().__init__(parent) self.current_vulnerability = None self.setup_ui() self.load_vulnerabilities() # Устанавливаем таймер для периодического обновления данных self.refresh_timer = QTimer(self) self.refresh_timer.timeout.connect(self.load_vulnerabilities) self.refresh_timer.start(30000) # Обновление каждые 30 секунд def setup_ui(self): """Настраивает пользовательский интерфейс.""" layout = QVBoxLayout(self) # Верхняя панель с фильтрами filter_panel = self.create_filter_panel() layout.addWidget(filter_panel) # Основная область с разделением splitter = QSplitter(Qt.Horizontal) # Дерево уязвимостей слева self.vulnerability_tree = self.create_vulnerability_tree() splitter.addWidget(self.vulnerability_tree) # Детали уязвимостей справа self.details_panel = self.create_details_panel() splitter.addWidget(self.details_panel) # Устанавливаем пропорции разделения splitter.setSizes([400, 600]) layout.addWidget(splitter) # Нижняя панель с кнопками button_panel = self.create_button_panel() layout.addWidget(button_panel) def create_filter_panel(self): """Создает панель фильтров.""" group = QGroupBox("Фильтры") layout = QHBoxLayout(group) # Фильтр по типу уязвимости self.type_filter = QComboBox() self.type_filter.addItems(["Все типы", "SQL-инъекции", "XSS", "CSRF", "SSRF", "XXE", "RCE"]) self.type_filter.currentTextChanged.connect(self.filter_vulnerabilities) layout.addWidget(QLabel("Тип уязвимости:")) layout.addWidget(self.type_filter) # Фильтр по уровню опасности self.severity_filter = QComboBox() self.severity_filter.addItems(["Все уровни", "Критический", "Высокий", "Средний", "Низкий"]) self.severity_filter.currentTextChanged.connect(self.filter_vulnerabilities) layout.addWidget(QLabel("Уровень опасности:")) layout.addWidget(self.severity_filter) # Фильтр по статусу self.status_filter = QComboBox() self.status_filter.addItems(["Все статусы", "Новые", "В работе", "Исправленные", "Ложные срабатывания"]) self.status_filter.currentTextChanged.connect(self.filter_vulnerabilities) layout.addWidget(QLabel("Статус:")) layout.addWidget(self.status_filter) # Фильтр по дате self.date_filter = QComboBox() self.date_filter.addItems(["Все даты", "Сегодня", "Последние 7 дней", "Последние 30 дней"]) self.date_filter.currentTextChanged.connect(self.filter_vulnerabilities) layout.addWidget(QLabel("Период:")) layout.addWidget(self.date_filter) # Поле поиска self.search_field = QLineEdit() self.search_field.setPlaceholderText("Поиск по URL или описанию...") self.search_field.textChanged.connect(self.filter_vulnerabilities) layout.addWidget(QLabel("Поиск:")) layout.addWidget(self.search_field) # Кнопка обновления refresh_btn = QPushButton("Обновить") refresh_btn.clicked.connect(self.load_vulnerabilities) layout.addWidget(refresh_btn) layout.addStretch() return group def create_vulnerability_tree(self): """Создает дерево уязвимостей.""" tree = QTreeWidget() tree.setHeaderLabels(["URL", "Тип", "Уровень опасности", "Статус"]) tree.setColumnWidth(0, 200) tree.setColumnWidth(1, 100) tree.setColumnWidth(2, 120) tree.setColumnWidth(3, 120) # Настройка внешнего вида tree.setAlternatingRowColors(True) tree.setSortingEnabled(True) tree.setRootIsDecorated(False) # Подключаем сигнал выбора элемента tree.itemClicked.connect(self.on_vulnerability_selected) return tree def create_details_panel(self): """Создает панель деталей уязвимости.""" tabs = QTabWidget() # Вкладка "Общие сведения" general_tab = QWidget() general_layout = QVBoxLayout(general_tab) # URL self.url_label = QLabel("URL:") self.url_value = QLabel() self.url_value.setWordWrap(True) self.url_value.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse) general_layout.addWidget(self.url_label) general_layout.addWidget(self.url_value) # Тип уязвимости self.type_label = QLabel("Тип уязвимости:") self.type_value = QLabel() general_layout.addWidget(self.type_label) general_layout.addWidget(self.type_value) # Уровень опасности self.severity_label = QLabel("Уровень опасности:") self.severity_value = QLabel() general_layout.addWidget(self.severity_label) general_layout.addWidget(self.severity_value) # Статус self.status_label = QLabel("Статус:") self.status_value = QLabel() general_layout.addWidget(self.status_label) general_layout.addWidget(self.status_value) # Описание self.description_label = QLabel("Описание:") self.description_text = QTextEdit() self.description_text.setReadOnly(True) general_layout.addWidget(self.description_label) general_layout.addWidget(self.description_text) general_layout.addStretch() tabs.addTab(general_tab, "Общие сведения") # Вкладка "Технические детали" technical_tab = QWidget() technical_layout = QVBoxLayout(technical_tab) # Параметры запроса self.request_params_label = QLabel("Параметры запроса:") self.request_params_text = QTextEdit() self.request_params_text.setReadOnly(True) technical_layout.addWidget(self.request_params_label) technical_layout.addWidget(self.request_params_text) # Ответ сервера self.response_label = QLabel("Ответ сервера:") self.response_text = QTextEdit() self.response_text.setReadOnly(True) technical_layout.addWidget(self.response_label) technical_layout.addWidget(self.response_text) technical_layout.addStretch() tabs.addTab(technical_tab, "Технические детали") # Вкладка "Рекомендации" recommendations_tab = QWidget() recommendations_layout = QVBoxLayout(recommendations_tab) # Рекомендации по исправлению self.recommendations_label = QLabel("Рекомендации по исправлению:") self.recommendations_text = QTextEdit() self.recommendations_text.setReadOnly(True) recommendations_layout.addWidget(self.recommendations_label) recommendations_layout.addWidget(self.recommendations_text) # Ссылки на ресурсы self.resources_label = QLabel("Полезные ресурсы:") self.resources_table = QTableWidget() self.resources_table.setColumnCount(2) self.resources_table.setHorizontalHeaderLabels(["Название", "URL"]) self.resources_table.horizontalHeader().setSectionResizeMode(0, QHeaderView.Stretch) self.resources_table.horizontalHeader().setSectionResizeMode(1, QHeaderView.Stretch) self.resources_table.setEditTriggers(QTableWidget.EditTrigger.NoEditTriggers) self.resources_table.setSelectionBehavior(QTableWidget.SelectRows) recommendations_layout.addWidget(self.resources_label) recommendations_layout.addWidget(self.resources_table) recommendations_layout.addStretch() tabs.addTab(recommendations_tab, "Рекомендации") return tabs def create_button_panel(self): """Создает панель кнопок.""" panel = QFrame() layout = QHBoxLayout(panel) # Кнопка изменения статуса self.change_status_btn = QPushButton("Изменить статус") self.change_status_btn.clicked.connect(self.change_vulnerability_status) layout.addWidget(self.change_status_btn) # Кнопка добавления комментария self.add_comment_btn = QPushButton("Добавить комментарий") self.add_comment_btn.clicked.connect(self.add_comment) layout.addWidget(self.add_comment_btn) # Кнопка исправления уязвимости self.fix_btn = QPushButton("Исправить") self.fix_btn.clicked.connect(self.fix_vulnerability) self.fix_btn.setEnabled(False) # Изначально неактивна layout.addWidget(self.fix_btn) # Кнопка экспорта self.export_btn = QPushButton("Экспортировать") self.export_btn.clicked.connect(self.export_vulnerabilities) layout.addWidget(self.export_btn) # Кнопка генерации отчета self.report_btn = QPushButton("Сгенерировать отчет") self.report_btn.clicked.connect(self.generate_report) layout.addWidget(self.report_btn) layout.addStretch() return panel def load_vulnerabilities(self): """Загружает уязвимости из базы данных.""" try: # Очищаем дерево self.vulnerability_tree.clear() # Получаем уязвимости из базы данных vulnerabilities: List[Dict[str, Any]] = db.get_all_vulnerabilities() # Добавляем уязвимости в дерево for vuln in vulnerabilities: item = QTreeWidgetItem(self.vulnerability_tree) item.setText(0, vuln.get('url', '')) item.setText(1, vuln.get('type', '')) item.setText(2, vuln.get('severity', '')) item.setText(3, vuln.get('status', 'Новая')) # Сохраняем ID уязвимости в данных элемента item.setData(0, Qt.UserRole, vuln.get('id')) # Устанавливаем цвет в зависимости от уровня опасности severity = vuln.get('severity', '').lower() if severity == 'критический': item.setBackground(2, QColor('#ffcccc')) # Светло-красный elif severity == 'высокий': item.setBackground(2, QColor('#ffdddd')) # Очень светло-красный elif severity == 'средний': item.setBackground(2, QColor('#ffffcc')) # Светло-желтый elif severity == 'низкий': item.setBackground(2, QColor('#ccffcc')) # Светло-зеленый except Exception as e: logger.error(f"Error loading vulnerabilities: {e}") def filter_vulnerabilities(self): """Применяет фильтры к списку уязвимостей.""" type_filter = self.type_filter.currentText() severity_filter = self.severity_filter.currentText() status_filter = self.status_filter.currentText() date_filter = self.date_filter.currentText() search_text = self.search_field.text().lower() # Проходим по всем элементам дерева for i in range(self.vulnerability_tree.topLevelItemCount()): item = self.vulnerability_tree.topLevelItem(i) # Проверяем соответствие фильтрам show = True if type_filter != "Все типы" and item.text(1) != type_filter: show = False if severity_filter != "Все уровни" and item.text(2) != severity_filter: show = False if status_filter != "Все статусы" and item.text(3) != status_filter: show = False # Фильтр по дате if date_filter != "Все даты": # Получаем ID уязвимости vuln_id = item.data(0, Qt.UserRole) if vuln_id: vulnerability = db.get_vulnerability_by_id(vuln_id) if vulnerability: created_date = vulnerability.get('created_date', '') if created_date: try: # Преобразуем строку даты в объект datetime vuln_date = datetime.fromisoformat(created_date) today = datetime.now() if date_filter == "Сегодня": if vuln_date.date() != today.date(): show = False elif date_filter == "Последние 7 дней": if (today - vuln_date).days > 7: show = False elif date_filter == "Последние 30 дней": if (today - vuln_date).days > 30: show = False except Exception: # Если не удалось преобразовать дату, показываем элемент pass # Фильтр по тексту поиска if search_text: url = item.text(0).lower() vuln_type = item.text(1).lower() if search_text not in url and search_text not in vuln_type: show = False # Показываем или скрываем элемент item.setHidden(not show) def on_vulnerability_selected(self, item: QTreeWidgetItem, column: int) -> None: """Обрабатывает выбор уязвимости в дереве.""" try: # Получаем ID уязвимости vuln_id = item.data(0, Qt.UserRole) if not vuln_id: return # Получаем детали уязвимости из базы данных vulnerability = db.get_vulnerability_by_id(vuln_id) if not vulnerability: return # Сохраняем текущую уязвимость self.current_vulnerability = vulnerability # Обновляем детали уязвимости self.update_vulnerability_details(vulnerability) # Активируем кнопку исправления self.fix_btn.setEnabled(True) # Отправляем сигнал о выборе уязвимости self.vulnerability_selected.emit(vulnerability) except Exception as e: logger.error(f"Error selecting vulnerability: {e}") def update_vulnerability_details(self, vulnerability): """Обновляет детали выбранной уязвимости.""" try: # Общие сведения self.url_value.setText(vulnerability.get('url', '')) self.type_value.setText(vulnerability.get('type', '')) self.severity_value.setText(vulnerability.get('severity', '')) self.status_value.setText(vulnerability.get('status', 'Новая')) self.description_text.setPlainText(vulnerability.get('description', '')) # Технические детали request_params = vulnerability.get('request_params', {}) if isinstance(request_params, dict): self.request_params_text.setPlainText(json.dumps(request_params, indent=2)) else: self.request_params_text.setPlainText(str(request_params)) response = vulnerability.get('response', '') self.response_text.setPlainText(response) # Рекомендации recommendations = vulnerability.get('recommendations', []) if recommendations: recommendations_text = "\n\n".join(rec.get('description', '') for rec in recommendations) self.recommendations_text.setPlainText(recommendations_text) else: self.recommendations_text.setPlainText("Рекомендации отсутствуют") # Ресурсы self.update_resources_table(vulnerability.get('resources', [])) except Exception as e: logger.error(f"Error updating vulnerability details: {e}") def update_resources_table(self, resources): """Обновляет таблицу ресурсов.""" try: self.resources_table.setRowCount(0) for i, resource in enumerate(resources): self.resources_table.insertRow(i) # Название title_item = QTableWidgetItem(resource.get('title', '')) self.resources_table.setItem(i, 0, title_item) # URL url_item = QTableWidgetItem(resource.get('url', '')) self.resources_table.setItem(i, 1, url_item) except Exception as e: logger.error(f"Error updating resources table: {e}") def change_vulnerability_status(self): """Изменяет статус выбранной уязвимости.""" if not self.current_vulnerability: QMessageBox.warning(self, "Внимание", "Выберите уязвимость для изменения статуса") return dialog = StatusChangeDialog(self.current_vulnerability, self) if dialog.exec_() == QDialog.DialogCode.Accepted: new_status = dialog.get_status() try: # Обновляем статус в базе данных db.update_vulnerability_status(self.current_vulnerability['id'], new_status) # Обновляем статус в интерфейсе self.status_value.setText(new_status) # Обновляем элемент в дереве for i in range(self.vulnerability_tree.topLevelItemCount()): item = self.vulnerability_tree.topLevelItem(i) if item.data(0, Qt.UserRole) == self.current_vulnerability['id']: item.setText(3, new_status) break # Обновляем текущую уязвимость self.current_vulnerability['status'] = new_status QMessageBox.information(self, "Успех", "Статус уязвимости успешно изменен") except Exception as e: logger.error(f"Error changing vulnerability status: {e}") QMessageBox.critical(self, "Ошибка", f"Не удалось изменить статус уязвимости: {e}") def add_comment(self): """Добавляет комментарий к выбранной уязвимости.""" if not self.current_vulnerability: QMessageBox.warning(self, "Внимание", "Выберите уязвимость для добавления комментария") return dialog = CommentDialog(self.current_vulnerability, self) if dialog.exec_() == QDialog.DialogCode.Accepted: comment = dialog.get_comment() try: # Добавляем комментарий в базу данных db.add_vulnerability_comment(self.current_vulnerability['id'], comment) QMessageBox.information(self, "Успех", "Комментарий успешно добавлен") except Exception as e: logger.error(f"Error adding comment: {e}") QMessageBox.critical(self, "Ошибка", f"Не удалось добавить комментарий: {e}") def export_vulnerabilities(self): """Экспортирует отфильтрованные уязвимости.""" try: # Собираем отфильтрованные уязвимости vulnerabilities: List[Dict[str, Any]] = [] for i in range(self.vulnerability_tree.topLevelItemCount()): item = self.vulnerability_tree.topLevelItem(i) if not item.isHidden(): vuln_id = item.data(0, Qt.UserRole) if vuln_id: vulnerability = db.get_vulnerability_by_id(vuln_id) if vulnerability: vulnerabilities.append(vulnerability) if not vulnerabilities: QMessageBox.information(self, "Информация", "Нет уязвимостей для экспорта") return # Диалог выбора формата и файла dialog = ExportDialog(vulnerabilities, self) if dialog.exec_() == QDialog.Accepted: format_type, file_path = dialog.get_export_settings() # Экспортируем уязвимости if format_type == "JSON": with open(file_path, 'w', encoding='utf-8') as f: json.dump(vulnerabilities, f, indent=2, ensure_ascii=False) elif format_type == "CSV": import csv with open(file_path, 'w', newline='', encoding='utf-8') as f: if vulnerabilities: fieldnames = vulnerabilities[0].keys() writer = csv.DictWriter(f, fieldnames=fieldnames) writer.writeheader() writer.writerows(vulnerabilities) elif format_type == "XML": from xml.etree.ElementTree import Element, SubElement, ElementTree root = Element("vulnerabilities") for vuln in vulnerabilities: vuln_elem = SubElement(root, "vulnerability") for key, value in vuln.items(): if isinstance(value, dict): value = json.dumps(value) SubElement(vuln_elem, key).text = str(value) tree = ElementTree(root) tree.write(file_path, encoding='utf-8', xml_declaration=True) QMessageBox.information(self, "Успех", f"Уязвимости успешно экспортированы в {file_path}") # Отправляем сигнал об экспорте self.export_requested.emit(vulnerabilities, format_type) except Exception as e: logger.error(f"Error exporting vulnerabilities: {e}") QMessageBox.critical(self, "Ошибка", f"Не удалось экспортировать уязвимости: {e}") def fix_vulnerability(self): """Запускает процесс исправления уязвимости.""" if not self.current_vulnerability: QMessageBox.warning(self, "Внимание", "Выберите уязвимость для исправления") return try: # Отправляем сигнал о запросе исправления self.fix_requested.emit(self.current_vulnerability) # Показываем диалог прогресса dialog = FixProgressDialog(self.current_vulnerability, self) dialog.exec_() except Exception as e: logger.error(f"Error fixing vulnerability: {e}") QMessageBox.critical(self, "Ошибка", f"Не удалось исправить уязвимость: {e}") def generate_report(self): """Генерирует отчет по уязвимостям.""" try: # Собираем отфильтрованные уязвимости vulnerabilities = [] for i in range(self.vulnerability_tree.topLevelItemCount()): item = self.vulnerability_tree.topLevelItem(i) if not item.isHidden(): vuln_id = item.data(0, Qt.UserRole) if vuln_id: vulnerability = db.get_vulnerability_by_id(vuln_id) if vulnerability: vulnerabilities.append(vulnerability) if not vulnerabilities: QMessageBox.information(self, "Информация", "Нет уязвимостей для генерации отчета") return # Диалог настроек отчета dialog = ReportDialog(vulnerabilities, self) if dialog.exec_() == QDialog.Accepted: report_settings = dialog.get_report_settings() # Генерируем отчет from export.report_generator import ScanReportGenerator generator = ScanReportGenerator() file_path = generator.generate_report(vulnerabilities, report_settings) QMessageBox.information(self, "Успех", f"Отчет успешно сгенерирован: {file_path}") except Exception as e: logger.error(f"Error generating report: {e}") QMessageBox.critical(self, "Ошибка", f"Не удалось сгенерировать отчет: {e}")