init commit. main app + frontend/backend
This commit is contained in:
commit
967886ef49
13 changed files with 1512 additions and 0 deletions
12
.env.example
Normal file
12
.env.example
Normal file
|
@ -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
|
231
README.md
Executable file
231
README.md
Executable file
|
@ -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 <repository-url>
|
||||
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
|
26
backend/Dockerfile
Normal file
26
backend/Dockerfile
Normal file
|
@ -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"]
|
394
backend/main.py
Normal file
394
backend/main.py
Normal file
|
@ -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)
|
10
backend/requirements.txt
Normal file
10
backend/requirements.txt
Normal file
|
@ -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
|
56
docker-compose.yml
Normal file
56
docker-compose.yml
Normal file
|
@ -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:
|
24
frontend/Dockerfile
Normal file
24
frontend/Dockerfile
Normal file
|
@ -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"]
|
47
frontend/package.json
Normal file
47
frontend/package.json
Normal file
|
@ -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"
|
||||
}
|
18
frontend/public/index.html
Normal file
18
frontend/public/index.html
Normal file
|
@ -0,0 +1,18 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta
|
||||
name="description"
|
||||
content="CVE-SIGMA Auto Generator - Automatically generate SIGMA rules from CVE data"
|
||||
/>
|
||||
<title>CVE-SIGMA Auto Generator</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
126
frontend/src/App.css
Normal file
126
frontend/src/App.css
Normal file
|
@ -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;
|
||||
}
|
||||
}
|
425
frontend/src/App.js
Normal file
425
frontend/src/App.js
Normal file
|
@ -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 = () => (
|
||||
<div className="space-y-6">
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
<div className="bg-white p-6 rounded-lg shadow">
|
||||
<h3 className="text-lg font-medium text-gray-900">Total CVEs</h3>
|
||||
<p className="text-3xl font-bold text-blue-600">{stats.total_cves || 0}</p>
|
||||
</div>
|
||||
<div className="bg-white p-6 rounded-lg shadow">
|
||||
<h3 className="text-lg font-medium text-gray-900">SIGMA Rules</h3>
|
||||
<p className="text-3xl font-bold text-green-600">{stats.total_sigma_rules || 0}</p>
|
||||
</div>
|
||||
<div className="bg-white p-6 rounded-lg shadow">
|
||||
<h3 className="text-lg font-medium text-gray-900">Recent CVEs (7d)</h3>
|
||||
<p className="text-3xl font-bold text-purple-600">{stats.recent_cves_7_days || 0}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-white rounded-lg shadow p-6">
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<h2 className="text-xl font-bold text-gray-900">Recent CVEs</h2>
|
||||
<button
|
||||
onClick={handleFetchCves}
|
||||
className="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-md"
|
||||
>
|
||||
Fetch New CVEs
|
||||
</button>
|
||||
</div>
|
||||
<div className="overflow-x-auto">
|
||||
<table className="min-w-full divide-y divide-gray-200">
|
||||
<thead className="bg-gray-50">
|
||||
<tr>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
CVE ID
|
||||
</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Severity
|
||||
</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
CVSS Score
|
||||
</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Published
|
||||
</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Actions
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="bg-white divide-y divide-gray-200">
|
||||
{cves.slice(0, 10).map((cve) => (
|
||||
<tr key={cve.id}>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">
|
||||
{cve.cve_id}
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap">
|
||||
<span className={`inline-flex px-2 py-1 text-xs font-semibold rounded-full ${getSeverityColor(cve.severity)}`}>
|
||||
{cve.severity || 'N/A'}
|
||||
</span>
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
|
||||
{cve.cvss_score || 'N/A'}
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
||||
{cve.published_date ? formatDate(cve.published_date) : 'N/A'}
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium">
|
||||
<button
|
||||
onClick={() => setSelectedCve(cve)}
|
||||
className="text-blue-600 hover:text-blue-900"
|
||||
>
|
||||
View Details
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const CVEList = () => (
|
||||
<div className="bg-white rounded-lg shadow">
|
||||
<div className="px-6 py-4 border-b border-gray-200">
|
||||
<h2 className="text-xl font-bold text-gray-900">All CVEs</h2>
|
||||
</div>
|
||||
<div className="divide-y divide-gray-200">
|
||||
{cves.map((cve) => (
|
||||
<div key={cve.id} className="p-6 hover:bg-gray-50">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex-1">
|
||||
<h3 className="text-lg font-medium text-gray-900">{cve.cve_id}</h3>
|
||||
<p className="text-sm text-gray-600 mt-1 line-clamp-2">
|
||||
{cve.description}
|
||||
</p>
|
||||
<div className="flex items-center mt-2 space-x-4">
|
||||
<span className={`inline-flex px-2 py-1 text-xs font-semibold rounded-full ${getSeverityColor(cve.severity)}`}>
|
||||
{cve.severity || 'N/A'}
|
||||
</span>
|
||||
<span className="text-sm text-gray-500">
|
||||
CVSS: {cve.cvss_score || 'N/A'}
|
||||
</span>
|
||||
<span className="text-sm text-gray-500">
|
||||
{cve.published_date ? formatDate(cve.published_date) : 'N/A'}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => setSelectedCve(cve)}
|
||||
className="ml-4 bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-md"
|
||||
>
|
||||
View Details
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const SigmaRulesList = () => (
|
||||
<div className="bg-white rounded-lg shadow">
|
||||
<div className="px-6 py-4 border-b border-gray-200">
|
||||
<h2 className="text-xl font-bold text-gray-900">Generated SIGMA Rules</h2>
|
||||
</div>
|
||||
<div className="divide-y divide-gray-200">
|
||||
{sigmaRules.map((rule) => (
|
||||
<div key={rule.id} className="p-6">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<div>
|
||||
<h3 className="text-lg font-medium text-gray-900">{rule.rule_name}</h3>
|
||||
<p className="text-sm text-gray-600">CVE: {rule.cve_id}</p>
|
||||
<div className="flex items-center mt-2 space-x-4">
|
||||
<span className="inline-flex px-2 py-1 text-xs font-semibold rounded-full bg-blue-100 text-blue-800">
|
||||
{rule.detection_type}
|
||||
</span>
|
||||
<span className="inline-flex px-2 py-1 text-xs font-semibold rounded-full bg-green-100 text-green-800">
|
||||
{rule.confidence_level}
|
||||
</span>
|
||||
{rule.auto_generated && (
|
||||
<span className="inline-flex px-2 py-1 text-xs font-semibold rounded-full bg-purple-100 text-purple-800">
|
||||
Auto-generated
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<span className="text-sm text-gray-500">
|
||||
{formatDate(rule.created_at)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="mt-4">
|
||||
<SyntaxHighlighter
|
||||
language="yaml"
|
||||
style={tomorrow}
|
||||
className="rounded-md"
|
||||
customStyle={{ fontSize: '12px' }}
|
||||
>
|
||||
{rule.rule_content}
|
||||
</SyntaxHighlighter>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
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 (
|
||||
<div className="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50">
|
||||
<div className="relative top-20 mx-auto p-5 border w-11/12 max-w-4xl shadow-lg rounded-md bg-white">
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<h2 className="text-2xl font-bold text-gray-900">{cve.cve_id}</h2>
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="text-gray-400 hover:text-gray-600"
|
||||
>
|
||||
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h3 className="text-lg font-medium text-gray-900 mb-2">Details</h3>
|
||||
<div className="bg-gray-50 p-4 rounded-md">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<span className="text-sm font-medium text-gray-500">Severity:</span>
|
||||
<span className={`ml-2 inline-flex px-2 py-1 text-xs font-semibold rounded-full ${getSeverityColor(cve.severity)}`}>
|
||||
{cve.severity || 'N/A'}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-sm font-medium text-gray-500">CVSS Score:</span>
|
||||
<span className="ml-2 text-sm text-gray-900">{cve.cvss_score || 'N/A'}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-sm font-medium text-gray-500">Published:</span>
|
||||
<span className="ml-2 text-sm text-gray-900">
|
||||
{cve.published_date ? formatDate(cve.published_date) : 'N/A'}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 className="text-lg font-medium text-gray-900 mb-2">Description</h3>
|
||||
<p className="text-sm text-gray-700 bg-gray-50 p-4 rounded-md">
|
||||
{cve.description}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{cve.affected_products && cve.affected_products.length > 0 && (
|
||||
<div>
|
||||
<h3 className="text-lg font-medium text-gray-900 mb-2">Affected Products</h3>
|
||||
<div className="bg-gray-50 p-4 rounded-md">
|
||||
<ul className="list-disc list-inside space-y-1">
|
||||
{cve.affected_products.slice(0, 5).map((product, index) => (
|
||||
<li key={index} className="text-sm text-gray-700">{product}</li>
|
||||
))}
|
||||
{cve.affected_products.length > 5 && (
|
||||
<li className="text-sm text-gray-500">... and {cve.affected_products.length - 5} more</li>
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div>
|
||||
<h3 className="text-lg font-medium text-gray-900 mb-2">Generated SIGMA Rules ({cveRules.length})</h3>
|
||||
{cveRules.length > 0 ? (
|
||||
<div className="space-y-4">
|
||||
{cveRules.map((rule) => (
|
||||
<div key={rule.id} className="border border-gray-200 rounded-md p-4">
|
||||
<div className="flex justify-between items-center mb-2">
|
||||
<h4 className="font-medium text-gray-900">{rule.rule_name}</h4>
|
||||
<div className="flex space-x-2">
|
||||
<span className="inline-flex px-2 py-1 text-xs font-semibold rounded-full bg-blue-100 text-blue-800">
|
||||
{rule.detection_type}
|
||||
</span>
|
||||
<span className="inline-flex px-2 py-1 text-xs font-semibold rounded-full bg-green-100 text-green-800">
|
||||
{rule.confidence_level}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<SyntaxHighlighter
|
||||
language="yaml"
|
||||
style={tomorrow}
|
||||
className="rounded-md"
|
||||
customStyle={{ fontSize: '12px' }}
|
||||
>
|
||||
{rule.rule_content}
|
||||
</SyntaxHighlighter>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-gray-500 bg-gray-50 p-4 rounded-md">No SIGMA rules generated for this CVE yet.</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-100 flex items-center justify-center">
|
||||
<div className="text-center">
|
||||
<div className="animate-spin rounded-full h-32 w-32 border-b-2 border-blue-600"></div>
|
||||
<p className="mt-4 text-gray-600">Loading...</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-100">
|
||||
<nav className="bg-white shadow">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div className="flex justify-between h-16">
|
||||
<div className="flex">
|
||||
<div className="flex-shrink-0 flex items-center">
|
||||
<h1 className="text-xl font-bold text-gray-900">CVE-SIGMA Auto Generator</h1>
|
||||
</div>
|
||||
<div className="ml-6 flex space-x-8">
|
||||
<button
|
||||
onClick={() => setActiveTab('dashboard')}
|
||||
className={`inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium ${
|
||||
activeTab === 'dashboard'
|
||||
? 'border-blue-500 text-gray-900'
|
||||
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'
|
||||
}`}
|
||||
>
|
||||
Dashboard
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab('cves')}
|
||||
className={`inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium ${
|
||||
activeTab === 'cves'
|
||||
? 'border-blue-500 text-gray-900'
|
||||
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'
|
||||
}`}
|
||||
>
|
||||
CVEs
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab('rules')}
|
||||
className={`inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium ${
|
||||
activeTab === 'rules'
|
||||
? 'border-blue-500 text-gray-900'
|
||||
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'
|
||||
}`}
|
||||
>
|
||||
SIGMA Rules
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main className="max-w-7xl mx-auto py-6 sm:px-6 lg:px-8">
|
||||
<div className="px-4 py-6 sm:px-0">
|
||||
{activeTab === 'dashboard' && <Dashboard />}
|
||||
{activeTab === 'cves' && <CVEList />}
|
||||
{activeTab === 'rules' && <SigmaRulesList />}
|
||||
</div>
|
||||
</main>
|
||||
|
||||
{selectedCve && (
|
||||
<CVEDetail cve={selectedCve} onClose={() => setSelectedCve(null)} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
11
frontend/src/index.js
Normal file
11
frontend/src/index.js
Normal file
|
@ -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(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>
|
||||
);
|
132
init.sql
Normal file
132
init.sql
Normal file
|
@ -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);
|
Loading…
Add table
Reference in a new issue