Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Nuclei analyzer (Draft) Closes #1883 #2670

Closed
Closed
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
d27ac28
Install Kanvas workflow
meshery-dev[bot] Nov 6, 2024
03e881d
initial commit
pranjalg1331 Jan 5, 2025
de0e71e
Dockerfile
pranjalg1331 Jan 6, 2025
7c8b076
synchronous nuclei api
pranjalg1331 Jan 13, 2025
c9c4b69
removing unnecessary file
pranjalg1331 Jan 13, 2025
4c4d70b
WAD Analyzer, Closes #814 (#2655)
basedBaba Jan 14, 2025
b84e378
Bump pygraphviz in /integrations/malware_tools_analyzers/requirements…
dependabot[bot] Jan 15, 2025
5af5aaa
Bump blinker from 1.7.0 to 1.8.2 in /integrations/phishing_analyzers …
dependabot[bot] Jan 15, 2025
920164a
Wrongly merged dependency upgrade
fgibertoni Jan 16, 2025
e87acee
Fixed timeout error on page without input tag
fgibertoni Jan 17, 2025
fdc2b5d
Update abuse.ch services (#2683)
fgibertoni Jan 17, 2025
c01a44c
fixed tests + props warning (#2688)
carellamartina Jan 20, 2025
af952d4
Frontend - Possible playbook flows (#2668)
carellamartina Jan 20, 2025
2926c7a
Added timeout parameter malware bazaar end threatfox (#2691)
federicofantini Jan 21, 2025
97ab70e
filter robot from most used playbook count
drosetti Jan 21, 2025
b2b483c
Revert "Bump pygraphviz in /integrations/malware_tools_analyzers/requ…
fgibertoni Jan 21, 2025
a558c6e
disabled autoComplete for secrets (#2693)
carellamartina Jan 22, 2025
e91acf4
refactor plugin config tests (#2696)
carellamartina Jan 23, 2025
422edcd
Fix all pivot (#2695)
drosetti Jan 23, 2025
86e51c2
dockerfile
pranjalg1331 Jan 14, 2025
43e0cb4
made api async
pranjalg1331 Jan 17, 2025
08cdb0b
error resolved
pranjalg1331 Jan 25, 2025
07aa7d6
async working
pranjalg1331 Jan 25, 2025
0b114a2
another
pranjalg1331 Jan 25, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions api_app/analyzers_manager/observable_analyzers/nuclei.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# This file is a part of IntelOwl https://github.com/intelowlproject/IntelOwl
# See the file 'LICENSE' for copying permission.

from api_app.analyzers_manager.classes import DockerBasedAnalyzer, ObservableAnalyzer


class NucleiAnalyzer(ObservableAnalyzer, DockerBasedAnalyzer):
url: str = "http://nuclei_analyzer:4008/run-nuclei"
template_dirs: list
max_tries: int = 10
poll_distance: int = 10

def run(self):
"""
Prepares and executes a Nuclei scan through the Docker-based API.
"""
# Prepare request data
self.template_dirs = ["dns"]
print("hello")
req_data = {
"url": self.observable_name, # The URL or observable to scan
"template_dirs": self.template_dirs
or [], # Use provided template directories or default to an empty list
}

# Execute the request
report = self._docker_run(req_data=req_data, req_files=None)
print("helllo")

print(report)
return report
54 changes: 54 additions & 0 deletions integrations/nuclei_analyzer/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
FROM python:3.9-slim
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

python 3.9 will reach the end of life in oct 2025 please use an higher version


# Set environment variables
ENV PROJECT_PATH=/opt/nuclei-api
ENV LOG_PATH=/var/log/nuclei_analyzer
ENV USER=nuclei-user

# Install required packages and download nuclei
RUN apt-get update && \
apt-get install -y --no-install-recommends \
ca-certificates \
wget \
unzip && \
wget https://github.com/projectdiscovery/nuclei/releases/download/v3.3.7/nuclei_3.3.7_linux_amd64.zip && \
unzip nuclei_3.3.7_linux_amd64.zip && \
mv nuclei /usr/local/bin/ && \
chmod +x /usr/local/bin/nuclei && \
rm nuclei_3.3.7_linux_amd64.zip && \
apt-get autoremove -y && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*

# Create non-root user
RUN useradd -ms /bin/bash ${USER}

# Create necessary directories
RUN mkdir -p ${PROJECT_PATH} ${LOG_PATH}

# Set working directory
WORKDIR ${PROJECT_PATH}

# Copy application files
COPY requirements.txt .
COPY app.py .
COPY entrypoint.sh .

# Set proper permissions
RUN chmod +x entrypoint.sh && \
chown -R ${USER}:${USER} ${PROJECT_PATH} && \
chown -R ${USER}:${USER} ${LOG_PATH} && \
chmod 755 ${PROJECT_PATH} && \
chmod 755 ${LOG_PATH}

# Install Python dependencies
RUN pip install --no-cache-dir -r requirements.txt

# Switch to non-root user
USER ${USER}

# Expose port
EXPOSE 4008

# Use full path to entrypoint script (this is another key fix)
ENTRYPOINT ["./entrypoint.sh"]
173 changes: 173 additions & 0 deletions integrations/nuclei_analyzer/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
import json
import logging
import os
import subprocess
import traceback
from typing import Any, Dict, Tuple

from flask import Flask, jsonify, request

# Set the log path (ensure this is a valid directory)
log_path = os.getenv("LOG_PATH", "/var/log/intel_owl/nuclei_analyzers")
os.makedirs(log_path, exist_ok=True) # Ensure the directory exists

# Configure logging
logging.basicConfig(
level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
)
logger = logging.getLogger("nuclei-analyzer")

# Set up file logging
file_handler = logging.FileHandler(f"{log_path}/nuclei_analyzer.log")
file_handler.setLevel(logging.INFO)
formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
file_handler.setFormatter(formatter)
logger.addHandler(file_handler)

app = Flask(__name__)


def run_nuclei_command(
url: str, template_dirs: list = None
) -> Tuple[bool, Dict[str, Any]]:
"""
Returns: (success: bool, result: dict)
"""
try:
logger.info(f"Starting Nuclei scan for URL: {url}")

# Ensure nuclei binary exists
nuclei_path = "./nuclei" if os.path.exists("./nuclei") else "nuclei"

command = [nuclei_path, "-u", url, "-jsonl"]

if template_dirs:
for template_dir in template_dirs:
command.extend(["-t", template_dir])

logger.debug(f"Running command: {' '.join(command)}")

result = subprocess.run(
command,
Fixed Show fixed Hide fixed
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
timeout=300, # 5 minute timeout
)

if result.returncode != 0:
error_msg = result.stderr.strip()
logger.error(f"Nuclei scan failed: {error_msg}")
return False, {
"success": False,
"error": "Failed to run Nuclei",
"details": error_msg,
}

# Process JSON output
output_lines = [
line.strip() for line in result.stdout.split("\n") if line.strip()
]
parsed_results = []

for line in output_lines:
try:
parsed_results.append(json.loads(line))
except json.JSONDecodeError as e:
logger.warning(f"Failed to parse JSON line: {line}, error: {str(e)}")
continue

logger.info(f"Nuclei scan completed successfully for {url}")
return True, {
"success": True,
"results": parsed_results,
"scan_status": "COMPLETED",
"statistics": {"total_findings": len(parsed_results)},
}

except subprocess.TimeoutExpired:
logger.error("Nuclei scan timed out after 600 seconds")
return False, {
"success": False,
"error": "Scan timed out",
"scan_status": "TIMEOUT",
}
except Exception as e:
logger.error(f"Unexpected error during Nuclei scan: {str(e)}")
logger.error(traceback.format_exc())
return False, {
"success": False,
"error": "An unexpected error occurred",
"details": str(e),
"scan_status": "ERROR",
}


@app.route("/run-nuclei", methods=["POST"])
def run_nuclei():
"""
Endpoint to run Nuclei analysis.

Expected payload:
{
"url": "https://example.com"
}
"""
try:
logger.info("Received Nuclei scan request")

# Validate request
data = request.get_json()
if not data or "url" not in data:
logger.error("Invalid request: missing 'data' field")
return (
jsonify(
{
"success": False,
"error": "Invalid request, 'data' field is required",
}
),
400,
)

url = data["url"]
template_dirs = data.get("template_dirs", [])

if not isinstance(template_dirs, list):
logger.error("Invalid request: 'template_dirs' must be a list")
return (
jsonify(
{
"success": False,
"error": "Invalid request, 'template_dirs' must be a list",
}
),
400,
)

# Run scan
success, result = run_nuclei_command(url, template_dirs)

if success:
return jsonify(result), 200
Fixed Show fixed Hide fixed
else:
return jsonify(result), 500
Fixed Show fixed Hide fixed

except Exception as e:
logger.error(f"Unexpected API error: {str(e)}")
logger.error(traceback.format_exc())
return (
jsonify(
{
"success": False,
"error": "An unexpected error occurred",
"details": str(e),
}
Fixed Show fixed Hide fixed
),
500,
)


if __name__ == "__main__":
logger.info("Starting the Nuclei Analyzer API")
app.run(host="0.0.0.0", port=4008)
Empty file.
15 changes: 15 additions & 0 deletions integrations/nuclei_analyzer/compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# All additional integrations should be added following this format only.

services:
nuclei_analyzer:
image: pranjalg1310/nuclei-analyzer:1.0.3
container_name: nuclei_analyzer
restart: unless-stopped
expose:
- "4008"
env_file:
- env_file_integrations
volumes:
- generic_logs:/var/log/intel_owl
depends_on:
- uwsgi
14 changes: 14 additions & 0 deletions integrations/nuclei_analyzer/entrypoint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#!/bin/bash

# Ensure proper permissions for logs
chown -R ${USER}:${USER} ${LOG_PATH}

# Run any initialization logic for Nuclei
echo "Starting Nuclei Analyzer Service..."

# Run the Flask application with Gunicorn
exec gunicorn --bind 0.0.0.0:4008 \
--timeout 600 \
--workers 4 \
--log-level info \
app:app
Binary file added integrations/nuclei_analyzer/nuclei
Binary file not shown.
2 changes: 2 additions & 0 deletions integrations/nuclei_analyzer/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Flask==3.1.0
gunicorn==21.2.0
6 changes: 5 additions & 1 deletion start
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ check_parameters "$@" && shift 2
load_env "docker/.env"
current_version=${REACT_APP_INTELOWL_VERSION/"v"/""}

docker_analyzers=("pcap_analyzers" "tor_analyzers" "malware_tools_analyzers" "cyberchef" "phoneinfoga" "phishing_analyzers")
docker_analyzers=("pcap_analyzers" "tor_analyzers" "malware_tools_analyzers" "cyberchef" "phoneinfoga" "phishing_analyzers" "nuclei_analyzer")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I think you should add an "echo" in the print_help function (line 19) and add the path mapping (line 12)



for value in "${docker_analyzers[@]}"; do
Expand Down Expand Up @@ -147,6 +147,10 @@ while [[ $# -gt 0 ]]; do
analyzers["tor_analyzers"]=true
shift 1
;;
--nuclei_analyzer)
analyzers["nuclei_analyzer"]=true
shift 1
;;
--malware_tools_analyzers)
analyzers["malware_tools_analyzers"]=true
shift 1
Expand Down
Loading