diff --git a/.env.example b/.env.example index 10049c0..785ac9c 100644 --- a/.env.example +++ b/.env.example @@ -2,6 +2,11 @@ # Get your free API key at: https://nvd.nist.gov/developers/request-an-api-key NVD_API_KEY=your_nvd_api_key_here +# GitHub API Configuration (Optional - for exploit analysis) +# Get your personal access token at: https://github.com/settings/tokens +# Only needs "public_repo" scope for searching public repositories +GITHUB_TOKEN=your_github_token_here + # Database Configuration (Docker Compose will use defaults) # DATABASE_URL=postgresql://cve_user:cve_password@localhost:5432/cve_sigma_db diff --git a/README.md b/README.md index b39f6a1..0c8b25d 100644 --- a/README.md +++ b/README.md @@ -5,10 +5,13 @@ An automated platform that fetches CVE data and automatically generates SIGMA ru ## Features - **Automated CVE Fetching**: Regularly polls the NVD (National Vulnerability Database) for CVEs from July 2025 -- **Intelligent SIGMA Rule Generation**: Automatically creates SIGMA rules based on CVE characteristics +- **GitHub Exploit Analysis**: Automatically searches GitHub for exploit code related to each CVE +- **Intelligent SIGMA Rule Generation**: Creates SIGMA rules based on CVE characteristics AND actual exploit code +- **Exploit-Based Detection**: Enhanced rules using real indicators extracted from GitHub exploits - **Modern Web Interface**: React-based UI for browsing CVEs and managing SIGMA rules - **Real-time Updates**: Background tasks keep CVE data current with current 2025 vulnerabilities - **Rule Templates**: Configurable templates for different types of vulnerabilities +- **MITRE ATT&CK Mapping**: Automatic mapping to MITRE ATT&CK techniques - **API Testing**: Built-in NVD API connectivity testing - **Enhanced Error Handling**: Robust fallback mechanisms and detailed logging - **Docker Compose**: Easy deployment and orchestration @@ -101,9 +104,25 @@ curl -X POST http://localhost:8000/api/fetch-cves ### Environment Variables - `DATABASE_URL`: PostgreSQL connection string -- `NVD_API_KEY`: Optional NVD API key for higher rate limits +- `NVD_API_KEY`: Optional NVD API key for higher rate limits (5→50 requests/30s) +- `GITHUB_TOKEN`: Optional GitHub personal access token for exploit analysis - `REACT_APP_API_URL`: Backend API URL for frontend +### GitHub Integration (Optional) + +For enhanced SIGMA rule generation with exploit analysis: + +1. **Create GitHub Token**: Visit https://github.com/settings/tokens +2. **Required Permissions**: Only needs "public_repo" scope for searching public repositories +3. **Add to Environment**: `GITHUB_TOKEN=your_token_here` in `.env` file +4. **Benefits**: + - Automatically searches for CVE-related exploit code + - Extracts real indicators (processes, files, network connections) + - Generates more accurate and specific SIGMA rules + - Higher confidence ratings for exploit-based rules + +**Rate Limits**: 5000 requests/hour with token, 60/hour without + ### Rule Templates The application includes pre-configured rule templates for: @@ -115,19 +134,55 @@ Additional templates can be added to the database via the `rule_templates` table ## SIGMA Rule Generation Logic -The rule generation process: +The enhanced rule generation process: 1. **CVE Analysis**: Analyzes CVE description and affected products -2. **Template Selection**: Chooses appropriate SIGMA rule template -3. **Indicator Extraction**: Extracts suspicious processes, ports, or file patterns -4. **Rule Population**: Fills template with CVE-specific data -5. **Confidence Scoring**: Assigns confidence level based on CVSS score +2. **GitHub Exploit Search**: Searches GitHub for exploit code using multiple query strategies +3. **Code Analysis**: Extracts specific indicators from exploit code: + - Process names and command lines + - File paths and registry keys + - Network connections and ports + - PowerShell commands and scripts + - Command execution patterns +4. **Template Selection**: Chooses appropriate SIGMA rule template based on exploit analysis +5. **Enhanced Rule Population**: Fills template with real exploit indicators +6. **MITRE ATT&CK Mapping**: Maps to specific MITRE ATT&CK techniques +7. **Confidence Scoring**: Higher confidence for exploit-based rules + +### Rule Quality Levels + +- **Basic Rules**: Generated from CVE description only +- **Exploit-Based Rules**: Enhanced with GitHub exploit analysis (marked with 🔍) +- **Confidence Ratings**: + - **High**: CVSS ≥9.0 + exploit analysis + - **Medium**: CVSS ≥7.0 or exploit analysis + - **Low**: Basic CVE description only ### Template Matching -- **Process Execution**: Keywords like "process", "execution", "command" -- **Network Connection**: Keywords like "network", "remote", "connection" -- **File Modification**: Keywords like "file", "write", "filesystem" +- **PowerShell Execution**: Exploit contains PowerShell scripts or cmdlets +- **Process Execution**: Exploit shows process creation or command execution +- **Network Connection**: Exploit demonstrates network communications +- **File Modification**: Exploit involves file system operations + +### Example Enhanced Rule + +```yaml +title: CVE-2025-1234 Exploit-Based Detection +description: Detection for CVE-2025-1234 remote code execution [Enhanced with GitHub exploit analysis] +tags: + - attack.t1059.001 + - cve-2025-1234 + - exploit.github +detection: + selection: + Image|contains: + - "powershell.exe" + - "malicious_payload.exe" + - "reverse_shell.ps1" + condition: selection +level: high +``` ## Development @@ -217,7 +272,8 @@ docker-compose ps ### API Key Setup -For optimal performance, get a free NVD API key: +**NVD API (Recommended)** +For optimal CVE fetching performance: 1. Visit: https://nvd.nist.gov/developers/request-an-api-key 2. Add to your `.env` file: `NVD_API_KEY=your_key_here` 3. Restart the application @@ -225,6 +281,16 @@ For optimal performance, get a free NVD API key: Without an API key: 5 requests per 30 seconds With an API key: 50 requests per 30 seconds +**GitHub API (Optional)** +For enhanced exploit-based SIGMA rules: +1. Visit: https://github.com/settings/tokens +2. Create token with "public_repo" scope +3. Add to your `.env` file: `GITHUB_TOKEN=your_token_here` +4. Restart the application + +Without a GitHub token: Basic rules only +With a GitHub token: Enhanced rules with exploit analysis (🔍 Exploit-Based) + ### Rate Limits Without an API key, NVD limits requests to 5 per 30 seconds. With an API key, the limit increases to 50 per 30 seconds. diff --git a/backend/main.py b/backend/main.py index 7494cba..d1ff519 100644 --- a/backend/main.py +++ b/backend/main.py @@ -15,6 +15,10 @@ from typing import List, Optional from pydantic import BaseModel import asyncio from contextlib import asynccontextmanager +import base64 +from github import Github +from urllib.parse import urlparse +import hashlib # Database setup DATABASE_URL = os.getenv("DATABASE_URL", "postgresql://cve_user:cve_password@localhost:5432/cve_sigma_db") @@ -49,6 +53,9 @@ class SigmaRule(Base): log_source = Column(String(100)) confidence_level = Column(String(20)) auto_generated = Column(Boolean, default=True) + exploit_based = Column(Boolean, default=False) + github_repos = Column(ARRAY(String)) + exploit_indicators = Column(Text) # JSON string of extracted indicators created_at = Column(TIMESTAMP, default=datetime.utcnow) updated_at = Column(TIMESTAMP, default=datetime.utcnow) @@ -85,12 +92,271 @@ class SigmaRuleResponse(BaseModel): log_source: Optional[str] = None confidence_level: Optional[str] = None auto_generated: bool = True + exploit_based: bool = False + github_repos: Optional[List[str]] = None + exploit_indicators: Optional[str] = None created_at: datetime class Config: from_attributes = True -# CVE and SIGMA Rule Generator Service +# GitHub Exploit Analysis Service +class GitHubExploitAnalyzer: + def __init__(self): + self.github_token = os.getenv("GITHUB_TOKEN") + self.github = Github(self.github_token) if self.github_token else None + + async def search_exploits_for_cve(self, cve_id: str) -> List[dict]: + """Search GitHub for exploit code related to a CVE""" + if not self.github: + print(f"No GitHub token configured, skipping exploit search for {cve_id}") + return [] + + try: + print(f"Searching GitHub for exploits for {cve_id}") + + # Search queries to find exploit code + search_queries = [ + f"{cve_id} exploit", + f"{cve_id} poc", + f"{cve_id} vulnerability", + f'"{cve_id}" exploit code', + f"{cve_id.replace('-', '_')} exploit" + ] + + exploits = [] + seen_repos = set() + + for query in search_queries[:2]: # Limit to 2 queries to avoid rate limits + try: + # Search repositories + repos = self.github.search_repositories( + query=query, + sort="updated", + order="desc" + ) + + # Get top 5 results per query + for repo in repos[:5]: + if repo.full_name in seen_repos: + continue + seen_repos.add(repo.full_name) + + # Analyze repository + exploit_info = await self._analyze_repository(repo, cve_id) + if exploit_info: + exploits.append(exploit_info) + + if len(exploits) >= 10: # Limit total exploits + break + + if len(exploits) >= 10: + break + + except Exception as e: + print(f"Error searching GitHub with query '{query}': {str(e)}") + continue + + print(f"Found {len(exploits)} potential exploits for {cve_id}") + return exploits + + except Exception as e: + print(f"Error searching GitHub for {cve_id}: {str(e)}") + return [] + + async def _analyze_repository(self, repo, cve_id: str) -> Optional[dict]: + """Analyze a GitHub repository for exploit code""" + try: + # Check if repo name or description mentions the CVE + repo_text = f"{repo.name} {repo.description or ''}".lower() + if cve_id.lower() not in repo_text and cve_id.replace('-', '_').lower() not in repo_text: + return None + + # Get repository contents + exploit_files = [] + indicators = { + 'processes': set(), + 'files': set(), + 'registry': set(), + 'network': set(), + 'commands': set(), + 'powershell': set(), + 'urls': set() + } + + try: + contents = repo.get_contents("") + for content in contents[:20]: # Limit files to analyze + if content.type == "file" and self._is_exploit_file(content.name): + file_analysis = await self._analyze_file_content(repo, content, cve_id) + if file_analysis: + exploit_files.append(file_analysis) + # Merge indicators + for key, values in file_analysis.get('indicators', {}).items(): + if key in indicators: + indicators[key].update(values) + + except Exception as e: + print(f"Error analyzing repo contents for {repo.full_name}: {str(e)}") + + if not exploit_files: + return None + + return { + 'repo_name': repo.full_name, + 'repo_url': repo.html_url, + 'description': repo.description, + 'language': repo.language, + 'stars': repo.stargazers_count, + 'updated': repo.updated_at.isoformat(), + 'files': exploit_files, + 'indicators': {k: list(v) for k, v in indicators.items()} + } + + except Exception as e: + print(f"Error analyzing repository {repo.full_name}: {str(e)}") + return None + + def _is_exploit_file(self, filename: str) -> bool: + """Check if a file is likely to contain exploit code""" + exploit_extensions = ['.py', '.ps1', '.sh', '.c', '.cpp', '.js', '.rb', '.pl', '.php', '.java'] + exploit_names = ['exploit', 'poc', 'payload', 'shell', 'reverse', 'bind', 'attack'] + + filename_lower = filename.lower() + + # Check extension + if not any(filename_lower.endswith(ext) for ext in exploit_extensions): + return False + + # Check filename for exploit-related terms + return any(term in filename_lower for term in exploit_names) or 'cve' in filename_lower + + async def _analyze_file_content(self, repo, file_content, cve_id: str) -> Optional[dict]: + """Analyze individual file content for exploit indicators""" + try: + if file_content.size > 100000: # Skip files larger than 100KB + return None + + # Decode file content + content = file_content.decoded_content.decode('utf-8', errors='ignore') + + # Check if file actually mentions the CVE + if cve_id.lower() not in content.lower() and cve_id.replace('-', '_').lower() not in content.lower(): + return None + + indicators = self._extract_indicators_from_code(content, file_content.name) + + if not any(indicators.values()): + return None + + return { + 'filename': file_content.name, + 'path': file_content.path, + 'size': file_content.size, + 'indicators': indicators + } + + except Exception as e: + print(f"Error analyzing file {file_content.name}: {str(e)}") + return None + + def _extract_indicators_from_code(self, content: str, filename: str) -> dict: + """Extract security indicators from exploit code""" + indicators = { + 'processes': set(), + 'files': set(), + 'registry': set(), + 'network': set(), + 'commands': set(), + 'powershell': set(), + 'urls': set() + } + + # Process patterns + process_patterns = [ + r'CreateProcess[AW]?\s*\(\s*["\']([^"\']+)["\']', + r'ShellExecute[AW]?\s*\([^,]*,\s*["\']([^"\']+)["\']', + r'system\s*\(\s*["\']([^"\']+)["\']', + r'exec\s*\(\s*["\']([^"\']+)["\']', + r'subprocess\.(?:call|run|Popen)\s*\(\s*["\']([^"\']+)["\']' + ] + + # File patterns + file_patterns = [ + r'(?:fopen|CreateFile|WriteFile|ReadFile)\s*\(\s*["\']([^"\']+\.[a-zA-Z0-9]+)["\']', + r'(?:copy|move|del|rm)\s+["\']?([^\s"\']+\.[a-zA-Z0-9]+)["\']?', + r'\\\\[^\\]+\\[^\\]+\\([^\\]+\.[a-zA-Z0-9]+)', + r'[C-Z]:\\\\[^\\]+\\\\([^\\]+\.[a-zA-Z0-9]+)' + ] + + # Registry patterns + registry_patterns = [ + r'(?:RegOpenKey|RegSetValue|RegCreateKey)\s*\([^,]*,\s*["\']([^"\']+)["\']', + r'HKEY_[A-Z_]+\\\\([^"\'\\]+)', + r'reg\s+add\s+["\']?([^"\'\\]+\\\\[^"\']+)["\']?' + ] + + # Network patterns + network_patterns = [ + r'(?:connect|bind|listen)\s*\([^,]*,\s*(\d+)', + r'socket\.connect\s*\(\s*\(["\']?([^"\']+)["\']?,\s*(\d+)\)', + r'(?:http|https|ftp)://([^\s"\'<>]+)', + r'(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}):(\d+)' + ] + + # PowerShell patterns + powershell_patterns = [ + r'(?:powershell|pwsh)\s+(?:-[a-zA-Z]+\s+)*["\']?([^"\']+)["\']?', + r'Invoke-(?:Expression|Command|WebRequest|RestMethod)\s+["\']?([^"\']+)["\']?', + r'Start-Process\s+["\']?([^"\']+)["\']?', + r'Get-Process\s+["\']?([^"\']+)["\']?' + ] + + # Command patterns + command_patterns = [ + r'(?:cmd|command)\s+(?:/[a-zA-Z]+\s+)*["\']?([^"\']+)["\']?', + r'(?:ping|nslookup|netstat|tasklist|wmic)\s+([^\s"\']+)', + r'(?:net|sc|schtasks)\s+[a-zA-Z]+\s+([^\s"\']+)' + ] + + # Extract indicators using regex patterns + patterns = { + 'processes': process_patterns, + 'files': file_patterns, + 'registry': registry_patterns, + 'powershell': powershell_patterns, + 'commands': command_patterns + } + + for category, pattern_list in patterns.items(): + for pattern in pattern_list: + matches = re.findall(pattern, content, re.IGNORECASE | re.MULTILINE) + for match in matches: + if isinstance(match, tuple): + indicators[category].add(match[0]) + else: + indicators[category].add(match) + + # Special handling for network indicators + for pattern in network_patterns: + matches = re.findall(pattern, content, re.IGNORECASE) + for match in matches: + if isinstance(match, tuple): + if len(match) >= 2: + indicators['network'].add(f"{match[0]}:{match[1]}") + else: + indicators['network'].add(match[0]) + else: + indicators['network'].add(match) + + # Convert sets to lists and filter out empty/invalid indicators + cleaned_indicators = {} + for key, values in indicators.items(): + cleaned_values = [v for v in values if v and len(v.strip()) > 2 and len(v) < 200] + if cleaned_values: + cleaned_indicators[key] = cleaned_values[:10] # Limit to 10 per category + + return cleaned_indicators class CVESigmaService: def __init__(self, db: Session): self.db = db @@ -207,11 +473,33 @@ class CVESigmaService: self.db.add(sigma_rule) return sigma_rule - def _select_template(self, description: str, affected_products: List[str]): - """Select appropriate SIGMA rule template""" + def _select_template(self, description: str, affected_products: List[str], exploit_indicators: dict = None): + """Select appropriate SIGMA rule template based on CVE and exploit analysis""" templates = self.db.query(RuleTemplate).all() - # Simple template selection logic + # If we have exploit indicators, use them to determine the best template + if exploit_indicators: + if exploit_indicators.get('powershell'): + powershell_template = next((t for t in templates if "PowerShell" in t.template_name), None) + if powershell_template: + return powershell_template + + if exploit_indicators.get('network'): + network_template = next((t for t in templates if "Network Connection" in t.template_name), None) + if network_template: + return network_template + + if exploit_indicators.get('files'): + file_template = next((t for t in templates if "File Modification" in t.template_name), None) + if file_template: + return file_template + + if exploit_indicators.get('processes') or exploit_indicators.get('commands'): + process_template = next((t for t in templates if "Process Execution" in t.template_name), None) + if process_template: + return process_template + + # Fallback to original logic if any("windows" in p or "microsoft" in p for p in affected_products): if "process" in description or "execution" in description: return next((t for t in templates if "Process Execution" in t.template_name), None) @@ -223,24 +511,50 @@ class CVESigmaService: # Default to process execution template return next((t for t in templates if "Process Execution" in t.template_name), None) - def _populate_template(self, cve: CVE, template: RuleTemplate) -> str: - """Populate template with CVE-specific data""" + def _populate_template(self, cve: CVE, template: RuleTemplate, exploit_indicators: dict = None) -> str: + """Populate template with CVE-specific data and exploit indicators""" try: - # Extract suspicious indicators from description - suspicious_processes = self._extract_suspicious_indicators(cve.description, "process") - suspicious_ports = self._extract_suspicious_indicators(cve.description, "port") - file_patterns = self._extract_suspicious_indicators(cve.description, "file") + # Use exploit indicators if available, otherwise extract from description + if exploit_indicators: + suspicious_processes = exploit_indicators.get('processes', []) + exploit_indicators.get('commands', []) + suspicious_ports = [] + file_patterns = exploit_indicators.get('files', []) + + # Extract ports from network indicators + for net_indicator in exploit_indicators.get('network', []): + if ':' in str(net_indicator): + try: + port = int(str(net_indicator).split(':')[-1]) + suspicious_ports.append(port) + except ValueError: + pass + else: + # Fallback to original extraction + suspicious_processes = self._extract_suspicious_indicators(cve.description, "process") + suspicious_ports = self._extract_suspicious_indicators(cve.description, "port") + file_patterns = self._extract_suspicious_indicators(cve.description, "file") # Determine severity level level = "high" if cve.cvss_score and cve.cvss_score >= 7.0 else "medium" + # Create enhanced description + enhanced_description = cve.description[:200] + "..." if len(cve.description) > 200 else cve.description + if exploit_indicators: + enhanced_description += " [Enhanced with GitHub exploit analysis]" + + # Build tags + tags = [f"attack.{self._get_mitre_technique(cve.description, exploit_indicators)}", cve.cve_id.lower()] + if exploit_indicators: + tags.append("exploit.github") + rule_content = template.template_content.format( - title=f"CVE-{cve.cve_id} Exploitation Attempt", - description=cve.description[:200] + "..." if len(cve.description) > 200 else cve.description, + title=f"CVE-{cve.cve_id} {'Exploit-Based ' if exploit_indicators else ''}Detection", + description=enhanced_description, rule_id=str(uuid.uuid4()), date=datetime.utcnow().strftime("%Y/%m/%d"), cve_url=f"https://nvd.nist.gov/vuln/detail/{cve.cve_id}", cve_id=cve.cve_id.lower(), + tags="\n - ".join(tags), suspicious_processes=suspicious_processes or ["suspicious.exe", "malware.exe"], suspicious_ports=suspicious_ports or [4444, 8080, 9999], file_patterns=file_patterns or ["temp", "malware", "exploit"], @@ -253,6 +567,37 @@ class CVESigmaService: print(f"Error populating template: {str(e)}") return None + def _get_mitre_technique(self, description: str, exploit_indicators: dict = None) -> str: + """Map CVE and exploit indicators to MITRE ATT&CK techniques""" + desc_lower = description.lower() + + # Check exploit indicators first + if exploit_indicators: + if exploit_indicators.get('powershell'): + return "t1059.001" # PowerShell + elif exploit_indicators.get('commands'): + return "t1059.003" # Windows Command Shell + elif exploit_indicators.get('network'): + return "t1071.001" # Web Protocols + elif exploit_indicators.get('files'): + return "t1105" # Ingress Tool Transfer + elif exploit_indicators.get('processes'): + return "t1106" # Native API + + # Fallback to description analysis + if "powershell" in desc_lower: + return "t1059.001" + elif "command" in desc_lower or "cmd" in desc_lower: + return "t1059.003" + elif "network" in desc_lower or "remote" in desc_lower: + return "t1071.001" + elif "file" in desc_lower or "upload" in desc_lower: + return "t1105" + elif "process" in desc_lower or "execution" in desc_lower: + return "t1106" + else: + return "execution" # Generic + def _extract_suspicious_indicators(self, description: str, indicator_type: str) -> List: """Extract suspicious indicators from CVE description""" if indicator_type == "process": @@ -272,8 +617,19 @@ class CVESigmaService: return None - def _determine_detection_type(self, description: str) -> str: - """Determine detection type based on CVE description""" + def _determine_detection_type(self, description: str, exploit_indicators: dict = None) -> str: + """Determine detection type based on CVE description and exploit indicators""" + if exploit_indicators: + if exploit_indicators.get('powershell'): + return "powershell" + elif exploit_indicators.get('network'): + return "network" + elif exploit_indicators.get('files'): + return "file" + elif exploit_indicators.get('processes') or exploit_indicators.get('commands'): + return "process" + + # Fallback to original logic if "remote" in description or "network" in description: return "network" elif "process" in description or "execution" in description: @@ -283,11 +639,27 @@ class CVESigmaService: else: return "general" - def _calculate_confidence(self, cve: CVE) -> str: + def _calculate_confidence(self, cve: CVE, exploit_based: bool = False) -> str: """Calculate confidence level for the generated rule""" - if cve.cvss_score and cve.cvss_score >= 9.0: + base_confidence = 0 + + # CVSS score contributes to confidence + if cve.cvss_score: + if cve.cvss_score >= 9.0: + base_confidence += 3 + elif cve.cvss_score >= 7.0: + base_confidence += 2 + else: + base_confidence += 1 + + # Exploit-based rules get higher confidence + if exploit_based: + base_confidence += 2 + + # Map to confidence levels + if base_confidence >= 4: return "high" - elif cve.cvss_score and cve.cvss_score >= 7.0: + elif base_confidence >= 2: return "medium" else: return "low" @@ -427,6 +799,9 @@ async def get_sigma_rules(skip: int = 0, limit: int = 50, db: Session = Depends( 'log_source': rule.log_source, 'confidence_level': rule.confidence_level, 'auto_generated': rule.auto_generated, + 'exploit_based': rule.exploit_based or False, + 'github_repos': rule.github_repos or [], + 'exploit_indicators': rule.exploit_indicators, 'created_at': rule.created_at } result.append(SigmaRuleResponse(**rule_dict)) @@ -447,6 +822,9 @@ async def get_sigma_rules_by_cve(cve_id: str, db: Session = Depends(get_db)): 'log_source': rule.log_source, 'confidence_level': rule.confidence_level, 'auto_generated': rule.auto_generated, + 'exploit_based': rule.exploit_based or False, + 'github_repos': rule.github_repos or [], + 'exploit_indicators': rule.exploit_indicators, 'created_at': rule.created_at } result.append(SigmaRuleResponse(**rule_dict)) diff --git a/backend/requirements.txt b/backend/requirements.txt index 3e7f1bb..5d590e5 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -8,3 +8,7 @@ python-multipart==0.0.6 redis==5.0.1 alembic==1.13.1 asyncpg==0.29.0 +pygithub==2.1.1 +gitpython==3.1.40 +beautifulsoup4==4.12.2 +lxml==4.9.3 diff --git a/frontend/src/App.js b/frontend/src/App.js index b082396..0bb35ef 100644 --- a/frontend/src/App.js +++ b/frontend/src/App.js @@ -275,7 +275,37 @@ function App() { Auto-generated )} + {rule.exploit_based && ( + + 🔍 Exploit-Based + + )} + {rule.github_repos && rule.github_repos.length > 0 && ( +
+ Based on {rule.github_repos.length} GitHub repository{rule.github_repos.length > 1 ? 's' : ''}: +
+Invalid indicator data
; + } + }; + const CVEDetail = ({ cve, onClose }) => { const [cveRules, setCveRules] = useState([]); @@ -393,8 +463,38 @@ function App() { {rule.confidence_level} + {rule.exploit_based && ( + + 🔍 Exploit-Based + + )} + {rule.github_repos && rule.github_repos.length > 0 && ( ++ Based on GitHub exploit analysis: +
+Exploit Indicators:
+