-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathapp.py
More file actions
214 lines (165 loc) · 6.32 KB
/
Copy pathapp.py
File metadata and controls
214 lines (165 loc) · 6.32 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
import re
import os
from flask import Flask, render_template, request, jsonify, Response
from scanner import scan_target
from cve_lookup import lookup_cves_for_services
from database import init_db, save_scan, get_all_scans, get_cves_by_scan, get_severity_summary, get_latest_scan_id, get_all_cves
from reporter import generate_csv, generate_pdf
app = Flask(__name__)
def is_valid_target(target: str) -> bool:
"""Validate if target is a valid IPv4 or domain name (including localhost)."""
target = target.strip()
if target.lower() == "localhost":
return True
ipv4_pattern = r"^(?:(?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})\.){3}(?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})$"
hostname_pattern = r"^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,6}$"
return bool(re.match(ipv4_pattern, target) or re.match(hostname_pattern, target))
# Initialize database on startup
init_db()
@app.route("/")
def index():
"""Main dashboard — shows all past scans."""
scans = get_all_scans()
latest_id = get_latest_scan_id()
severity_summary = {"CRITICAL": 0, "HIGH": 0, "MEDIUM": 0, "LOW": 0, "N/A": 0}
latest_cves = []
if latest_id:
severity_summary = get_severity_summary(latest_id)
latest_cves = get_cves_by_scan(latest_id)
nvd_api_key = os.getenv("NVD_API_KEY", "")
return render_template(
"index.html",
scans=scans,
latest_cves=latest_cves,
severity_summary=severity_summary,
latest_id=latest_id,
nvd_api_key=nvd_api_key
)
@app.route("/scan", methods=["POST"])
def run_scan():
"""
Accepts JSON: { "target": "127.0.0.1" }
Runs Nmap scan → CVE lookup → saves to DB.
Returns scan_id and summary.
"""
data = request.get_json()
if not data or "target" not in data:
return jsonify({"error": "No target provided"}), 400
target = data["target"].strip()
if not target:
return jsonify({"error": "Target cannot be empty"}), 400
if not is_valid_target(target):
return jsonify({"error": "Invalid target format. Must be a valid IP address or domain name."}), 400
print(f"\n[*] Scan requested for: {target}")
# Step 1: Nmap scan
scan_data = scan_target(target)
if not scan_data or not scan_data.get("services"):
return jsonify({
"error": f"No open services found on {target}. Host may be down or unreachable."
}), 404
# Step 2: CVE lookup for each service
cve_results = lookup_cves_for_services(scan_data["services"])
# Step 3: Save to database
scan_id = save_scan(scan_data, cve_results)
return jsonify({
"scan_id": scan_id,
"target": target,
"total_services": len(scan_data["services"]),
"total_cves": len(cve_results),
"message": f"Scan complete. Found {len(cve_results)} CVE(s) across {len(scan_data['services'])} service(s)."
})
@app.route("/results/<int:scan_id>")
def get_results(scan_id):
"""
Returns CVE results and severity summary for a specific scan as JSON.
Used by the dashboard to load results dynamically.
"""
cves = get_cves_by_scan(scan_id)
summary = get_severity_summary(scan_id)
scans = get_all_scans()
# Find the scan record
scan = next((s for s in scans if s["id"] == scan_id), None)
if not scan:
return jsonify({"error": "Scan not found"}), 404
return jsonify({
"scan": scan,
"cves": cves,
"severity_summary": summary
})
@app.route("/scans")
def list_scans():
"""Returns all past scans as JSON."""
scans = get_all_scans()
return jsonify(scans)
@app.route("/export/csv/<int:scan_id>")
def export_csv(scan_id):
"""Generate and download a CSV report for a given scan."""
scans = get_all_scans()
scan = next((s for s in scans if s["id"] == scan_id), None)
if not scan:
return jsonify({"error": "Scan not found"}), 404
cves = get_cves_by_scan(scan_id)
csv_content = generate_csv(scan, cves)
filename = f"cve_report_{scan['target']}_{scan_id}.csv"
return Response(
csv_content,
mimetype="text/csv",
headers={"Content-disposition": f"attachment; filename={filename}"}
)
@app.route("/export/pdf/<int:scan_id>")
def export_pdf(scan_id):
"""Generate and download a PDF report for a given scan."""
scans = get_all_scans()
scan = next((s for s in scans if s["id"] == scan_id), None)
if not scan:
return jsonify({"error": "Scan not found"}), 404
cves = get_cves_by_scan(scan_id)
pdf_bytes = generate_pdf(scan, cves)
filename = f"cve_report_{scan['target']}_{scan_id}.pdf"
return Response(
pdf_bytes,
mimetype="application/pdf",
headers={"Content-disposition": f"attachment; filename={filename}"}
)
@app.route("/api/cves")
def list_all_cves():
"""Returns all CVE results across all scans as JSON."""
cves = get_all_cves()
return jsonify(cves)
@app.route("/api/settings", methods=["POST"])
def update_settings():
"""Accepts JSON: { "api_key": "..." } and updates NVD_API_KEY in .env."""
data = request.get_json()
if not data or "api_key" not in data:
return jsonify({"error": "No api_key provided"}), 400
api_key = data["api_key"].strip()
# Read existing env file if present
env_lines = []
if os.path.exists(".env"):
try:
with open(".env", "r") as f:
env_lines = f.readlines()
except Exception:
pass
# Update or add NVD_API_KEY
key_found = False
new_lines = []
for line in env_lines:
if line.strip().startswith("NVD_API_KEY="):
new_lines.append(f"NVD_API_KEY={api_key}\n")
key_found = True
else:
new_lines.append(line)
if not key_found:
new_lines.append(f"NVD_API_KEY={api_key}\n")
try:
with open(".env", "w") as f:
f.writelines(new_lines)
except Exception as e:
return jsonify({"error": f"Failed to write to .env: {e}"}), 500
# Reload environment variable locally so the running server uses it immediately
os.environ["NVD_API_KEY"] = api_key
return jsonify({"success": True, "message": "API key saved successfully."})
if __name__ == "__main__":
debug_mode = os.getenv("FLASK_DEBUG", "false").lower() in ("true", "1")
app.run(debug=debug_mode, port=5000)