feat: challenge registry-driven platform + XVI/XVII imports + admin toggle + domain rename
- Rename repo/domain: attack-defense-platform / attackdefense.imrnes.team (all refs replaced)
- challenge_registry.json: single source of truth (28 challs across gemastik18/xvi/xvii)
- teams.py: registry-driven CHALLENGES, set_challenge_enabled, sync_challenge_runtime
(apply enable/disable to live teams: build/up or stop/remove + receiver restart)
- compose_gen.py: render per-team compose from canonical per-challenge templates
(image reuse, per-team ports 30xxx, flag mounts, passwords)
- gen_canonical_composes.py: canonical docker-compose.yml for all services
- import_new_challenges.py: import XVI/XVII services + EOL base image fixes
(debian:buster→bookworm, node:14→20, python:3.7-slim→3.11)
- receiver: xvi package (10 checkers) + xvii package (12 generic checkers),
Challenge base reads PASSWORD_<team_port> from env; gen_receiver_main.py
generates per-team main.py from registry
- main.py: /api/challenges returns full registry; PATCH /api/challenges/<name>
toggles enabled + applies to live teams
- index.html: 🏗️ Challenge Manager tab (toggle per challenge, grouped by set)
- SLA bonus now dynamic (all enabled challenges, not hardcoded 6)
This commit is contained in:
1 parent
c35b23a37f
commit
c6fd9ec268
1317 files changed
+306586
-128
No files matched your search
@@ -0,0 +1,3 @@
|
||||
Acquire::AllowInsecureRepositories "true";
|
||||
Acquire::AllowDowngradeToInsecureRepositories "true";
|
||||
Apt::Get::AllowUnauthenticated "true";
|
||||
@@ -0,0 +1,35 @@
|
||||
FROM python:3.11-slim-bookworm
|
||||
|
||||
ARG PASSWORD
|
||||
|
||||
COPY apt-insecure.conf /etc/apt/apt.conf.d/99gemastik-insecure
|
||||
RUN apt-get -o Acquire::AllowInsecureRepositories=true update
|
||||
RUN apt-get -y --allow-unauthenticated install -y openssh-server curl nano cron
|
||||
|
||||
RUN echo root:${PASSWORD} | chpasswd
|
||||
RUN echo "PasswordAuthentication yes" >> /etc/ssh/sshd_config
|
||||
RUN echo "PermitRootLogin yes" >> /etc/ssh/sshd_config
|
||||
RUN service ssh start
|
||||
RUN useradd --user-group --system --create-home --no-log-init --shell /bin/bash ctf
|
||||
|
||||
WORKDIR /home/ctf/app
|
||||
|
||||
COPY requirements.txt .
|
||||
RUN pip install -r ./requirements.txt && rm ./requirements.txt
|
||||
|
||||
COPY src/ .
|
||||
RUN mkdir report
|
||||
RUN chown -R ctf:ctf /home/ctf/app/report
|
||||
COPY entrypoint.sh .
|
||||
RUN chmod +x entrypoint.sh
|
||||
|
||||
# Add the cron job
|
||||
RUN echo "*/5 * * * * find /home/ctf/app/report -type f -mmin +5 -exec rm -f {} \;" > /etc/cron.d/clean_report
|
||||
|
||||
# Give execution rights on the cron job
|
||||
RUN chmod 0644 /etc/cron.d/clean_report
|
||||
|
||||
# Apply the cron job
|
||||
RUN crontab /etc/cron.d/clean_report
|
||||
|
||||
ENTRYPOINT [ "./entrypoint.sh" ]
|
||||
@@ -0,0 +1,3 @@
|
||||
Acquire::AllowInsecureRepositories "true";
|
||||
Acquire::AllowDowngradeToInsecureRepositories "true";
|
||||
Apt::Get::AllowUnauthenticated "true";
|
||||
@@ -0,0 +1,5 @@
|
||||
#!/bin/bash
|
||||
cron
|
||||
/usr/sbin/sshd -D &
|
||||
sleep 3
|
||||
su ctf -c "python app.py"
|
||||
@@ -0,0 +1,4 @@
|
||||
tornado
|
||||
python-engineio
|
||||
python-socketio
|
||||
requests
|
||||
@@ -0,0 +1,251 @@
|
||||
import tornado.ioloop
|
||||
import tornado.web
|
||||
import tornado.websocket
|
||||
import tornado.gen
|
||||
import threading
|
||||
import requests
|
||||
import json
|
||||
import asyncio
|
||||
import ipaddress
|
||||
import socket
|
||||
import random
|
||||
import uuid
|
||||
import os
|
||||
from urllib.parse import urlparse
|
||||
import urllib3
|
||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||
|
||||
# Dictionary to keep track of WebSocket clients
|
||||
clients = {}
|
||||
|
||||
# Expanded templates for more diverse scan results
|
||||
vulnerability_templates = [
|
||||
{'template': 'CVE-2021-26855', 'severity': 'critical', 'description': 'Exchange Server SSRF vulnerability detected.'},
|
||||
{'template': 'CVE-2021-34473', 'severity': 'high', 'description': 'ProxyShell vulnerability detected.'},
|
||||
{'template': 'CVE-2021-34527', 'severity': 'critical', 'description': 'PrintNightmare vulnerability detected.'},
|
||||
{'template': 'CVE-2020-1472', 'severity': 'critical', 'description': 'Zerologon vulnerability detected.'},
|
||||
{'template': 'CVE-2019-19781', 'severity': 'medium', 'description': 'Citrix ADC Remote Code Execution detected.'},
|
||||
{'template': 'CVE-2020-0601', 'severity': 'high', 'description': 'CryptoAPI Spoofing vulnerability detected.'},
|
||||
{'template': 'CVE-2021-21972', 'severity': 'high', 'description': 'VMware vSphere Client RCE detected.'},
|
||||
{'template': 'CVE-2017-11882', 'severity': 'medium', 'description': 'Microsoft Office Memory Corruption detected.'}
|
||||
]
|
||||
|
||||
# Let's simulate a scan. We are not doing an actual scan on this PoC to prevent unnecessary problem in the network.
|
||||
# For actual scanning it should be straight forward. We just need to run all the tools with subprocess, catch the results and send them to the client.
|
||||
async def simulate_scan(target, ws_handler, scan_id):
|
||||
ports = random.sample([22, 80, 443, 8080, 3306, 5432], random.randint(2, 4))
|
||||
dummy_progress = [
|
||||
"Initializing scan engine...",
|
||||
"Checking for internet connectivity...",
|
||||
"Loading vulnerability templates...",
|
||||
f"Starting scan with scan_id {scan_id}...",
|
||||
f"Target acquired: {target}",
|
||||
f"Scanning target IP: {random.randint(100, 255)}.{random.randint(0, 255)}.{random.randint(0, 255)}.{random.randint(0, 255)}",
|
||||
f"Running OS fingerprinting...",
|
||||
f"Port scan started on {target}...",
|
||||
f"Detected services on ports: {ports}",
|
||||
"Loading 15 vulnerability templates for scanning...",
|
||||
"[info] Running template: CVE-2021-26855 (Exchange Server SSRF)",
|
||||
"[info] Running template: CVE-2021-34473 (ProxyShell)",
|
||||
"[info] Running template: CVE-2021-34527 (PrintNightmare)",
|
||||
"[info] Running template: CVE-2020-1472 (Zerologon)",
|
||||
"[info] Running template: CVE-2019-19781 (Citrix ADC Remote Code Execution)",
|
||||
"[info] Running template: CVE-2020-0601 (CryptoAPI Spoofing)",
|
||||
"[info] Running template: CVE-2021-21972 (VMware vSphere RCE)",
|
||||
"[info] Running template: CVE-2017-11882 (Office Memory Corruption)",
|
||||
"Analyzing detected vulnerabilities...",
|
||||
"Compiling result..."
|
||||
]
|
||||
|
||||
for progress in dummy_progress:
|
||||
await asyncio.sleep(0.5)
|
||||
try:
|
||||
ws_handler.write_message(json.dumps({"event": "scan_progress", 'data': progress}))
|
||||
except Exception:
|
||||
print("WebSocket connection closed, cannot send progress.", flush=True)
|
||||
|
||||
# Emit the final scan results
|
||||
try:
|
||||
ws_handler.write_message(json.dumps({"event": "scan_result", 'data': f"Result saved to report/{scan_id}.json. Please contact admin for detailed report"}))
|
||||
except Exception:
|
||||
print("WebSocket connection closed, cannot send final result.", flush=True)
|
||||
|
||||
# Final results
|
||||
dummy_results = random.sample(vulnerability_templates, random.randint(2, 5))
|
||||
|
||||
with open(f"report/{scan_id}.json", "w") as f:
|
||||
json.dump(
|
||||
{
|
||||
"open_ports": [str(i) for i in ports],
|
||||
"vulnerabilities": dummy_results,
|
||||
"scan_status": "completed",
|
||||
"scan_duration": f"{random.randint(1,10)} minutes"
|
||||
}, f)
|
||||
|
||||
def start_scan_wrapper(target, ws_handler, scan_id):
|
||||
loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(loop)
|
||||
|
||||
loop.run_until_complete(simulate_scan(target, ws_handler, scan_id))
|
||||
loop.close()
|
||||
|
||||
def get_local_ips():
|
||||
"""Get a list of all local IP addresses of the server."""
|
||||
local_ips = []
|
||||
hostname = socket.gethostname()
|
||||
for ip in socket.gethostbyname_ex(hostname)[2]:
|
||||
local_ips.append(ipaddress.ip_address(ip))
|
||||
return local_ips
|
||||
|
||||
def is_local_ip(ip, local_ips):
|
||||
"""Check if the IP address is one of the local IP addresses of the server."""
|
||||
try:
|
||||
ip_obj = ipaddress.ip_address(ip)
|
||||
return ip_obj in local_ips or ip_obj.is_loopback
|
||||
except ValueError:
|
||||
return False
|
||||
|
||||
def validate_url(url):
|
||||
parsed_url = urlparse(url)
|
||||
if parsed_url.scheme != "http" and parsed_url.scheme != "https":
|
||||
return False
|
||||
|
||||
local_ips = get_local_ips()
|
||||
netloc = parsed_url.netloc.split(":")[0]
|
||||
|
||||
# domain check
|
||||
if not netloc.replace(".", "").isdigit():
|
||||
# resolve the domain to ip
|
||||
try:
|
||||
ip = socket.gethostbyname(netloc)
|
||||
except socket.gaierror:
|
||||
return False
|
||||
|
||||
if is_local_ip(ip, local_ips):
|
||||
return False
|
||||
else:
|
||||
# check if the ip is local
|
||||
if is_local_ip(netloc, local_ips):
|
||||
return False
|
||||
return True
|
||||
|
||||
# WebSocket handler
|
||||
class WebSocketHandler(tornado.websocket.WebSocketHandler):
|
||||
def check_origin(self, origin):
|
||||
return True
|
||||
|
||||
def open(self):
|
||||
self.cid = self.get_argument("client_id")
|
||||
print("WebSocket opened", flush=True)
|
||||
clients[self.cid] = self
|
||||
|
||||
def on_message(self, message):
|
||||
print(f"Received message: {message}")
|
||||
|
||||
def on_close(self):
|
||||
print("WebSocket closed", flush=True)
|
||||
if self.cid in clients:
|
||||
del clients[self.cid]
|
||||
|
||||
# Start scan endpoint
|
||||
class StartScanHandler(tornado.web.RequestHandler):
|
||||
def post(self):
|
||||
data = json.loads(self.request.body)
|
||||
target = data.get('target')
|
||||
if not validate_url(target):
|
||||
self.set_status(400)
|
||||
self.write(json.dumps({'error': 'Invalid Target'}))
|
||||
return
|
||||
cid = data.get('client_id')
|
||||
scan_id = str(uuid.uuid4())
|
||||
|
||||
if not target:
|
||||
self.set_status(400)
|
||||
self.finish(json.dumps({'error': 'No target provided'}))
|
||||
elif cid not in clients:
|
||||
self.set_status(400)
|
||||
self.finish(json.dumps({'error': 'Invalid client ID'}))
|
||||
else:
|
||||
# Simulate the scan in a background thread
|
||||
threading.Thread(target=start_scan_wrapper, args=(target, clients[cid], scan_id)).start()
|
||||
self.write(json.dumps({'status': 'scanning started', 'scan_id': scan_id}))
|
||||
|
||||
# Test website handler (GET/POST)
|
||||
class TestWebsiteHandler(tornado.web.RequestHandler):
|
||||
def post(self):
|
||||
data = json.loads(self.request.body)
|
||||
url = data.get('url')
|
||||
if not url:
|
||||
self.set_status(400)
|
||||
self.write(json.dumps({'error': 'No URL provided'}))
|
||||
return
|
||||
|
||||
self.check_url_alive(url)
|
||||
|
||||
def check_url_alive(self, url):
|
||||
try:
|
||||
if not validate_url(url):
|
||||
self.set_status(400)
|
||||
self.write(json.dumps({'error': 'Invalid Target'}))
|
||||
return
|
||||
|
||||
# cleanup url
|
||||
parsed_url = urlparse(url)
|
||||
cleaned_url = f"{parsed_url.scheme}://{parsed_url.netloc}"
|
||||
|
||||
# Check if the URL is alive
|
||||
response = requests.get(cleaned_url, allow_redirects=False, verify=False, timeout=10)
|
||||
self.set_status(response.status_code)
|
||||
self.finish()
|
||||
except requests.RequestException:
|
||||
self.set_status(500)
|
||||
self.finish()
|
||||
|
||||
# Generate Report Endpoint (reads from the saved JSON report file)
|
||||
class GenerateReportHandler(tornado.web.RequestHandler):
|
||||
def get(self):
|
||||
scan_id = self.get_argument("scan_id", None)
|
||||
report_name = self.get_argument("report_name", "")
|
||||
|
||||
if not scan_id:
|
||||
self.set_status(400)
|
||||
self.write(json.dumps({"error": "Missing scan_id"}))
|
||||
return
|
||||
|
||||
# File path for the report
|
||||
report_file = f"report/{scan_id}.json"
|
||||
|
||||
# Check if the report file exists
|
||||
if not os.path.exists(report_file):
|
||||
self.set_status(404)
|
||||
self.write(json.dumps({"error": f"Report for scan_id {scan_id} not found"}))
|
||||
return
|
||||
|
||||
# Load the report data from the JSON file
|
||||
with open(report_file, "r") as f:
|
||||
scan_data = json.load(f)
|
||||
|
||||
# Read the HTML template from the file
|
||||
with open("report_template.html", "r") as template:
|
||||
report_template = template.read()
|
||||
|
||||
report_template = report_template.replace("<REPORT_NAME>", report_name)
|
||||
|
||||
# Dynamically render the report with scan data
|
||||
self.write(tornado.template.Template(report_template).generate(
|
||||
scan_id=scan_id,
|
||||
scan_data=scan_data
|
||||
))
|
||||
|
||||
def make_app():
|
||||
return tornado.web.Application([
|
||||
(r"/api/public/ws/", WebSocketHandler),
|
||||
(r"/api/public/startscan", StartScanHandler),
|
||||
(r"/api/public/test", TestWebsiteHandler),
|
||||
(r"/api/private/report", GenerateReportHandler)
|
||||
])
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = make_app()
|
||||
app.listen(5000)
|
||||
tornado.ioloop.IOLoop.instance().start()
|
||||
@@ -0,0 +1,60 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Scan Report</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container mt-5">
|
||||
<h1 class="text-center">Scan Report <REPORT_NAME></h1>
|
||||
<table class="table table-bordered mt-3">
|
||||
<thead class="thead-dark">
|
||||
<tr>
|
||||
<th scope="col">Attribute</th>
|
||||
<th scope="col">Details</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><strong>Scan ID</strong></td>
|
||||
<td>{{ scan_id }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Open Ports</strong></td>
|
||||
<td>{{ ','.join(scan_data["open_ports"]) }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Scan Status</strong></td>
|
||||
<td>{{ scan_data["scan_status"] }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Scan Duration</strong></td>
|
||||
<td>{{ scan_data["scan_duration"] }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2 class="mt-5">Vulnerabilities</h2>
|
||||
<table class="table table-bordered mt-3">
|
||||
<thead class="thead-dark">
|
||||
<tr>
|
||||
<th scope="col">Template</th>
|
||||
<th scope="col">Severity</th>
|
||||
<th scope="col">Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for vuln in scan_data["vulnerabilities"] %}
|
||||
<tr>
|
||||
<td>{{ vuln["template"] }}</td>
|
||||
<td>{{ vuln["severity"] }}</td>
|
||||
<td>{{ vuln["description"] }}</td>
|
||||
</tr>
|
||||
{% end %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,17 @@
|
||||
services:
|
||||
tempest-poc:
|
||||
container_name: tempest-poc_container
|
||||
hostname: tempest-poc
|
||||
restart: always
|
||||
build:
|
||||
context: ./tempest-poc/frontend
|
||||
dockerfile: Dockerfile
|
||||
ports:
|
||||
- "14080:80"
|
||||
- "14102:22"
|
||||
extra_hosts:
|
||||
- "host.docker.internal:host-gateway"
|
||||
backend:
|
||||
build:
|
||||
context: ./tempest-poc/backend
|
||||
dockerfile: Dockerfile
|
||||
@@ -0,0 +1,9 @@
|
||||
FROM public.ecr.aws/nginx/nginx:alpine
|
||||
|
||||
RUN rm /etc/nginx/conf.d/default.conf
|
||||
|
||||
COPY nginx.conf /etc/nginx/conf.d/
|
||||
|
||||
COPY . /usr/share/nginx/html
|
||||
|
||||
EXPOSE 80
|
||||
@@ -0,0 +1,3 @@
|
||||
Acquire::AllowInsecureRepositories "true";
|
||||
Acquire::AllowDowngradeToInsecureRepositories "true";
|
||||
Apt::Get::AllowUnauthenticated "true";
|
||||
@@ -0,0 +1,24 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Tempest | Futuristic Website Scanner</title>
|
||||
<link rel="stylesheet" href="static/css/styles.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>Tempest PoC</h1>
|
||||
<div class="input-group">
|
||||
<input type="url" id="target" placeholder="Enter target URL" required>
|
||||
</div>
|
||||
<div class="button-group">
|
||||
<button id="testConnection">Test Connection</button>
|
||||
<button id="startScan" disabled>Start Scan</button>
|
||||
</div>
|
||||
<div id="statusAndResults"></div>
|
||||
</div>
|
||||
|
||||
<script src="static/js/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,38 @@
|
||||
server {
|
||||
listen 80;
|
||||
|
||||
location / {
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
}
|
||||
|
||||
# Proxy for WebSocket and HTTP requests
|
||||
location ~ ^/api/public {
|
||||
proxy_pass http://tempest-poc:5000;
|
||||
proxy_http_version 1.1;
|
||||
|
||||
# WebSocket-specific headers
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
|
||||
# Conditionally set the Connection header
|
||||
set $connection_upgrade '';
|
||||
if ($http_upgrade) {
|
||||
set $connection_upgrade 'Upgrade';
|
||||
}
|
||||
proxy_set_header Connection $connection_upgrade;
|
||||
|
||||
proxy_buffering off;
|
||||
proxy_cache off;
|
||||
|
||||
# Optional: Increase timeouts to handle WebSocket connections
|
||||
proxy_read_timeout 3600;
|
||||
proxy_send_timeout 3600;
|
||||
proxy_connect_timeout 3600;
|
||||
|
||||
# Additional headers to pass
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
@import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700&family=Roboto:wght@300;400&display=swap');
|
||||
|
||||
:root {
|
||||
--bg-color: #0a0e17;
|
||||
--text-color: #e0e0e0;
|
||||
--primary-color: #00ffff;
|
||||
--secondary-color: #ff00ff;
|
||||
--accent-color: #ffff00;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Roboto', sans-serif;
|
||||
line-height: 1.6;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: var(--bg-color);
|
||||
color: var(--text-color);
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.container {
|
||||
width: 100%;
|
||||
max-width: 600px;
|
||||
padding: 2rem;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-family: 'Orbitron', sans-serif;
|
||||
text-align: center;
|
||||
color: var(--primary-color);
|
||||
font-size: 2.5rem;
|
||||
margin-bottom: 2rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 3px;
|
||||
text-shadow: 0 0 10px var(--primary-color);
|
||||
}
|
||||
|
||||
.input-group {
|
||||
margin-bottom: 1.5rem;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
input[type="url"] {
|
||||
width: 100%;
|
||||
padding: 1rem;
|
||||
font-size: 1rem;
|
||||
border: none;
|
||||
border-bottom: 2px solid var(--primary-color);
|
||||
background-color: rgba(0, 255, 255, 0.1);
|
||||
color: var(--text-color);
|
||||
transition: all 0.3s ease;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
input[type="url"]:focus {
|
||||
outline: none;
|
||||
box-shadow: 0 0 10px var(--primary-color);
|
||||
}
|
||||
|
||||
.button-group {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 0.8rem 1.5rem;
|
||||
font-size: 1rem;
|
||||
font-family: 'Orbitron', sans-serif;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
border: none;
|
||||
border-radius: 25px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
margin: 0 0.5rem;
|
||||
}
|
||||
|
||||
#testConnection {
|
||||
background: linear-gradient(45deg, var(--primary-color), var(--secondary-color));
|
||||
color: var(--bg-color);
|
||||
}
|
||||
|
||||
#startScan {
|
||||
background: linear-gradient(45deg, var(--secondary-color), var(--accent-color));
|
||||
color: var(--bg-color);
|
||||
}
|
||||
|
||||
button:hover {
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
button:active {
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
button:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
#statusAndResults {
|
||||
margin-top: 1.5rem;
|
||||
padding: 1rem;
|
||||
border-radius: 10px;
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
backdrop-filter: blur(10px);
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
#statusAndResults::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
#statusAndResults::-webkit-scrollbar-track {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
#statusAndResults::-webkit-scrollbar-thumb {
|
||||
background-color: var(--primary-color);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.status-item {
|
||||
margin: 0.5rem 0;
|
||||
padding: 0.5rem;
|
||||
background-color: rgba(0, 255, 255, 0.1);
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.success {
|
||||
color: var(--accent-color);
|
||||
}
|
||||
|
||||
.error {
|
||||
color: var(--secondary-color);
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
const startScanButton = document.getElementById("startScan");
|
||||
const testConnectionButton = document.getElementById("testConnection");
|
||||
const statusAndResultsDiv = document.getElementById("statusAndResults");
|
||||
const urlInput = document.getElementById("target");
|
||||
|
||||
if (!crypto.randomUUID) {
|
||||
crypto.randomUUID = function () {
|
||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
|
||||
var r = (crypto.getRandomValues(new Uint8Array(1))[0] % 16) | 0,
|
||||
v = c === 'x' ? r : (r & 0x3) | 0x8;
|
||||
return v.toString(16);
|
||||
});
|
||||
};
|
||||
}
|
||||
const uuid = crypto.randomUUID();
|
||||
|
||||
const socket = new WebSocket(`ws://${window.location.host}/api/public/ws/?client_id=${uuid}`);
|
||||
|
||||
function addStatusItem(message, className = '') {
|
||||
const item = document.createElement('div');
|
||||
item.textContent = message;
|
||||
item.className = `status-item ${className}`;
|
||||
statusAndResultsDiv.appendChild(item);
|
||||
statusAndResultsDiv.scrollTop = statusAndResultsDiv.scrollHeight;
|
||||
}
|
||||
|
||||
socket.addEventListener("message", (event) => {
|
||||
const data = JSON.parse(event.data);
|
||||
addStatusItem(data.data);
|
||||
});
|
||||
|
||||
startScanButton.addEventListener("click", () => {
|
||||
statusAndResultsDiv.innerHTML = '';
|
||||
const target = urlInput.value;
|
||||
if (!target) return;
|
||||
|
||||
startScanButton.disabled = true;
|
||||
fetch("/api/public/startscan", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify({ target: target, client_id: uuid })
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.error) {
|
||||
addStatusItem(data.error, 'error');
|
||||
} else {
|
||||
addStatusItem('Scan initiated. Monitoring progress...', 'success');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
addStatusItem(`Error: ${error.message}`, 'error');
|
||||
})
|
||||
.finally(() => {
|
||||
urlInput.value = '';
|
||||
});
|
||||
});
|
||||
|
||||
testConnectionButton.addEventListener("click", () => {
|
||||
statusAndResultsDiv.innerHTML = '';
|
||||
const url = urlInput.value;
|
||||
if (!url) return;
|
||||
|
||||
testConnectionButton.disabled = true;
|
||||
fetch("/api/public/test", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify({ url: url })
|
||||
})
|
||||
.then(response => {
|
||||
if (!response.ok && response.status != 301) {
|
||||
addStatusItem(`Target unreachable. Status Code: ${response.status}`, 'error');
|
||||
} else {
|
||||
addStatusItem(`Target accessible. Status Code: ${response.status}`, 'success');
|
||||
startScanButton.disabled = false;
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
addStatusItem(`Error: ${error.message}`, 'error');
|
||||
})
|
||||
.finally(() => {
|
||||
testConnectionButton.disabled = false;
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user