commit 967886ef4981b6bb9eb2c8fe1893ab92138bafff Author: bpmcdevitt Date: Tue Jul 8 08:34:28 2025 -0500 init commit. main app + frontend/backend diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..10049c0 --- /dev/null +++ b/.env.example @@ -0,0 +1,12 @@ +# NVD API Configuration +# Get your free API key at: https://nvd.nist.gov/developers/request-an-api-key +NVD_API_KEY=your_nvd_api_key_here + +# Database Configuration (Docker Compose will use defaults) +# DATABASE_URL=postgresql://cve_user:cve_password@localhost:5432/cve_sigma_db + +# Frontend Configuration +# REACT_APP_API_URL=http://localhost:8000 + +# Optional: Redis Configuration +# REDIS_URL=redis://localhost:6379 diff --git a/README.md b/README.md new file mode 100755 index 0000000..91cc28f --- /dev/null +++ b/README.md @@ -0,0 +1,231 @@ +# CVE-SIGMA Auto Generator + +An automated platform that fetches CVE data and automatically generates SIGMA rules for threat detection. + +## Features + +- **Automated CVE Fetching**: Regularly polls the NVD (National Vulnerability Database) for new CVEs +- **Intelligent SIGMA Rule Generation**: Automatically creates SIGMA rules based on CVE characteristics +- **Modern Web Interface**: React-based UI for browsing CVEs and managing SIGMA rules +- **Real-time Updates**: Background tasks keep CVE data current +- **Rule Templates**: Configurable templates for different types of vulnerabilities +- **Docker Compose**: Easy deployment and orchestration + +## Architecture + +- **Backend**: FastAPI with SQLAlchemy ORM +- **Frontend**: React with Tailwind CSS +- **Database**: PostgreSQL +- **Cache**: Redis (optional) +- **Containerization**: Docker & Docker Compose + +## Quick Start + +### Prerequisites + +- Docker and Docker Compose +- (Optional) NVD API Key for increased rate limits + +### Setup + +1. Clone the repository: +```bash +git clone +cd cve-sigma-generator +``` + +2. (Optional) Set your NVD API Key: +```bash +export NVD_API_KEY="your-api-key-here" +``` + +3. Start the application: +```bash +docker-compose up -d +``` + +4. Wait for services to initialize (about 30-60 seconds) + +5. Access the application: + - Frontend: http://localhost:3000 + - Backend API: http://localhost:8000 + - API Documentation: http://localhost:8000/docs + +### First Run + +The application will automatically: +1. Initialize the database with rule templates +2. Start fetching recent CVEs from NVD +3. Generate SIGMA rules for each CVE +4. Continue polling for new CVEs every hour + +## Usage + +### Web Interface + +The web interface provides three main sections: + +1. **Dashboard**: Overview statistics and recent CVEs +2. **CVEs**: Complete list of all fetched CVEs with details +3. **SIGMA Rules**: Generated detection rules organized by CVE + +### Manual CVE Fetch + +You can trigger a manual CVE fetch using the "Fetch New CVEs" button in the dashboard or via API: + +```bash +curl -X POST http://localhost:8000/api/fetch-cves +``` + +### API Endpoints + +- `GET /api/cves` - List all CVEs +- `GET /api/cves/{cve_id}` - Get specific CVE details +- `GET /api/sigma-rules` - List all SIGMA rules +- `GET /api/sigma-rules/{cve_id}` - Get SIGMA rules for specific CVE +- `POST /api/fetch-cves` - Manually trigger CVE fetch +- `GET /api/stats` - Get application statistics + +## Configuration + +### Environment Variables + +- `DATABASE_URL`: PostgreSQL connection string +- `NVD_API_KEY`: Optional NVD API key for higher rate limits +- `REACT_APP_API_URL`: Backend API URL for frontend + +### Rule Templates + +The application includes pre-configured rule templates for: +- Windows Process Execution +- Network Connections +- File Modifications + +Additional templates can be added to the database via the `rule_templates` table. + +## SIGMA Rule Generation Logic + +The 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 + +### Template Matching + +- **Process Execution**: Keywords like "process", "execution", "command" +- **Network Connection**: Keywords like "network", "remote", "connection" +- **File Modification**: Keywords like "file", "write", "filesystem" + +## Development + +### Local Development + +1. Start the database: +```bash +docker-compose up -d db redis +``` + +2. Run the backend: +```bash +cd backend +pip install -r requirements.txt +uvicorn main:app --reload +``` + +3. Run the frontend: +```bash +cd frontend +npm install +npm start +``` + +### Database Migration + +The application automatically creates tables on startup. For manual schema changes: + +```bash +# Connect to database +docker-compose exec db psql -U cve_user -d cve_sigma_db + +# Run custom SQL +\i /path/to/migration.sql +``` + +## SIGMA Rule Quality + +Generated rules are marked as "experimental" and should be: +- Reviewed by security analysts +- Tested in a lab environment +- Tuned to reduce false positives +- Validated against real attack scenarios + +## Monitoring + +### Logs + +View application logs: +```bash +# All services +docker-compose logs -f + +# Specific service +docker-compose logs -f backend +``` + +### Health Checks + +The application includes health checks for database connectivity. Monitor with: +```bash +docker-compose ps +``` + +## Troubleshooting + +### Common Issues + +1. **CVE Fetch Failing**: Check NVD API rate limits or network connectivity +2. **Database Connection Error**: Ensure PostgreSQL is running and accessible +3. **Frontend Not Loading**: Verify backend is running and CORS is configured +4. **Rule Generation Issues**: Check CVE description quality and template matching + +### 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. + +## Security Considerations + +- **API Keys**: Store NVD API keys securely using environment variables +- **Database Access**: Use strong passwords and restrict database access +- **Network Security**: Deploy behind a reverse proxy in production +- **Rule Validation**: Always validate generated SIGMA rules before deployment + +## Contributing + +1. Fork the repository +2. Create a feature branch +3. Make changes and add tests +4. Submit a pull request + +## License + +This project is licensed under the MIT License - see the LICENSE file for details. + +## Support + +For issues and questions: +1. Check the troubleshooting section +2. Review application logs +3. Open an issue on GitHub + +## Roadmap + +Planned features: +- [ ] Custom rule template editor +- [ ] MITRE ATT&CK mapping +- [ ] Rule effectiveness scoring +- [ ] Export to SIEM platforms +- [ ] Advanced threat intelligence integration +- [ ] Machine learning-based rule optimization \ No newline at end of file diff --git a/backend/Dockerfile b/backend/Dockerfile new file mode 100644 index 0000000..927b36c --- /dev/null +++ b/backend/Dockerfile @@ -0,0 +1,26 @@ +FROM python:3.11-slim + +WORKDIR /app + +# Install system dependencies +RUN apt-get update && apt-get install -y \ + gcc \ + postgresql-client \ + && rm -rf /var/lib/apt/lists/* + +# Copy requirements first for better caching +COPY requirements.txt . + +# Install Python dependencies +RUN pip install --no-cache-dir -r requirements.txt + +# Copy application code +COPY . . + +# Create non-root user +RUN useradd -m -u 1000 appuser && chown -R appuser:appuser /app +USER appuser + +EXPOSE 8000 + +CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"] diff --git a/backend/main.py b/backend/main.py new file mode 100644 index 0000000..11936a0 --- /dev/null +++ b/backend/main.py @@ -0,0 +1,394 @@ +from fastapi import FastAPI, HTTPException, BackgroundTasks, Depends +from fastapi.middleware.cors import CORSMiddleware +from fastapi.responses import JSONResponse +from sqlalchemy import create_engine, Column, String, Text, DECIMAL, TIMESTAMP, Boolean, ARRAY +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.orm import sessionmaker, Session +from sqlalchemy.dialects.postgresql import UUID +import uuid +from datetime import datetime, timedelta +import requests +import json +import re +import os +from typing import List, Optional +from pydantic import BaseModel +import asyncio +from contextlib import asynccontextmanager + +# Database setup +DATABASE_URL = os.getenv("DATABASE_URL", "postgresql://cve_user:cve_password@localhost:5432/cve_sigma_db") +engine = create_engine(DATABASE_URL) +SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) +Base = declarative_base() + +# Database Models +class CVE(Base): + __tablename__ = "cves" + + id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) + cve_id = Column(String(20), unique=True, nullable=False) + description = Column(Text) + cvss_score = Column(DECIMAL(3, 1)) + severity = Column(String(20)) + published_date = Column(TIMESTAMP) + modified_date = Column(TIMESTAMP) + affected_products = Column(ARRAY(String)) + references = Column(ARRAY(String)) + created_at = Column(TIMESTAMP, default=datetime.utcnow) + updated_at = Column(TIMESTAMP, default=datetime.utcnow) + +class SigmaRule(Base): + __tablename__ = "sigma_rules" + + id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) + cve_id = Column(String(20)) + rule_name = Column(String(255), nullable=False) + rule_content = Column(Text, nullable=False) + detection_type = Column(String(50)) + log_source = Column(String(100)) + confidence_level = Column(String(20)) + auto_generated = Column(Boolean, default=True) + created_at = Column(TIMESTAMP, default=datetime.utcnow) + updated_at = Column(TIMESTAMP, default=datetime.utcnow) + +class RuleTemplate(Base): + __tablename__ = "rule_templates" + + id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) + template_name = Column(String(255), nullable=False) + template_content = Column(Text, nullable=False) + applicable_product_patterns = Column(ARRAY(String)) + description = Column(Text) + created_at = Column(TIMESTAMP, default=datetime.utcnow) + +# Pydantic models +class CVEResponse(BaseModel): + id: str + cve_id: str + description: Optional[str] + cvss_score: Optional[float] + severity: Optional[str] + published_date: Optional[datetime] + affected_products: Optional[List[str]] + + class Config: + from_attributes = True + +class SigmaRuleResponse(BaseModel): + id: str + cve_id: str + rule_name: str + rule_content: str + detection_type: Optional[str] + log_source: Optional[str] + confidence_level: Optional[str] + auto_generated: bool + created_at: datetime + + class Config: + from_attributes = True + +# CVE and SIGMA Rule Generator Service +class CVESigmaService: + def __init__(self, db: Session): + self.db = db + self.nvd_api_key = os.getenv("NVD_API_KEY") + + async def fetch_recent_cves(self, days_back: int = 7): + """Fetch recent CVEs from NVD API""" + end_date = datetime.utcnow() + start_date = end_date - timedelta(days=days_back) + + url = "https://services.nvd.nist.gov/rest/json/cves/2.0" + params = { + "pubStartDate": start_date.strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3] + "Z", + "pubEndDate": end_date.strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3] + "Z", + "resultsPerPage": 100 + } + + headers = {} + if self.nvd_api_key: + headers["apiKey"] = self.nvd_api_key + + try: + response = requests.get(url, params=params, headers=headers, timeout=30) + response.raise_for_status() + data = response.json() + + new_cves = [] + for vuln in data.get("vulnerabilities", []): + cve_data = vuln.get("cve", {}) + cve_id = cve_data.get("id") + + # Check if CVE already exists + existing = self.db.query(CVE).filter(CVE.cve_id == cve_id).first() + if existing: + continue + + # Extract CVE information + description = "" + if cve_data.get("descriptions"): + description = cve_data["descriptions"][0].get("value", "") + + cvss_score = None + severity = None + if cve_data.get("metrics", {}).get("cvssMetricV31"): + cvss_data = cve_data["metrics"]["cvssMetricV31"][0] + cvss_score = cvss_data.get("cvssData", {}).get("baseScore") + severity = cvss_data.get("cvssData", {}).get("baseSeverity") + + affected_products = [] + if cve_data.get("configurations"): + for config in cve_data["configurations"]: + for node in config.get("nodes", []): + for cpe_match in node.get("cpeMatch", []): + if cpe_match.get("vulnerable"): + affected_products.append(cpe_match.get("criteria", "")) + + references = [] + if cve_data.get("references"): + references = [ref.get("url", "") for ref in cve_data["references"]] + + cve_obj = CVE( + cve_id=cve_id, + description=description, + cvss_score=cvss_score, + severity=severity, + published_date=datetime.fromisoformat(cve_data.get("published", "").replace("Z", "+00:00")), + modified_date=datetime.fromisoformat(cve_data.get("lastModified", "").replace("Z", "+00:00")), + affected_products=affected_products, + references=references + ) + + self.db.add(cve_obj) + new_cves.append(cve_obj) + + self.db.commit() + return new_cves + + except Exception as e: + print(f"Error fetching CVEs: {str(e)}") + return [] + + def generate_sigma_rule(self, cve: CVE) -> Optional[SigmaRule]: + """Generate SIGMA rule based on CVE data""" + if not cve.description: + return None + + # Analyze CVE to determine appropriate template + description_lower = cve.description.lower() + affected_products = [p.lower() for p in (cve.affected_products or [])] + + template = self._select_template(description_lower, affected_products) + if not template: + return None + + # Generate rule content + rule_content = self._populate_template(cve, template) + if not rule_content: + return None + + # Determine detection type and confidence + detection_type = self._determine_detection_type(description_lower) + confidence_level = self._calculate_confidence(cve) + + sigma_rule = SigmaRule( + cve_id=cve.cve_id, + rule_name=f"CVE-{cve.cve_id.split('-')[1]}-{cve.cve_id.split('-')[2]} Detection", + rule_content=rule_content, + detection_type=detection_type, + log_source=template.template_name.lower().replace(" ", "_"), + confidence_level=confidence_level, + auto_generated=True + ) + + self.db.add(sigma_rule) + return sigma_rule + + def _select_template(self, description: str, affected_products: List[str]): + """Select appropriate SIGMA rule template""" + templates = self.db.query(RuleTemplate).all() + + # Simple template selection 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) + elif "network" in description or "remote" in description: + return next((t for t in templates if "Network Connection" in t.template_name), None) + elif "file" in description or "write" in description: + return next((t for t in templates if "File Modification" in t.template_name), None) + + # 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""" + 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") + + # Determine severity level + level = "high" if cve.cvss_score and cve.cvss_score >= 7.0 else "medium" + + 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, + 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(), + 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"], + level=level + ) + + return rule_content + + except Exception as e: + print(f"Error populating template: {str(e)}") + return None + + def _extract_suspicious_indicators(self, description: str, indicator_type: str) -> List: + """Extract suspicious indicators from CVE description""" + if indicator_type == "process": + # Look for executable names or process patterns + exe_pattern = re.findall(r'(\w+\.exe)', description, re.IGNORECASE) + return exe_pattern[:5] if exe_pattern else None + + elif indicator_type == "port": + # Look for port numbers + port_pattern = re.findall(r'port\s+(\d+)', description, re.IGNORECASE) + return [int(p) for p in port_pattern[:3]] if port_pattern else None + + elif indicator_type == "file": + # Look for file extensions or paths + file_pattern = re.findall(r'(\w+\.\w{3,4})', description, re.IGNORECASE) + return file_pattern[:5] if file_pattern else None + + return None + + def _determine_detection_type(self, description: str) -> str: + """Determine detection type based on CVE description""" + if "remote" in description or "network" in description: + return "network" + elif "process" in description or "execution" in description: + return "process" + elif "file" in description or "filesystem" in description: + return "file" + else: + return "general" + + def _calculate_confidence(self, cve: CVE) -> str: + """Calculate confidence level for the generated rule""" + if cve.cvss_score and cve.cvss_score >= 9.0: + return "high" + elif cve.cvss_score and cve.cvss_score >= 7.0: + return "medium" + else: + return "low" + +# Dependency +def get_db(): + db = SessionLocal() + try: + yield db + finally: + db.close() + +# Background task to fetch CVEs and generate rules +async def background_cve_fetch(): + while True: + try: + db = SessionLocal() + service = CVESigmaService(db) + print("Fetching recent CVEs...") + new_cves = await service.fetch_recent_cves() + + print(f"Found {len(new_cves)} new CVEs") + for cve in new_cves: + print(f"Generating SIGMA rule for {cve.cve_id}") + sigma_rule = service.generate_sigma_rule(cve) + if sigma_rule: + print(f"Generated rule: {sigma_rule.rule_name}") + + db.commit() + db.close() + + except Exception as e: + print(f"Background task error: {str(e)}") + + # Wait 1 hour before next fetch + await asyncio.sleep(3600) + +@asynccontextmanager +async def lifespan(app: FastAPI): + # Start background task + task = asyncio.create_task(background_cve_fetch()) + yield + # Clean up + task.cancel() + +# FastAPI app +app = FastAPI(title="CVE-SIGMA Auto Generator", lifespan=lifespan) + +app.add_middleware( + CORSMiddleware, + allow_origins=["http://localhost:3000"], + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + +@app.get("/api/cves", response_model=List[CVEResponse]) +async def get_cves(skip: int = 0, limit: int = 50, db: Session = Depends(get_db)): + cves = db.query(CVE).order_by(CVE.published_date.desc()).offset(skip).limit(limit).all() + return cves + +@app.get("/api/cves/{cve_id}", response_model=CVEResponse) +async def get_cve(cve_id: str, db: Session = Depends(get_db)): + cve = db.query(CVE).filter(CVE.cve_id == cve_id).first() + if not cve: + raise HTTPException(status_code=404, detail="CVE not found") + return cve + +@app.get("/api/sigma-rules", response_model=List[SigmaRuleResponse]) +async def get_sigma_rules(skip: int = 0, limit: int = 50, db: Session = Depends(get_db)): + rules = db.query(SigmaRule).order_by(SigmaRule.created_at.desc()).offset(skip).limit(limit).all() + return rules + +@app.get("/api/sigma-rules/{cve_id}", response_model=List[SigmaRuleResponse]) +async def get_sigma_rules_by_cve(cve_id: str, db: Session = Depends(get_db)): + rules = db.query(SigmaRule).filter(SigmaRule.cve_id == cve_id).all() + return rules + +@app.post("/api/fetch-cves") +async def manual_fetch_cves(background_tasks: BackgroundTasks, db: Session = Depends(get_db)): + async def fetch_task(): + service = CVESigmaService(db) + new_cves = await service.fetch_recent_cves() + for cve in new_cves: + service.generate_sigma_rule(cve) + db.commit() + + background_tasks.add_task(fetch_task) + return {"message": "CVE fetch initiated"} + +@app.get("/api/stats") +async def get_stats(db: Session = Depends(get_db)): + total_cves = db.query(CVE).count() + total_rules = db.query(SigmaRule).count() + recent_cves = db.query(CVE).filter(CVE.published_date >= datetime.utcnow() - timedelta(days=7)).count() + + return { + "total_cves": total_cves, + "total_sigma_rules": total_rules, + "recent_cves_7_days": recent_cves + } + +if __name__ == "__main__": + import uvicorn + uvicorn.run(app, host="0.0.0.0", port=8000) diff --git a/backend/requirements.txt b/backend/requirements.txt new file mode 100644 index 0000000..3e7f1bb --- /dev/null +++ b/backend/requirements.txt @@ -0,0 +1,10 @@ +fastapi==0.104.1 +uvicorn[standard]==0.24.0 +sqlalchemy==2.0.23 +psycopg2-binary==2.9.9 +pydantic==2.5.0 +requests==2.31.0 +python-multipart==0.0.6 +redis==5.0.1 +alembic==1.13.1 +asyncpg==0.29.0 diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..048a4d0 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,56 @@ +version: '3.8' + +services: + db: + image: postgres:15 + environment: + POSTGRES_DB: cve_sigma_db + POSTGRES_USER: cve_user + POSTGRES_PASSWORD: cve_password + volumes: + - postgres_data:/var/lib/postgresql/data + - ./init.sql:/docker-entrypoint-initdb.d/init.sql + ports: + - "5432:5432" + healthcheck: + test: ["CMD-SHELL", "pg_isready -U cve_user -d cve_sigma_db"] + interval: 30s + timeout: 10s + retries: 3 + + backend: + build: ./backend + ports: + - "8000:8000" + environment: + DATABASE_URL: postgresql://cve_user:cve_password@db:5432/cve_sigma_db + NVD_API_KEY: ${NVD_API_KEY:-} + depends_on: + db: + condition: service_healthy + volumes: + - ./backend:/app + command: uvicorn main:app --host 0.0.0.0 --port 8000 --reload + + frontend: + build: ./frontend + ports: + - "3000:3000" + environment: + REACT_APP_API_URL: http://localhost:8000 + volumes: + - ./frontend:/app + - /app/node_modules + command: npm start + + redis: + image: redis:7-alpine + ports: + - "6379:6379" + command: redis-server --appendonly yes + volumes: + - redis_data:/data + +volumes: + postgres_data: + redis_data: diff --git a/frontend/Dockerfile b/frontend/Dockerfile new file mode 100644 index 0000000..15e9058 --- /dev/null +++ b/frontend/Dockerfile @@ -0,0 +1,24 @@ +FROM node:18-alpine + +WORKDIR /app + +# Copy package files +COPY package*.json ./ + +# Install dependencies +RUN npm ci + +# Copy source code +COPY . . + +# Create non-root user +RUN addgroup -g 1001 -S nodejs +RUN adduser -S nextjs -u 1001 + +# Change ownership +RUN chown -R nextjs:nodejs /app +USER nextjs + +EXPOSE 3000 + +CMD ["npm", "start"] diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..471cdc7 --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,47 @@ +{ + "name": "cve-sigma-frontend", + "version": "0.1.0", + "private": true, + "dependencies": { + "@testing-library/jest-dom": "^5.16.4", + "@testing-library/react": "^13.3.0", + "@testing-library/user-event": "^13.5.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-scripts": "5.0.1", + "axios": "^1.6.0", + "react-router-dom": "^6.8.0", + "tailwindcss": "^3.3.0", + "autoprefixer": "^10.4.14", + "postcss": "^8.4.24", + "@headlessui/react": "^1.7.17", + "@heroicons/react": "^2.0.18", + "react-syntax-highlighter": "^15.5.0", + "web-vitals": "^2.1.4" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test", + "eject": "react-scripts eject" + }, + "eslintConfig": { + "extends": [ + "react-app", + "react-app/jest" + ] + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + }, + "proxy": "http://backend:8000" +} diff --git a/frontend/public/index.html b/frontend/public/index.html new file mode 100644 index 0000000..da32bfe --- /dev/null +++ b/frontend/public/index.html @@ -0,0 +1,18 @@ + + + + + + + + CVE-SIGMA Auto Generator + + + + +
+ + diff --git a/frontend/src/App.css b/frontend/src/App.css new file mode 100644 index 0000000..c7043fd --- /dev/null +++ b/frontend/src/App.css @@ -0,0 +1,126 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +.line-clamp-2 { + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; +} + +.animate-spin { + animation: spin 1s linear infinite; +} + +@keyframes spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +/* Custom scrollbar for syntax highlighter */ +.react-syntax-highlighter-line-number { + color: #6b7280 !important; +} + +/* Responsive table improvements */ +@media (max-width: 768px) { + .overflow-x-auto { + -webkit-overflow-scrolling: touch; + } + + table { + font-size: 0.875rem; + } + + .px-6 { + padding-left: 1rem; + padding-right: 1rem; + } +} + +/* Modal backdrop blur effect */ +.fixed.inset-0.bg-gray-600 { + backdrop-filter: blur(4px); +} + +/* Syntax highlighter theme overrides */ +.language-yaml { + border-radius: 0.375rem; + max-height: 400px; + overflow-y: auto; +} + +/* Loading spinner improvements */ +.animate-spin { + border-top-color: transparent; +} + +/* Badge hover effects */ +.inline-flex.px-2.py-1 { + transition: all 0.2s ease-in-out; +} + +.inline-flex.px-2.py-1:hover { + transform: translateY(-1px); + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); +} + +/* Button hover effects */ +button { + transition: all 0.2s ease-in-out; +} + +button:hover { + transform: translateY(-1px); +} + +/* Card hover effects */ +.hover\:bg-gray-50:hover { + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05); + transition: all 0.2s ease-in-out; +} + +/* Smooth transitions for tab switching */ +.border-b-2 { + transition: border-color 0.2s ease-in-out; +} + +/* Custom focus styles for accessibility */ +button:focus, +.focus\:outline-none:focus { + outline: 2px solid #3b82f6; + outline-offset: 2px; +} + +/* Table row hover effects */ +tbody tr:hover { + background-color: #f9fafb; + transition: background-color 0.15s ease-in-out; +} + +/* Responsive grid improvements */ +@media (max-width: 640px) { + .grid-cols-1.md\:grid-cols-3 { + gap: 1rem; + } +} + +/* Loading state styles */ +.loading-pulse { + animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; +} + +@keyframes pulse { + 0%, 100% { + opacity: 1; + } + 50% { + opacity: .5; + } +} diff --git a/frontend/src/App.js b/frontend/src/App.js new file mode 100644 index 0000000..2737abc --- /dev/null +++ b/frontend/src/App.js @@ -0,0 +1,425 @@ +import React, { useState, useEffect } from 'react'; +import axios from 'axios'; +import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; +import { tomorrow } from 'react-syntax-highlighter/dist/esm/styles/prism'; +import './App.css'; + +const API_BASE_URL = process.env.REACT_APP_API_URL || 'http://localhost:8000'; + +function App() { + const [cves, setCves] = useState([]); + const [sigmaRules, setSigmaRules] = useState([]); + const [selectedCve, setSelectedCve] = useState(null); + const [stats, setStats] = useState({}); + const [loading, setLoading] = useState(true); + const [activeTab, setActiveTab] = useState('dashboard'); + + useEffect(() => { + fetchData(); + }, []); + + const fetchData = async () => { + try { + setLoading(true); + const [cvesRes, rulesRes, statsRes] = await Promise.all([ + axios.get(`${API_BASE_URL}/api/cves`), + axios.get(`${API_BASE_URL}/api/sigma-rules`), + axios.get(`${API_BASE_URL}/api/stats`) + ]); + + setCves(cvesRes.data); + setSigmaRules(rulesRes.data); + setStats(statsRes.data); + } catch (error) { + console.error('Error fetching data:', error); + } finally { + setLoading(false); + } + }; + + const handleFetchCves = async () => { + try { + await axios.post(`${API_BASE_URL}/api/fetch-cves`); + setTimeout(fetchData, 2000); // Refresh after 2 seconds + } catch (error) { + console.error('Error fetching CVEs:', error); + } + }; + + const getSeverityColor = (severity) => { + switch (severity?.toLowerCase()) { + case 'critical': return 'bg-red-100 text-red-800'; + case 'high': return 'bg-orange-100 text-orange-800'; + case 'medium': return 'bg-yellow-100 text-yellow-800'; + case 'low': return 'bg-green-100 text-green-800'; + default: return 'bg-gray-100 text-gray-800'; + } + }; + + const formatDate = (dateString) => { + return new Date(dateString).toLocaleDateString('en-US', { + year: 'numeric', + month: 'short', + day: 'numeric' + }); + }; + + const Dashboard = () => ( +
+
+
+

Total CVEs

+

{stats.total_cves || 0}

+
+
+

SIGMA Rules

+

{stats.total_sigma_rules || 0}

+
+
+

Recent CVEs (7d)

+

{stats.recent_cves_7_days || 0}

+
+
+ +
+
+

Recent CVEs

+ +
+
+ + + + + + + + + + + + {cves.slice(0, 10).map((cve) => ( + + + + + + + + ))} + +
+ CVE ID + + Severity + + CVSS Score + + Published + + Actions +
+ {cve.cve_id} + + + {cve.severity || 'N/A'} + + + {cve.cvss_score || 'N/A'} + + {cve.published_date ? formatDate(cve.published_date) : 'N/A'} + + +
+
+
+
+ ); + + const CVEList = () => ( +
+
+

All CVEs

+
+
+ {cves.map((cve) => ( +
+
+
+

{cve.cve_id}

+

+ {cve.description} +

+
+ + {cve.severity || 'N/A'} + + + CVSS: {cve.cvss_score || 'N/A'} + + + {cve.published_date ? formatDate(cve.published_date) : 'N/A'} + +
+
+ +
+
+ ))} +
+
+ ); + + const SigmaRulesList = () => ( +
+
+

Generated SIGMA Rules

+
+
+ {sigmaRules.map((rule) => ( +
+
+
+

{rule.rule_name}

+

CVE: {rule.cve_id}

+
+ + {rule.detection_type} + + + {rule.confidence_level} + + {rule.auto_generated && ( + + Auto-generated + + )} +
+
+ + {formatDate(rule.created_at)} + +
+
+ + {rule.rule_content} + +
+
+ ))} +
+
+ ); + + const CVEDetail = ({ cve, onClose }) => { + const [cveRules, setCveRules] = useState([]); + + useEffect(() => { + if (cve) { + fetchCveRules(cve.cve_id); + } + }, [cve]); + + const fetchCveRules = async (cveId) => { + try { + const response = await axios.get(`${API_BASE_URL}/api/sigma-rules/${cveId}`); + setCveRules(response.data); + } catch (error) { + console.error('Error fetching CVE rules:', error); + } + }; + + return ( +
+
+
+

{cve.cve_id}

+ +
+ +
+
+

Details

+
+
+
+ Severity: + + {cve.severity || 'N/A'} + +
+
+ CVSS Score: + {cve.cvss_score || 'N/A'} +
+
+ Published: + + {cve.published_date ? formatDate(cve.published_date) : 'N/A'} + +
+
+
+
+ +
+

Description

+

+ {cve.description} +

+
+ + {cve.affected_products && cve.affected_products.length > 0 && ( +
+

Affected Products

+
+
    + {cve.affected_products.slice(0, 5).map((product, index) => ( +
  • {product}
  • + ))} + {cve.affected_products.length > 5 && ( +
  • ... and {cve.affected_products.length - 5} more
  • + )} +
+
+
+ )} + +
+

Generated SIGMA Rules ({cveRules.length})

+ {cveRules.length > 0 ? ( +
+ {cveRules.map((rule) => ( +
+
+

{rule.rule_name}

+
+ + {rule.detection_type} + + + {rule.confidence_level} + +
+
+ + {rule.rule_content} + +
+ ))} +
+ ) : ( +

No SIGMA rules generated for this CVE yet.

+ )} +
+
+
+
+ ); + }; + + if (loading) { + return ( +
+
+
+

Loading...

+
+
+ ); + } + + return ( +
+ + +
+
+ {activeTab === 'dashboard' && } + {activeTab === 'cves' && } + {activeTab === 'rules' && } +
+
+ + {selectedCve && ( + setSelectedCve(null)} /> + )} +
+ ); +} + +export default App; diff --git a/frontend/src/index.js b/frontend/src/index.js new file mode 100644 index 0000000..7006ec3 --- /dev/null +++ b/frontend/src/index.js @@ -0,0 +1,11 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import './App.css'; +import App from './App'; + +const root = ReactDOM.createRoot(document.getElementById('root')); +root.render( + + + +); diff --git a/init.sql b/init.sql new file mode 100644 index 0000000..bfa9102 --- /dev/null +++ b/init.sql @@ -0,0 +1,132 @@ +-- Database initialization script + +CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; + +-- CVEs table +CREATE TABLE cves ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + cve_id VARCHAR(20) UNIQUE NOT NULL, + description TEXT, + cvss_score DECIMAL(3,1), + severity VARCHAR(20), + published_date TIMESTAMP, + modified_date TIMESTAMP, + affected_products TEXT[], + references TEXT[], + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW() +); + +-- SIGMA rules table +CREATE TABLE sigma_rules ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + cve_id VARCHAR(20) REFERENCES cves(cve_id), + rule_name VARCHAR(255) NOT NULL, + rule_content TEXT NOT NULL, + detection_type VARCHAR(50), + log_source VARCHAR(100), + confidence_level VARCHAR(20), + auto_generated BOOLEAN DEFAULT TRUE, + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW() +); + +-- Rule templates table +CREATE TABLE rule_templates ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + template_name VARCHAR(255) NOT NULL, + template_content TEXT NOT NULL, + applicable_product_patterns TEXT[], + description TEXT, + created_at TIMESTAMP DEFAULT NOW() +); + +-- Insert some basic rule templates +INSERT INTO rule_templates (template_name, template_content, applicable_product_patterns, description) VALUES +( + 'Windows Process Execution', + 'title: {title} +description: {description} +id: {rule_id} +status: experimental +author: CVE-SIGMA Auto Generator +date: {date} +references: + - {cve_url} +tags: + - attack.execution + - {cve_id} +logsource: + category: process_creation + product: windows +detection: + selection: + Image|contains: {suspicious_processes} + condition: selection +falsepositives: + - Legitimate use of the software +level: {level}', + ARRAY['windows', 'microsoft'], + 'Template for Windows process execution detection' +), +( + 'Network Connection', + 'title: {title} +description: {description} +id: {rule_id} +status: experimental +author: CVE-SIGMA Auto Generator +date: {date} +references: + - {cve_url} +tags: + - attack.command_and_control + - {cve_id} +logsource: + category: network_connection + product: windows +detection: + selection: + Initiated: true + DestinationPort: {suspicious_ports} + condition: selection +falsepositives: + - Legitimate network connections +level: {level}', + ARRAY['network', 'connection', 'remote'], + 'Template for network connection detection' +), +( + 'File Modification', + 'title: {title} +description: {description} +id: {rule_id} +status: experimental +author: CVE-SIGMA Auto Generator +date: {date} +references: + - {cve_url} +tags: + - attack.defense_evasion + - {cve_id} +logsource: + category: file_event + product: windows +detection: + selection: + EventType: creation + TargetFilename|contains: {file_patterns} + condition: selection +falsepositives: + - Legitimate file operations +level: {level}', + ARRAY['file', 'filesystem', 'modification'], + 'Template for file modification detection' +); + +-- Create indexes +CREATE INDEX idx_cves_cve_id ON cves(cve_id); +CREATE INDEX idx_cves_published_date ON cves(published_date); +CREATE INDEX idx_cves_severity ON cves(severity); +CREATE INDEX idx_sigma_rules_cve_id ON sigma_rules(cve_id); +CREATE INDEX idx_sigma_rules_detection_type ON sigma_rules(detection_type);