├── README.md └── exploit_tracker.py /README.md: -------------------------------------------------------------------------------- 1 | # Exploit Tracker 2 | 3 | ![Python](https://img.shields.io/badge/python-3.8%2B-blue) 4 | ![License](https://img.shields.io/badge/license-MIT-green) 5 | ![Version](https://img.shields.io/badge/version-1.0.0-orange) 6 | ![GUI](https://img.shields.io/badge/GUI-Tkinter-yellowgreen) 7 | 8 | A comprehensive Python GUI application for tracking and analyzing security vulnerabilities from the National Vulnerability Database (NVD). 9 | 10 | ## Features 11 | 12 | - 🚀 **Real-time CVE tracking** with automatic refresh 13 | - 🌍 **Multi-language support** (English/Turkish) 14 | - 🌓 **Theme customization** (Light/Dark modes) 15 | - 🔎 **Advanced filtering** by: 16 | - Severity (Critical/High/Medium/Low) 17 | - Date ranges (Last 7d/1m/1y/Custom) 18 | - Keyword search 19 | - 📁 **Export capabilities** to CSV 20 | - ⚙️ **Customizable settings**: 21 | - Results count (10-100) 22 | - Sort order (Newest/Oldest first) 23 | 24 | ![Ekran görüntüsü 2025-03-30 152910](https://github.com/user-attachments/assets/aee86ec0-6f47-464b-89f6-d641fb01b2b3) 25 | 26 | 27 | ![Ekran görüntüsü 2025-03-30 152817](https://github.com/user-attachments/assets/43dc817d-4b7c-4583-9419-8d0d8425abe5) 28 | 29 | ## Installation 30 | 31 | ### Requirements 32 | - Python 3.8+ 33 | - pip package manager 34 | 35 | 36 | 37 | ☕ Would You Like to Buy Me a Coffee? 38 | 39 | Software development, creating content and sharing knowledge is my passion! If you find my work valuable and would like to support me, you can contribute to my motivation by buying me a coffee. 40 | 41 | 42 | 43 | ![thankyou](https://github.com/user-attachments/assets/0c2e794f-cf9c-49e9-b9b7-4bd0ab51ec53) 44 | 45 | 46 | 47 | 48 | 👉 buymeacoffee.com/coffeak 49 | 50 | Every coffee gives me energy to create new projects. 🚀 51 | Thank you! 🙏 52 | 53 | ### Quick Start 54 | ```bash 55 | # Clone the repository 56 | git clone https://github.com/coffeak/exploit-tracker.git 57 | cd exploit-tracker 58 | 59 | # Install dependencies 60 | pip install -r requirements.txt 61 | 62 | 63 | 64 | 65 | # Run the application 66 | python exploit_tracker.py 67 | 68 | self.API_KEY = "your-api-key-here" # In ExploitTracker class __init__ 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /exploit_tracker.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import tkinter as tk 3 | from tkinter import ttk, messagebox, scrolledtext 4 | import csv 5 | from datetime import datetime, timedelta 6 | import webbrowser 7 | from tkinter import simpledialog 8 | 9 | 10 | class ExploitTracker: 11 | def __init__(self, root): 12 | """Initialize the Exploit Tracker application""" 13 | self.root = root 14 | self.setup_language() 15 | self.setup_theme() 16 | 17 | # API settings 18 | self.NVD_API_URL = "https://services.nvd.nist.gov/rest/json/cves/2.0" 19 | self.API_KEY = "62ee7e1b-00ca-4a79-ab93-a2f4652b0141" # Replace with your API key 20 | 21 | # Initialize UI components 22 | self.setup_ui() 23 | 24 | # Load recent exploits on startup 25 | self.load_recent_exploits() 26 | 27 | def setup_language(self): 28 | """Load language settings""" 29 | self.language = "english" # Default language 30 | self.translations = { 31 | "english": { 32 | "title": "Latest Exploit Tracker", 33 | "auto_refresh": "Auto Refresh (every 5 mins)", 34 | "refresh_now": "Refresh Now", 35 | "search": "Search:", 36 | "results": "Results:", 37 | "date_pub": "Publication Date:", 38 | "custom_date": "Custom Date...", 39 | "severity": "Severity:", 40 | "custom_severity": "Custom Severity...", 41 | "sort_order": "Sort Order:", 42 | "new_to_old": "Newest to Oldest", 43 | "old_to_new": "Oldest to Newest", 44 | "all": "All", 45 | "low": "Low", 46 | "medium": "Medium", 47 | "high": "High", 48 | "critical": "Critical", 49 | "last_7d": "Last 7 Days", 50 | "last_1m": "Last 1 Month", 51 | "last_1y": "Last 1 Year", 52 | "export": "Export to CSV", 53 | "clear": "Clear", 54 | "exit": "Exit", 55 | "status_ready": "Ready", 56 | "status_searching": "Searching... Please wait.", 57 | "status_no_results": "No results found.", 58 | "menu_language": "Language", 59 | "menu_theme": "Theme", 60 | "menu_help": "Help", 61 | "dark_mode": "Dark Mode", 62 | "light_mode": "Light Mode", 63 | "about": "About", 64 | "cve_id": "CVE ID", 65 | "published": "Published", 66 | "description": "Description", 67 | "references": "References", 68 | "total_found": "Total {} exploits found.", 69 | "date_range": "Date Range", 70 | "select_dates": "Select Date Range", 71 | "start_date": "Start Date (YYYY-MM-DD):", 72 | "end_date": "End Date (YYYY-MM-DD):", 73 | "invalid_date": "Invalid date format! Use YYYY-MM-DD", 74 | "severity_filter": "Severity Filter", 75 | "select_severities": "Select Severities to Include", 76 | "developer": "Developed by: www.coffeak.com", 77 | "cancel": "Cancel", 78 | "api_error": "API Error: {}", 79 | "connection_error": "Connection Error: {}" 80 | }, 81 | "turkish": { 82 | "title": "Güncel Exploit Takip Uygulaması", 83 | "auto_refresh": "Otomatik Yenile (5 dakikada bir)", 84 | "refresh_now": "Şimdi Yenile", 85 | "search": "Arama:", 86 | "results": "Sonuç Sayısı:", 87 | "date_pub": "Yayın Tarihi:", 88 | "custom_date": "Özel Tarih...", 89 | "severity": "Önem Derecesi:", 90 | "custom_severity": "Özel Önem Derecesi...", 91 | "sort_order": "Sıralama Düzeni:", 92 | "new_to_old": "Yeniden Eskiye", 93 | "old_to_new": "Eskiden Yeniye", 94 | "all": "Tümü", 95 | "low": "Düşük", 96 | "medium": "Orta", 97 | "high": "Yüksek", 98 | "critical": "Kritik", 99 | "last_7d": "Son 7 Gün", 100 | "last_1m": "Son 1 Ay", 101 | "last_1y": "Son 1 Yıl", 102 | "export": "CSV'ye Aktar", 103 | "clear": "Temizle", 104 | "exit": "Çıkış", 105 | "status_ready": "Hazır", 106 | "status_searching": "Aranıyor... Lütfen bekleyin.", 107 | "status_no_results": "Sonuç bulunamadı.", 108 | "menu_language": "Dil", 109 | "menu_theme": "Tema", 110 | "menu_help": "Yardım", 111 | "dark_mode": "Gece Modu", 112 | "light_mode": "Gündüz Modu", 113 | "about": "Hakkında", 114 | "cve_id": "CVE ID", 115 | "published": "Yayın Tarihi", 116 | "description": "Açıklama", 117 | "references": "Referanslar", 118 | "total_found": "Toplam {} exploit bulundu.", 119 | "date_range": "Tarih Aralığı", 120 | "select_dates": "Tarih Aralığı Seçin", 121 | "start_date": "Başlangıç Tarihi (YYYY-AA-GG):", 122 | "end_date": "Bitiş Tarihi (YYYY-AA-GG):", 123 | "invalid_date": "Geçersiz tarih formatı! YYYY-AA-GG kullanın", 124 | "severity_filter": "Önem Derecesi Filtresi", 125 | "select_severities": "Dahil Edilecek Önem Derecelerini Seçin", 126 | "developer": "Geliştiren: www.coffeak.com", 127 | "cancel": "İptal", 128 | "api_error": "API Hatası: {}", 129 | "connection_error": "Bağlantı Hatası: {}" 130 | } 131 | } 132 | 133 | def setup_theme(self): 134 | """Initialize theme settings""" 135 | self.theme = "light" # Default theme 136 | self.themes = { 137 | "light": { 138 | "bg": "#ffffff", 139 | "fg": "#000000", 140 | "text_bg": "#ffffff", 141 | "text_fg": "#000000", 142 | "button_bg": "#f0f0f0", 143 | "button_fg": "#000000", 144 | "entry_bg": "#ffffff", 145 | "entry_fg": "#000000", 146 | "combobox_bg": "#ffffff", 147 | "combobox_fg": "#000000", 148 | "status_bg": "#f0f0f0", 149 | "status_fg": "#000000" 150 | }, 151 | "dark": { 152 | "bg": "#2d2d2d", 153 | "fg": "#ffffff", 154 | "text_bg": "#1e1e1e", 155 | "text_fg": "#ffffff", 156 | "button_bg": "#3d3d3d", 157 | "button_fg": "#ffffff", 158 | "entry_bg": "#3d3d3d", 159 | "entry_fg": "#ffffff", 160 | "combobox_bg": "#3d3d3d", 161 | "combobox_fg": "#ffffff", 162 | "status_bg": "#1e1e1e", 163 | "status_fg": "#ffffff" 164 | } 165 | } 166 | 167 | # Color tags for severity levels 168 | self.color_tags = { 169 | "CRITICAL": "red", 170 | "HIGH": "orange", 171 | "MEDIUM": "yellow", 172 | "LOW": "green" 173 | } 174 | 175 | def setup_ui(self): 176 | """Initialize the user interface""" 177 | self.root.title(self._("title")) 178 | self.root.geometry("1000x800") 179 | self.root.minsize(800, 600) 180 | 181 | # Setup menu bar 182 | self.setup_menu() 183 | 184 | # Title frame 185 | title_frame = ttk.Frame(self.root) 186 | title_frame.pack(pady=10, fill='x') 187 | ttk.Label(title_frame, text=self._("title"), font=("Arial", 16, "bold")).pack() 188 | 189 | # Control panel 190 | control_frame = ttk.Frame(self.root) 191 | control_frame.pack(pady=10, fill='x', padx=20) 192 | 193 | # Auto-refresh checkbox 194 | self.auto_refresh_var = tk.BooleanVar(value=True) 195 | auto_refresh_btn = ttk.Checkbutton( 196 | control_frame, 197 | text=self._("auto_refresh"), 198 | variable=self.auto_refresh_var, 199 | command=self.toggle_auto_refresh 200 | ) 201 | auto_refresh_btn.grid(row=0, column=0, padx=5, sticky='w') 202 | 203 | # Refresh button 204 | refresh_btn = ttk.Button(control_frame, text=self._("refresh_now"), command=self.load_recent_exploits) 205 | refresh_btn.grid(row=0, column=1, padx=5) 206 | 207 | # Search panel 208 | search_frame = ttk.Frame(self.root) 209 | search_frame.pack(pady=10, fill='x', padx=20) 210 | 211 | ttk.Label(search_frame, text=self._("search")).grid(row=0, column=0, padx=5) 212 | self.search_entry = ttk.Entry(search_frame, width=40) 213 | self.search_entry.grid(row=0, column=1, padx=5) 214 | 215 | ttk.Label(search_frame, text=self._("results")).grid(row=0, column=2, padx=5) 216 | self.result_count = ttk.Combobox(search_frame, values=[10, 25, 50, 100], width=5) 217 | self.result_count.current(0) 218 | self.result_count.grid(row=0, column=3, padx=5) 219 | 220 | search_btn = ttk.Button(search_frame, text=self._("search"), command=self.search_exploits) 221 | search_btn.grid(row=0, column=4, padx=5) 222 | 223 | # Filter options with dropdown style 224 | filter_frame = ttk.Frame(self.root) 225 | filter_frame.pack(pady=5, fill='x', padx=20) 226 | 227 | # Date filter with dropdown 228 | ttk.Label(filter_frame, text=self._("date_pub")).grid(row=0, column=0, padx=5) 229 | self.pub_date = ttk.Combobox( 230 | filter_frame, 231 | values=[ 232 | self._("last_7d"), 233 | self._("last_1m"), 234 | self._("last_1y"), 235 | self._("all"), 236 | self._("custom_date") 237 | ], 238 | state="readonly", 239 | width=15 240 | ) 241 | self.pub_date.current(0) 242 | self.pub_date.grid(row=0, column=1, padx=5) 243 | self.pub_date.bind("<>", self.on_date_selected) 244 | 245 | # Severity filter with dropdown 246 | ttk.Label(filter_frame, text=self._("severity")).grid(row=0, column=2, padx=5) 247 | self.severity = ttk.Combobox( 248 | filter_frame, 249 | values=[ 250 | self._("all"), 251 | self._("low"), 252 | self._("medium"), 253 | self._("high"), 254 | self._("critical"), 255 | self._("custom_severity") 256 | ], 257 | state="readonly", 258 | width=15 259 | ) 260 | self.severity.current(0) 261 | self.severity.grid(row=0, column=3, padx=5) 262 | self.severity.bind("<>", self.on_severity_selected) 263 | 264 | # Sort order dropdown 265 | ttk.Label(filter_frame, text=self._("sort_order")).grid(row=0, column=4, padx=5) 266 | self.sort_order = ttk.Combobox( 267 | filter_frame, 268 | values=[self._("new_to_old"), self._("old_to_new")], 269 | state="readonly", 270 | width=15 271 | ) 272 | self.sort_order.current(0) 273 | self.sort_order.grid(row=0, column=5, padx=5) 274 | 275 | # Results area 276 | result_frame = ttk.Frame(self.root) 277 | result_frame.pack(fill='both', expand=True, padx=20, pady=10) 278 | 279 | self.result_text = scrolledtext.ScrolledText( 280 | result_frame, 281 | wrap=tk.WORD, 282 | width=120, 283 | height=30, 284 | font=("Consolas", 10) 285 | ) 286 | self.result_text.pack(fill='both', expand=True) 287 | 288 | # Configure color tags for severity levels 289 | for severity, color in self.color_tags.items(): 290 | self.result_text.tag_configure(severity, foreground=color) 291 | 292 | # Status bar 293 | self.status_var = tk.StringVar() 294 | self.status_var.set(self._("status_ready")) 295 | status_bar = ttk.Label(self.root, textvariable=self.status_var, relief='sunken') 296 | status_bar.pack(fill='x', padx=5, pady=5) 297 | 298 | # Buttons 299 | button_frame = ttk.Frame(self.root) 300 | button_frame.pack(pady=10, fill='x', padx=20) 301 | 302 | export_btn = ttk.Button(button_frame, text=self._("export"), command=self.export_to_csv) 303 | export_btn.pack(side='left', padx=5) 304 | 305 | clear_btn = ttk.Button(button_frame, text=self._("clear"), command=self.clear_results) 306 | clear_btn.pack(side='left', padx=5) 307 | 308 | exit_btn = ttk.Button(button_frame, text=self._("exit"), command=self.root.quit) 309 | exit_btn.pack(side='right', padx=5) 310 | 311 | # Auto-refresh timer 312 | self.auto_refresh_id = None 313 | 314 | # Variables for custom selections 315 | self.custom_start_date = None 316 | self.custom_end_date = None 317 | self.selected_severities = [] 318 | 319 | # Apply theme 320 | self.apply_theme() 321 | 322 | def on_date_selected(self, event): 323 | """Handle date selection from dropdown""" 324 | if self.pub_date.get() == self._("custom_date"): 325 | self.select_custom_date() 326 | 327 | def on_severity_selected(self, event): 328 | """Handle severity selection from dropdown""" 329 | if self.severity.get() == self._("custom_severity"): 330 | self.select_custom_severity() 331 | 332 | def setup_menu(self): 333 | """Initialize the menu bar""" 334 | menubar = tk.Menu(self.root) 335 | 336 | # Language menu 337 | language_menu = tk.Menu(menubar, tearoff=0) 338 | language_menu.add_command(label="English", command=lambda: self.change_language("english")) 339 | language_menu.add_command(label="Türkçe", command=lambda: self.change_language("turkish")) 340 | 341 | # Theme menu 342 | theme_menu = tk.Menu(menubar, tearoff=0) 343 | theme_menu.add_command(label=self._("light_mode"), command=lambda: self.change_theme("light")) 344 | theme_menu.add_command(label=self._("dark_mode"), command=lambda: self.change_theme("dark")) 345 | 346 | # Help menu 347 | help_menu = tk.Menu(menubar, tearoff=0) 348 | help_menu.add_command(label=self._("about"), command=self.show_about) 349 | 350 | menubar.add_cascade(label=self._("menu_language"), menu=language_menu) 351 | menubar.add_cascade(label=self._("menu_theme"), menu=theme_menu) 352 | menubar.add_cascade(label=self._("menu_help"), menu=help_menu) 353 | 354 | self.root.config(menu=menubar) 355 | 356 | def _(self, text_key): 357 | """Get translated text for the given key""" 358 | return self.translations[self.language].get(text_key, text_key) 359 | 360 | def change_language(self, lang): 361 | """Change application language""" 362 | self.language = lang 363 | self.refresh_ui() 364 | 365 | def change_theme(self, theme): 366 | """Change application theme""" 367 | self.theme = theme 368 | self.apply_theme() 369 | 370 | def apply_theme(self): 371 | """Apply current theme to all widgets""" 372 | theme = self.themes[self.theme] 373 | 374 | # Main window 375 | self.root.config(bg=theme["bg"]) 376 | 377 | # Update all widgets 378 | for widget in self.root.winfo_children(): 379 | self.update_widget_theme(widget, theme) 380 | 381 | # ScrolledText specific settings 382 | self.result_text.config( 383 | bg=theme["text_bg"], 384 | fg=theme["text_fg"], 385 | insertbackground=theme["fg"] 386 | ) 387 | 388 | def update_widget_theme(self, widget, theme): 389 | """Update theme for a single widget""" 390 | if isinstance(widget, ttk.Frame) or isinstance(widget, ttk.LabelFrame): 391 | widget.config(style="TFrame") 392 | elif isinstance(widget, ttk.Label): 393 | widget.config(style="TLabel") 394 | elif isinstance(widget, ttk.Button): 395 | widget.config(style="TButton") 396 | elif isinstance(widget, ttk.Entry): 397 | widget.config(style="TEntry") 398 | elif isinstance(widget, ttk.Combobox): 399 | widget.config(style="TCombobox") 400 | 401 | # Update children widgets 402 | for child in widget.winfo_children(): 403 | self.update_widget_theme(child, theme) 404 | 405 | def refresh_ui(self): 406 | """Refresh UI after language change""" 407 | for widget in self.root.winfo_children(): 408 | widget.destroy() 409 | 410 | self.setup_ui() 411 | 412 | def toggle_auto_refresh(self): 413 | """Toggle auto-refresh on/off""" 414 | if self.auto_refresh_var.get(): 415 | self.start_auto_refresh() 416 | else: 417 | self.stop_auto_refresh() 418 | 419 | def start_auto_refresh(self): 420 | """Start auto-refresh timer""" 421 | self.auto_refresh() 422 | 423 | def stop_auto_refresh(self): 424 | """Stop auto-refresh timer""" 425 | if self.auto_refresh_id: 426 | self.root.after_cancel(self.auto_refresh_id) 427 | self.auto_refresh_id = None 428 | 429 | def auto_refresh(self): 430 | """Perform auto-refresh operation""" 431 | if self.auto_refresh_var.get(): 432 | self.load_recent_exploits() 433 | # Refresh every 5 minutes (300000 milliseconds) 434 | self.auto_refresh_id = self.root.after(300000, self.auto_refresh) 435 | 436 | def load_recent_exploits(self): 437 | """Load recent exploits (last 7 days by default)""" 438 | self.pub_date.current(0) # Select "Last 7 Days" option 439 | self.search_entry.delete(0, tk.END) # Clear search box 440 | self.search_exploits() 441 | 442 | def search_exploits(self): 443 | """Search and display exploits""" 444 | query = self.search_entry.get() 445 | results_count = int(self.result_count.get()) 446 | pub_date_filter = self.pub_date.get() 447 | severity_filter = self.severity.get() 448 | sort_order = self.sort_order.get() 449 | 450 | self.status_var.set(self._("status_searching")) 451 | self.result_text.delete(1.0, tk.END) 452 | self.result_text.insert(tk.END, self._("status_searching") + "\n") 453 | self.root.update() 454 | 455 | exploits = self.get_exploits(query, results_count, pub_date_filter, severity_filter) 456 | 457 | if not exploits: 458 | self.result_text.insert(tk.END, self._("status_no_results") + "\n") 459 | self.status_var.set(self._("status_no_results")) 460 | return 461 | 462 | # Sort exploits based on user selection 463 | reverse_sort = True if sort_order == self._("new_to_old") else False 464 | exploits.sort(key=lambda x: x['published'], reverse=reverse_sort) 465 | 466 | self.result_text.delete(1.0, tk.END) 467 | 468 | # Display exploits 469 | for exploit in exploits: 470 | self.result_text.insert(tk.END, f"\n{self._('cve_id')}: {exploit['id']}\n") 471 | self.result_text.insert(tk.END, f"{self._('published')}: {exploit['published']}\n") 472 | 473 | if exploit.get('severity'): 474 | severity = exploit['severity'].upper() 475 | self.result_text.insert(tk.END, f"{self._('severity')}: ") 476 | self.result_text.insert(tk.END, severity, severity) 477 | self.result_text.insert(tk.END, "\n") 478 | 479 | self.result_text.insert(tk.END, f"{self._('description')}: {exploit['description']}\n") 480 | 481 | if exploit.get('references'): 482 | self.result_text.insert(tk.END, f"{self._('references')}:\n") 483 | for ref in exploit['references'][:3]: # Show first 3 references 484 | self.result_text.insert(tk.END, f"- {ref}\n") 485 | 486 | self.result_text.insert(tk.END, "-" * 100 + "\n") 487 | 488 | self.result_text.insert(tk.END, "\n" + self._("total_found").format(len(exploits)) + "\n") 489 | self.status_var.set(self._("total_found").format(len(exploits)) + f" - {datetime.now().strftime('%H:%M:%S')}") 490 | 491 | def select_custom_date(self): 492 | """Show dialog for custom date range selection""" 493 | dialog = tk.Toplevel(self.root) 494 | dialog.title(self._("date_range")) 495 | dialog.transient(self.root) 496 | dialog.grab_set() 497 | 498 | ttk.Label(dialog, text=self._("select_dates")).pack(pady=5) 499 | 500 | # Start date 501 | start_frame = ttk.Frame(dialog) 502 | start_frame.pack(pady=5, padx=10, fill='x') 503 | ttk.Label(start_frame, text=self._("start_date")).pack(side='left') 504 | start_date_entry = ttk.Entry(start_frame) 505 | start_date_entry.pack(side='left', padx=5) 506 | 507 | # End date 508 | end_frame = ttk.Frame(dialog) 509 | end_frame.pack(pady=5, padx=10, fill='x') 510 | ttk.Label(end_frame, text=self._("end_date")).pack(side='left') 511 | end_date_entry = ttk.Entry(end_frame) 512 | end_date_entry.pack(side='left', padx=5) 513 | 514 | # Default dates (last 7 days) 515 | default_end = datetime.now() 516 | default_start = default_end - timedelta(days=7) 517 | start_date_entry.insert(0, default_start.strftime('%Y-%m-%d')) 518 | end_date_entry.insert(0, default_end.strftime('%Y-%m-%d')) 519 | 520 | # Buttons 521 | button_frame = ttk.Frame(dialog) 522 | button_frame.pack(pady=10) 523 | 524 | def on_ok(): 525 | try: 526 | start_date = datetime.strptime(start_date_entry.get(), '%Y-%m-%d') 527 | end_date = datetime.strptime(end_date_entry.get(), '%Y-%m-%d') 528 | 529 | if start_date > end_date: 530 | messagebox.showerror("Error", "Start date cannot be after end date!") 531 | return 532 | 533 | self.custom_start_date = start_date.strftime('%Y-%m-%dT%H:%M:%S') 534 | self.custom_end_date = end_date.strftime('%Y-%m-%dT%H:%M:%S') 535 | 536 | dialog.destroy() 537 | self.search_exploits() 538 | except ValueError: 539 | messagebox.showerror("Error", self._("invalid_date")) 540 | 541 | ttk.Button(button_frame, text="OK", command=on_ok).pack(side='left', padx=5) 542 | ttk.Button(button_frame, text=self._("cancel"), command=dialog.destroy).pack(side='left', padx=5) 543 | 544 | def select_custom_severity(self): 545 | """Show dialog for custom severity selection""" 546 | dialog = tk.Toplevel(self.root) 547 | dialog.title(self._("severity_filter")) 548 | dialog.transient(self.root) 549 | dialog.grab_set() 550 | 551 | ttk.Label(dialog, text=self._("select_severities")).pack(pady=5) 552 | 553 | # Severity checkboxes 554 | sev_vars = { 555 | "CRITICAL": tk.BooleanVar(value=True), 556 | "HIGH": tk.BooleanVar(value=True), 557 | "MEDIUM": tk.BooleanVar(value=True), 558 | "LOW": tk.BooleanVar(value=True) 559 | } 560 | 561 | for severity, var in sev_vars.items(): 562 | cb = ttk.Checkbutton(dialog, text=severity, variable=var) 563 | cb.pack(anchor='w', padx=20) 564 | 565 | # Buttons 566 | button_frame = ttk.Frame(dialog) 567 | button_frame.pack(pady=10) 568 | 569 | def on_ok(): 570 | self.selected_severities = [sev for sev, var in sev_vars.items() if var.get()] 571 | 572 | if not self.selected_severities: 573 | messagebox.showerror("Error", "At least one severity must be selected!") 574 | return 575 | 576 | dialog.destroy() 577 | self.search_exploits() 578 | 579 | ttk.Button(button_frame, text="OK", command=on_ok).pack(side='left', padx=5) 580 | ttk.Button(button_frame, text=self._("cancel"), command=dialog.destroy).pack(side='left', padx=5) 581 | 582 | def get_exploits(self, query=None, results_count=10, pub_date_filter=None, severity_filter=None): 583 | """Fetch exploits from API""" 584 | headers = {"apiKey": self.API_KEY} if self.API_KEY else {} 585 | params = { 586 | "resultsPerPage": results_count, 587 | "startIndex": 0 588 | } 589 | 590 | if query: 591 | params["keywordSearch"] = query 592 | 593 | # Apply date filter 594 | date_map = { 595 | self._("last_7d"): timedelta(days=7), 596 | self._("last_1m"): timedelta(days=30), 597 | self._("last_1y"): timedelta(days=365) 598 | } 599 | 600 | if hasattr(self, 'custom_start_date') and hasattr(self, 'custom_end_date'): 601 | params["pubStartDate"] = self.custom_start_date 602 | params["pubEndDate"] = self.custom_end_date 603 | elif pub_date_filter in date_map: 604 | start_date = (datetime.now() - date_map[pub_date_filter]).strftime('%Y-%m-%dT%H:%M:%S') 605 | params["pubStartDate"] = start_date 606 | params["pubEndDate"] = datetime.now().strftime('%Y-%m-%dT%H:%M:%S') 607 | 608 | # Apply severity filter 609 | severity_map = { 610 | self._("low"): "LOW", 611 | self._("medium"): "MEDIUM", 612 | self._("high"): "HIGH", 613 | self._("critical"): "CRITICAL" 614 | } 615 | 616 | if hasattr(self, 'selected_severities') and self.selected_severities: 617 | severity = None # Custom selection made 618 | else: 619 | severity = severity_map.get(severity_filter) 620 | 621 | try: 622 | response = requests.get(self.NVD_API_URL, headers=headers, params=params, timeout=30) 623 | 624 | # Check for API errors 625 | if response.status_code == 404: 626 | messagebox.showerror("Error", self._("api_error").format("Endpoint not found (404)")) 627 | return [] 628 | elif response.status_code == 403: 629 | messagebox.showerror("Error", self._("api_error").format("API limit exceeded or invalid API key!")) 630 | return [] 631 | elif response.status_code != 200: 632 | messagebox.showerror("Error", self._("api_error").format(f"{response.status_code}\n{response.text}")) 633 | return [] 634 | 635 | data = response.json() 636 | exploits = [] 637 | 638 | for vuln in data.get('vulnerabilities', []): 639 | cve = vuln['cve'] 640 | 641 | exploit = { 642 | 'id': cve['id'], 643 | 'published': cve['published'], 644 | 'description': cve['descriptions'][0]['value'], 645 | 'references': [ref['url'] for ref in cve.get('references', [])] 646 | } 647 | 648 | # Add severity information 649 | if 'metrics' in cve and 'cvssMetricV31' in cve['metrics']: 650 | exploit['severity'] = cve['metrics']['cvssMetricV31'][0]['cvssData']['baseSeverity'] 651 | elif 'metrics' in cve and 'cvssMetricV2' in cve['metrics']: 652 | exploit['severity'] = cve['metrics']['cvssMetricV2'][0]['baseSeverity'] 653 | else: 654 | exploit['severity'] = "UNKNOWN" 655 | 656 | # Apply severity filter 657 | if severity and exploit.get('severity', '').upper() != severity: 658 | continue 659 | 660 | # Check for custom severity selection 661 | if hasattr(self, 'selected_severities') and self.selected_severities: 662 | if exploit.get('severity', '').upper() not in self.selected_severities: 663 | continue 664 | 665 | exploits.append(exploit) 666 | 667 | return exploits 668 | 669 | except requests.exceptions.RequestException as e: 670 | messagebox.showerror("Error", self._("connection_error").format(e)) 671 | return [] 672 | 673 | def export_to_csv(self): 674 | """Export results to CSV file""" 675 | exploits = self.get_exploits( 676 | self.search_entry.get(), 677 | int(self.result_count.get()), 678 | self.pub_date.get(), 679 | self.severity.get() 680 | ) 681 | 682 | if not exploits: 683 | messagebox.showwarning("Warning", self._("status_no_results")) 684 | return 685 | 686 | filename = f"exploits_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv" 687 | 688 | try: 689 | with open(filename, mode='w', newline='', encoding='utf-8') as file: 690 | writer = csv.writer(file) 691 | writer.writerow([ 692 | self._("cve_id"), 693 | self._("published"), 694 | self._("severity"), 695 | self._("description"), 696 | self._("references") 697 | ]) 698 | 699 | for exploit in exploits: 700 | refs = "\n".join(exploit.get('references', [])) 701 | writer.writerow([ 702 | exploit['id'], 703 | exploit['published'], 704 | exploit.get('severity', self._("all")), 705 | exploit['description'], 706 | refs 707 | ]) 708 | 709 | messagebox.showinfo("Success", f"Data saved to {filename}!") 710 | except Exception as e: 711 | messagebox.showerror("Error", f"Error while saving file: {e}") 712 | 713 | def clear_results(self): 714 | """Clear search results""" 715 | self.result_text.delete(1.0, tk.END) 716 | self.search_entry.delete(0, tk.END) 717 | self.status_var.set(self._("status_ready")) 718 | 719 | def show_about(self): 720 | """Show about information""" 721 | about_text = f""" 722 | {self._("title")} v1.0 723 | 724 | {self._("developer")} 725 | 726 | This application tracks the latest security exploits 727 | from the National Vulnerability Database (NVD). 728 | 729 | Features: 730 | - Real-time exploit tracking 731 | - Multi-language support 732 | - Dark/light theme 733 | - Custom date range filtering 734 | - Custom severity filtering 735 | - Sort by newest or oldest 736 | - Export to CSV 737 | 738 | Developed with Python and Tkinter 739 | """ 740 | messagebox.showinfo(self._("about"), about_text) 741 | 742 | # Optionally open website 743 | if messagebox.askyesno("Visit Website", "Do you want to visit Coffeak website?"): 744 | webbrowser.open("https://www.coffeak.com") 745 | 746 | 747 | if __name__ == "__main__": 748 | root = tk.Tk() 749 | 750 | # Configure ttk styles 751 | style = ttk.Style() 752 | style.theme_use('clam') 753 | 754 | # Create application instance 755 | app = ExploitTracker(root) 756 | 757 | root.mainloop() --------------------------------------------------------------------------------