@@ -1,214 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
"""
|
|
||||||
Dashboard template for viewing honeypot statistics.
|
|
||||||
Customize this template to change the dashboard appearance.
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
def generate_dashboard(stats: dict) -> str:
|
|
||||||
"""Generate dashboard HTML with access statistics"""
|
|
||||||
|
|
||||||
top_ips_rows = '\n'.join([
|
|
||||||
f'<tr><td class="rank">{i+1}</td><td>{ip}</td><td>{count}</td></tr>'
|
|
||||||
for i, (ip, count) in enumerate(stats['top_ips'])
|
|
||||||
]) or '<tr><td colspan="3" style="text-align:center;">No data</td></tr>'
|
|
||||||
|
|
||||||
# Generate paths rows
|
|
||||||
top_paths_rows = '\n'.join([
|
|
||||||
f'<tr><td class="rank">{i+1}</td><td>{path}</td><td>{count}</td></tr>'
|
|
||||||
for i, (path, count) in enumerate(stats['top_paths'])
|
|
||||||
]) or '<tr><td colspan="3" style="text-align:center;">No data</td></tr>'
|
|
||||||
|
|
||||||
# Generate User-Agent rows
|
|
||||||
top_ua_rows = '\n'.join([
|
|
||||||
f'<tr><td class="rank">{i+1}</td><td style="word-break: break-all;">{ua[:80]}</td><td>{count}</td></tr>'
|
|
||||||
for i, (ua, count) in enumerate(stats['top_user_agents'])
|
|
||||||
]) or '<tr><td colspan="3" style="text-align:center;">No data</td></tr>'
|
|
||||||
|
|
||||||
# Generate suspicious accesses rows
|
|
||||||
suspicious_rows = '\n'.join([
|
|
||||||
f'<tr><td>{log["ip"]}</td><td>{log["path"]}</td><td style="word-break: break-all;">{log["user_agent"][:60]}</td><td>{log["timestamp"].split("T")[1][:8]}</td></tr>'
|
|
||||||
for log in stats['recent_suspicious'][-10:]
|
|
||||||
]) or '<tr><td colspan="4" style="text-align:center;">No suspicious activity detected</td></tr>'
|
|
||||||
|
|
||||||
return f"""<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>Krawl Dashboard</title>
|
|
||||||
<style>
|
|
||||||
body {{
|
|
||||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
|
||||||
background-color: #0d1117;
|
|
||||||
color: #c9d1d9;
|
|
||||||
margin: 0;
|
|
||||||
padding: 20px;
|
|
||||||
}}
|
|
||||||
.container {{
|
|
||||||
max-width: 1400px;
|
|
||||||
margin: 0 auto;
|
|
||||||
}}
|
|
||||||
h1 {{
|
|
||||||
color: #58a6ff;
|
|
||||||
text-align: center;
|
|
||||||
margin-bottom: 40px;
|
|
||||||
}}
|
|
||||||
.stats-grid {{
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
||||||
gap: 20px;
|
|
||||||
margin-bottom: 40px;
|
|
||||||
}}
|
|
||||||
.stat-card {{
|
|
||||||
background: #161b22;
|
|
||||||
border: 1px solid #30363d;
|
|
||||||
border-radius: 6px;
|
|
||||||
padding: 20px;
|
|
||||||
text-align: center;
|
|
||||||
}}
|
|
||||||
.stat-card.alert {{
|
|
||||||
border-color: #f85149;
|
|
||||||
}}
|
|
||||||
.stat-value {{
|
|
||||||
font-size: 36px;
|
|
||||||
font-weight: bold;
|
|
||||||
color: #58a6ff;
|
|
||||||
}}
|
|
||||||
.stat-value.alert {{
|
|
||||||
color: #f85149;
|
|
||||||
}}
|
|
||||||
.stat-label {{
|
|
||||||
font-size: 14px;
|
|
||||||
color: #8b949e;
|
|
||||||
margin-top: 5px;
|
|
||||||
}}
|
|
||||||
.table-container {{
|
|
||||||
background: #161b22;
|
|
||||||
border: 1px solid #30363d;
|
|
||||||
border-radius: 6px;
|
|
||||||
padding: 20px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}}
|
|
||||||
h2 {{
|
|
||||||
color: #58a6ff;
|
|
||||||
margin-top: 0;
|
|
||||||
}}
|
|
||||||
table {{
|
|
||||||
width: 100%;
|
|
||||||
border-collapse: collapse;
|
|
||||||
}}
|
|
||||||
th, td {{
|
|
||||||
padding: 12px;
|
|
||||||
text-align: left;
|
|
||||||
border-bottom: 1px solid #30363d;
|
|
||||||
}}
|
|
||||||
th {{
|
|
||||||
background: #0d1117;
|
|
||||||
color: #58a6ff;
|
|
||||||
font-weight: 600;
|
|
||||||
}}
|
|
||||||
tr:hover {{
|
|
||||||
background: #1c2128;
|
|
||||||
}}
|
|
||||||
.rank {{
|
|
||||||
color: #8b949e;
|
|
||||||
font-weight: bold;
|
|
||||||
}}
|
|
||||||
.alert-section {{
|
|
||||||
background: #1c1917;
|
|
||||||
border-left: 4px solid #f85149;
|
|
||||||
}}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="container">
|
|
||||||
<h1>🕷️ Krawl Dashboard</h1>
|
|
||||||
|
|
||||||
<div class="stats-grid">
|
|
||||||
<div class="stat-card">
|
|
||||||
<div class="stat-value">{stats['total_accesses']}</div>
|
|
||||||
<div class="stat-label">Total Accesses</div>
|
|
||||||
</div>
|
|
||||||
<div class="stat-card">
|
|
||||||
<div class="stat-value">{stats['unique_ips']}</div>
|
|
||||||
<div class="stat-label">Unique IPs</div>
|
|
||||||
</div>
|
|
||||||
<div class="stat-card">
|
|
||||||
<div class="stat-value">{stats['unique_paths']}</div>
|
|
||||||
<div class="stat-label">Unique Paths</div>
|
|
||||||
</div>
|
|
||||||
<div class="stat-card alert">
|
|
||||||
<div class="stat-value alert">{stats['suspicious_accesses']}</div>
|
|
||||||
<div class="stat-label">Suspicious Accesses</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="table-container alert-section">
|
|
||||||
<h2>⚠️ Recent Suspicious Activity</h2>
|
|
||||||
<table>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>IP Address</th>
|
|
||||||
<th>Path</th>
|
|
||||||
<th>User-Agent</th>
|
|
||||||
<th>Time</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{suspicious_rows}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="table-container">
|
|
||||||
<h2>Top IP Addresses</h2>
|
|
||||||
<table>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>#</th>
|
|
||||||
<th>IP Address</th>
|
|
||||||
<th>Access Count</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{top_ips_rows}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="table-container">
|
|
||||||
<h2>Top Paths</h2>
|
|
||||||
<table>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>#</th>
|
|
||||||
<th>Path</th>
|
|
||||||
<th>Access Count</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{top_paths_rows}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="table-container">
|
|
||||||
<h2>Top User-Agents</h2>
|
|
||||||
<table>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>#</th>
|
|
||||||
<th>User-Agent</th>
|
|
||||||
<th>Count</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{top_ua_rows}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
"""
|
|
||||||
179
src/handler.py
179
src/handler.py
@@ -197,96 +197,114 @@ class Handler(BaseHTTPRequestHandler):
|
|||||||
"""Handle POST requests (mainly login attempts)"""
|
"""Handle POST requests (mainly login attempts)"""
|
||||||
client_ip = self._get_client_ip()
|
client_ip = self._get_client_ip()
|
||||||
user_agent = self._get_user_agent()
|
user_agent = self._get_user_agent()
|
||||||
|
post_data = ""
|
||||||
self.tracker.record_access(client_ip, self.path, user_agent)
|
|
||||||
|
|
||||||
print(f"[LOGIN ATTEMPT] {client_ip} - {self.path} - {user_agent[:50]}")
|
print(f"[LOGIN ATTEMPT] {client_ip} - {self.path} - {user_agent[:50]}")
|
||||||
|
|
||||||
content_length = int(self.headers.get('Content-Length', 0))
|
content_length = int(self.headers.get('Content-Length', 0))
|
||||||
if content_length > 0:
|
if content_length > 0:
|
||||||
post_data = self.rfile.read(content_length).decode('utf-8')
|
post_data = self.rfile.read(content_length).decode('utf-8', errors="replace")
|
||||||
|
|
||||||
print(f"[POST DATA] {post_data[:200]}")
|
print(f"[POST DATA] {post_data[:200]}")
|
||||||
|
|
||||||
|
# send the post data (body) to the record_access function so the post data can be used to detect suspicious things.
|
||||||
|
self.tracker.record_access(client_ip, self.path, user_agent, post_data)
|
||||||
|
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
|
|
||||||
self.send_response(200)
|
try:
|
||||||
self.send_header('Content-type', 'text/html')
|
self.send_response(200)
|
||||||
self.end_headers()
|
self.send_header('Content-type', 'text/html')
|
||||||
self.wfile.write(html_templates.login_error().encode())
|
self.end_headers()
|
||||||
|
self.wfile.write(html_templates.login_error().encode())
|
||||||
|
except BrokenPipeError:
|
||||||
|
# Client disconnected before receiving response, ignore silently
|
||||||
|
pass
|
||||||
|
except Exception as e:
|
||||||
|
# Log other exceptions but don't crash
|
||||||
|
print(f"[ERROR] Failed to send response to {client_ip}: {str(e)}")
|
||||||
|
|
||||||
def serve_special_path(self, path: str) -> bool:
|
def serve_special_path(self, path: str) -> bool:
|
||||||
"""Serve special paths like robots.txt, API endpoints, etc."""
|
"""Serve special paths like robots.txt, API endpoints, etc."""
|
||||||
|
|
||||||
if path == '/robots.txt':
|
try:
|
||||||
self.send_response(200)
|
if path == '/robots.txt':
|
||||||
self.send_header('Content-type', 'text/plain')
|
self.send_response(200)
|
||||||
self.end_headers()
|
self.send_header('Content-type', 'text/plain')
|
||||||
self.wfile.write(html_templates.robots_txt().encode())
|
self.end_headers()
|
||||||
return True
|
self.wfile.write(html_templates.robots_txt().encode())
|
||||||
|
return True
|
||||||
if path in ['/credentials.txt', '/passwords.txt', '/admin_notes.txt']:
|
|
||||||
self.send_response(200)
|
if path in ['/credentials.txt', '/passwords.txt', '/admin_notes.txt']:
|
||||||
self.send_header('Content-type', 'text/plain')
|
self.send_response(200)
|
||||||
self.end_headers()
|
self.send_header('Content-type', 'text/plain')
|
||||||
if 'credentials' in path:
|
self.end_headers()
|
||||||
self.wfile.write(credentials_txt().encode())
|
if 'credentials' in path:
|
||||||
else:
|
self.wfile.write(credentials_txt().encode())
|
||||||
self.wfile.write(passwords_txt().encode())
|
else:
|
||||||
return True
|
self.wfile.write(passwords_txt().encode())
|
||||||
|
return True
|
||||||
if path in ['/users.json', '/api_keys.json', '/config.json']:
|
|
||||||
self.send_response(200)
|
if path in ['/users.json', '/api_keys.json', '/config.json']:
|
||||||
self.send_header('Content-type', 'application/json')
|
self.send_response(200)
|
||||||
self.end_headers()
|
self.send_header('Content-type', 'application/json')
|
||||||
if 'users' in path:
|
self.end_headers()
|
||||||
self.wfile.write(users_json().encode())
|
if 'users' in path:
|
||||||
elif 'api_keys' in path:
|
self.wfile.write(users_json().encode())
|
||||||
self.wfile.write(api_keys_json().encode())
|
elif 'api_keys' in path:
|
||||||
else:
|
self.wfile.write(api_keys_json().encode())
|
||||||
self.wfile.write(api_response('/api/config').encode())
|
else:
|
||||||
return True
|
self.wfile.write(api_response('/api/config').encode())
|
||||||
|
return True
|
||||||
if path in ['/admin', '/admin/', '/admin/login', '/login', '/wp-login.php']:
|
|
||||||
self.send_response(200)
|
if path in ['/admin', '/admin/', '/admin/login', '/login']:
|
||||||
self.send_header('Content-type', 'text/html')
|
self.send_response(200)
|
||||||
self.end_headers()
|
self.send_header('Content-type', 'text/html')
|
||||||
self.wfile.write(html_templates.login_form().encode())
|
self.end_headers()
|
||||||
return True
|
self.wfile.write(html_templates.login_form().encode())
|
||||||
|
return True
|
||||||
if path == '/wp-admin' or path == '/wp-admin/':
|
|
||||||
self.send_response(200)
|
# WordPress login page
|
||||||
self.send_header('Content-type', 'text/html')
|
if path in ['/wp-login.php', '/wp-login', '/wp-admin', '/wp-admin/']:
|
||||||
self.end_headers()
|
self.send_response(200)
|
||||||
self.wfile.write(html_templates.login_form().encode())
|
self.send_header('Content-type', 'text/html')
|
||||||
return True
|
self.end_headers()
|
||||||
|
self.wfile.write(html_templates.wp_login().encode())
|
||||||
if path in ['/wp-content/', '/wp-includes/'] or 'wordpress' in path.lower():
|
return True
|
||||||
self.send_response(200)
|
|
||||||
self.send_header('Content-type', 'text/html')
|
if path in ['/wp-content/', '/wp-includes/'] or 'wordpress' in path.lower():
|
||||||
self.end_headers()
|
self.send_response(200)
|
||||||
self.wfile.write(html_templates.wordpress().encode())
|
self.send_header('Content-type', 'text/html')
|
||||||
return True
|
self.end_headers()
|
||||||
|
self.wfile.write(html_templates.wordpress().encode())
|
||||||
if 'phpmyadmin' in path.lower() or path in ['/pma/', '/phpMyAdmin/']:
|
return True
|
||||||
self.send_response(200)
|
|
||||||
self.send_header('Content-type', 'text/html')
|
if 'phpmyadmin' in path.lower() or path in ['/pma/', '/phpMyAdmin/']:
|
||||||
self.end_headers()
|
self.send_response(200)
|
||||||
self.wfile.write(html_templates.phpmyadmin().encode())
|
self.send_header('Content-type', 'text/html')
|
||||||
return True
|
self.end_headers()
|
||||||
|
self.wfile.write(html_templates.phpmyadmin().encode())
|
||||||
if path.startswith('/api/') or path.startswith('/api') or path in ['/.env']:
|
return True
|
||||||
self.send_response(200)
|
|
||||||
self.send_header('Content-type', 'application/json')
|
if path.startswith('/api/') or path.startswith('/api') or path in ['/.env']:
|
||||||
self.end_headers()
|
self.send_response(200)
|
||||||
self.wfile.write(api_response(path).encode())
|
self.send_header('Content-type', 'application/json')
|
||||||
return True
|
self.end_headers()
|
||||||
|
self.wfile.write(api_response(path).encode())
|
||||||
if path in ['/backup/', '/uploads/', '/private/', '/admin/', '/config/', '/database/']:
|
return True
|
||||||
self.send_response(200)
|
|
||||||
self.send_header('Content-type', 'text/html')
|
if path in ['/backup/', '/uploads/', '/private/', '/admin/', '/config/', '/database/']:
|
||||||
self.end_headers()
|
self.send_response(200)
|
||||||
self.wfile.write(directory_listing(path).encode())
|
self.send_header('Content-type', 'text/html')
|
||||||
return True
|
self.end_headers()
|
||||||
|
self.wfile.write(directory_listing(path).encode())
|
||||||
|
return True
|
||||||
|
except BrokenPipeError:
|
||||||
|
# Client disconnected, ignore silently
|
||||||
|
pass
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[ERROR] Failed to serve special path {path}: {str(e)}")
|
||||||
|
pass
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -302,6 +320,8 @@ class Handler(BaseHTTPRequestHandler):
|
|||||||
try:
|
try:
|
||||||
stats = self.tracker.get_stats()
|
stats = self.tracker.get_stats()
|
||||||
self.wfile.write(generate_dashboard(stats).encode())
|
self.wfile.write(generate_dashboard(stats).encode())
|
||||||
|
except BrokenPipeError:
|
||||||
|
pass
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error generating dashboard: {e}")
|
print(f"Error generating dashboard: {e}")
|
||||||
return
|
return
|
||||||
@@ -333,6 +353,9 @@ class Handler(BaseHTTPRequestHandler):
|
|||||||
|
|
||||||
if Handler.counter < 0:
|
if Handler.counter < 0:
|
||||||
Handler.counter = self.config.canary_token_tries
|
Handler.counter = self.config.canary_token_tries
|
||||||
|
except BrokenPipeError:
|
||||||
|
# Client disconnected, ignore silently
|
||||||
|
pass
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error generating page: {e}")
|
print(f"Error generating page: {e}")
|
||||||
|
|
||||||
|
|||||||
15
src/templates/__init__.py
Normal file
15
src/templates/__init__.py
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
"""
|
||||||
|
Templates package for the deception server.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from .template_loader import load_template, clear_cache, TemplateNotFoundError
|
||||||
|
from . import html_templates
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
'load_template',
|
||||||
|
'clear_cache',
|
||||||
|
'TemplateNotFoundError',
|
||||||
|
'html_templates',
|
||||||
|
]
|
||||||
@@ -39,6 +39,12 @@ def generate_dashboard(stats: dict) -> str:
|
|||||||
for ip, paths in stats.get('honeypot_triggered_ips', [])
|
for ip, paths in stats.get('honeypot_triggered_ips', [])
|
||||||
]) or '<tr><td colspan="3" style="text-align:center;">No honeypot triggers yet</td></tr>'
|
]) or '<tr><td colspan="3" style="text-align:center;">No honeypot triggers yet</td></tr>'
|
||||||
|
|
||||||
|
# Generate attack types rows
|
||||||
|
attack_type_rows = '\n'.join([
|
||||||
|
f'<tr><td>{log["ip"]}</td><td>{log["path"]}</td><td>{", ".join(log["attack_types"])}</td><td style="word-break: break-all;">{log["user_agent"][:60]}</td><td>{log["timestamp"].split("T")[1][:8]}</td></tr>'
|
||||||
|
for log in stats.get('attack_types', [])[-10:]
|
||||||
|
]) or '<tr><td colspan="4" style="text-align:center;">No attacks detected</td></tr>'
|
||||||
|
|
||||||
return f"""<!DOCTYPE html>
|
return f"""<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
@@ -188,6 +194,24 @@ def generate_dashboard(stats: dict) -> str:
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="table-container alert-section">
|
||||||
|
<h2>😈 Detected Attack Types</h2>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>IP Address</th>
|
||||||
|
<th>Path</th>
|
||||||
|
<th>Attack Types</th>
|
||||||
|
<th>User-Agent</th>
|
||||||
|
<th>Time</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{attack_type_rows}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="table-container">
|
<div class="table-container">
|
||||||
<h2>Top IP Addresses</h2>
|
<h2>Top IP Addresses</h2>
|
||||||
<table>
|
<table>
|
||||||
|
|||||||
20
src/templates/html/directory_listing.html
Normal file
20
src/templates/html/directory_listing.html
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head><title>Index of {path}</title>
|
||||||
|
<style>
|
||||||
|
body {{ font-family: monospace; background: #fff; padding: 20px; }}
|
||||||
|
h1 {{ border-bottom: 1px solid #ccc; padding-bottom: 10px; }}
|
||||||
|
table {{ width: 100%; border-collapse: collapse; }}
|
||||||
|
th {{ text-align: left; padding: 10px; background: #f0f0f0; }}
|
||||||
|
td {{ padding: 8px; border-bottom: 1px solid #eee; }}
|
||||||
|
a {{ color: #0066cc; text-decoration: none; }}
|
||||||
|
a:hover {{ text-decoration: underline; }}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Index of {path}</h1>
|
||||||
|
<table>
|
||||||
|
<tr><th>Name</th><th>Last Modified</th><th>Size</th></tr>
|
||||||
|
<tr><td><a href="../">Parent Directory</a></td><td>-</td><td>-</td></tr>
|
||||||
|
{rows}
|
||||||
|
</table></body></html>
|
||||||
1
src/templates/html/directory_row.html
Normal file
1
src/templates/html/directory_row.html
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<tr><td><a href="{href}">{name}</a></td><td>{date}</td><td>{size}</td></tr>
|
||||||
108
src/templates/html/login_error.html
Normal file
108
src/templates/html/login_error.html
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<meta name="robots" content="noindex, nofollow">
|
||||||
|
<title>Error</title>
|
||||||
|
<style>
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
||||||
|
background: #f5f5f5;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-height: 100vh;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
background: white;
|
||||||
|
padding: 40px;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||||
|
width: 100%;
|
||||||
|
max-width: 400px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 32px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
color: #d32f2f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-message {
|
||||||
|
background: #ffebee;
|
||||||
|
border-left: 4px solid #d32f2f;
|
||||||
|
padding: 15px;
|
||||||
|
margin: 20px 0;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: #c62828;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
color: #666;
|
||||||
|
font-size: 14px;
|
||||||
|
margin: 15px 0;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.links-section {
|
||||||
|
margin-top: 20px;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.links-section a {
|
||||||
|
color: #2196f3;
|
||||||
|
text-decoration: none;
|
||||||
|
margin: 0 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.links-section a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-btn {
|
||||||
|
display: inline-block;
|
||||||
|
margin-top: 20px;
|
||||||
|
padding: 10px 20px;
|
||||||
|
background: #2196f3;
|
||||||
|
color: white;
|
||||||
|
text-decoration: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-btn:hover {
|
||||||
|
background: #1976d2;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<h1>⚠ Error</h1>
|
||||||
|
|
||||||
|
<div class="error-message">
|
||||||
|
Login Failed. Please try again.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p>If the problem persists, please contact support.</p>
|
||||||
|
|
||||||
|
<div class="links-section">
|
||||||
|
<a href="/forgot-password">Forgot password?</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a href="/" class="back-btn">← Back to Home</a>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
156
src/templates/html/login_form.html
Normal file
156
src/templates/html/login_form.html
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<meta name="robots" content="noindex, nofollow">
|
||||||
|
<title>Admin Login</title>
|
||||||
|
<style>
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
||||||
|
background: #f5f5f5;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-height: 100vh;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
background: white;
|
||||||
|
padding: 40px;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||||
|
width: 100%;
|
||||||
|
max-width: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 28px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
color: #333;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle {
|
||||||
|
text-align: center;
|
||||||
|
color: #666;
|
||||||
|
font-size: 14px;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
display: block;
|
||||||
|
margin: 15px 0 5px 0;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #333;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="text"],
|
||||||
|
input[type="password"],
|
||||||
|
input[type="email"] {
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px;
|
||||||
|
margin: 5px 0 15px 0;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 14px;
|
||||||
|
transition: border-color 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="text"]:focus,
|
||||||
|
input[type="password"]:focus,
|
||||||
|
input[type="email"]:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #2196f3;
|
||||||
|
box-shadow: 0 0 0 2px rgba(33, 150, 243, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.remember-me {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin: 15px 0;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.remember-me input {
|
||||||
|
margin: 0 8px 0 0;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px;
|
||||||
|
background: #2196f3;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.3s;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover {
|
||||||
|
background: #1976d2;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:active {
|
||||||
|
background: #1565c0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.links {
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 20px;
|
||||||
|
padding-top: 20px;
|
||||||
|
border-top: 1px solid #eee;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.links a {
|
||||||
|
color: #2196f3;
|
||||||
|
text-decoration: none;
|
||||||
|
display: block;
|
||||||
|
margin: 8px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.links a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<h1>Admin Panel</h1>
|
||||||
|
<p class="subtitle">Please log in to continue</p>
|
||||||
|
|
||||||
|
<form action="/admin/login" method="post">
|
||||||
|
<label for="username">Username</label>
|
||||||
|
<input type="text" id="username" name="username" placeholder="Enter your username" autocomplete="username" autofocus required>
|
||||||
|
|
||||||
|
<label for="password">Password</label>
|
||||||
|
<input type="password" id="password" name="password" placeholder="Enter your password" autocomplete="current-password" required>
|
||||||
|
|
||||||
|
<div class="remember-me">
|
||||||
|
<input type="checkbox" id="remember" name="remember" value="1">
|
||||||
|
<label for="remember" style="margin: 0;">Remember me</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit">Sign In</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div class="links">
|
||||||
|
<a href="/forgot-password">Forgot your password?</a>
|
||||||
|
<a href="/">← Back to Home</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
167
src/templates/html/phpmyadmin.html
Normal file
167
src/templates/html/phpmyadmin.html
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<meta name="robots" content="noindex,nofollow">
|
||||||
|
<title>phpMyAdmin</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: 'Segoe UI', Tahoma, sans-serif;
|
||||||
|
margin: 0;
|
||||||
|
background: #f0f0f0;
|
||||||
|
}
|
||||||
|
.header {
|
||||||
|
background: #2979ff;
|
||||||
|
color: white;
|
||||||
|
padding: 10px 20px;
|
||||||
|
}
|
||||||
|
.header h1 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
.login {
|
||||||
|
background: white;
|
||||||
|
width: 400px;
|
||||||
|
margin: 100px auto;
|
||||||
|
padding: 30px;
|
||||||
|
border-radius: 4px;
|
||||||
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
.login h2 {
|
||||||
|
margin-top: 0;
|
||||||
|
color: #333;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
input[type="text"],
|
||||||
|
input[type="password"],
|
||||||
|
select {
|
||||||
|
width: 100%;
|
||||||
|
padding: 8px;
|
||||||
|
margin: 8px 0;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 4px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
input[type="text"]:focus,
|
||||||
|
input[type="password"]:focus,
|
||||||
|
select:focus {
|
||||||
|
border-color: #2979ff;
|
||||||
|
outline: none;
|
||||||
|
box-shadow: 0 0 0 2px rgba(41, 121, 255, 0.1);
|
||||||
|
}
|
||||||
|
label {
|
||||||
|
display: block;
|
||||||
|
margin: 12px 0 5px 0;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #333;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px 20px;
|
||||||
|
background: #2979ff;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-top: 15px;
|
||||||
|
}
|
||||||
|
button:hover {
|
||||||
|
background: #1565c0;
|
||||||
|
}
|
||||||
|
button:active {
|
||||||
|
background: #0d47a1;
|
||||||
|
}
|
||||||
|
.lang-select {
|
||||||
|
margin-top: 15px;
|
||||||
|
padding-top: 15px;
|
||||||
|
border-top: 1px solid #eee;
|
||||||
|
}
|
||||||
|
.lang-select label {
|
||||||
|
display: inline;
|
||||||
|
margin-right: 10px;
|
||||||
|
margin: 0 10px 0 0;
|
||||||
|
}
|
||||||
|
.lang-select select {
|
||||||
|
width: auto;
|
||||||
|
padding: 5px 10px;
|
||||||
|
margin: 8px 0;
|
||||||
|
}
|
||||||
|
.footer {
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 20px;
|
||||||
|
padding-top: 15px;
|
||||||
|
border-top: 1px solid #eee;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #888;
|
||||||
|
}
|
||||||
|
.footer a {
|
||||||
|
color: #2979ff;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
.footer a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.login {
|
||||||
|
width: 90%;
|
||||||
|
margin: 50px auto;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
.login h2 {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="header">
|
||||||
|
<h1>phpMyAdmin</h1>
|
||||||
|
</div>
|
||||||
|
<div class="login">
|
||||||
|
<h2>MySQL Server Login</h2>
|
||||||
|
<form action="/phpmyadmin/index.php" method="post" autocomplete="off">
|
||||||
|
<label for="username">Username:</label>
|
||||||
|
<input type="text" name="pma_username" id="username" placeholder="Username" autocomplete="username" autofocus>
|
||||||
|
|
||||||
|
<label for="password">Password:</label>
|
||||||
|
<input type="password" name="pma_password" id="password" placeholder="Password" autocomplete="current-password">
|
||||||
|
|
||||||
|
<button type="submit">Go</button>
|
||||||
|
|
||||||
|
<div class="lang-select">
|
||||||
|
<label for="lang">Language:</label>
|
||||||
|
<select name="lang" id="lang">
|
||||||
|
<option value="en" selected>English</option>
|
||||||
|
<option value="de">Deutsch</option>
|
||||||
|
<option value="es">Español</option>
|
||||||
|
<option value="fr">Français</option>
|
||||||
|
<option value="it">Italiano</option>
|
||||||
|
<option value="ja">日本語</option>
|
||||||
|
<option value="ko">한국어</option>
|
||||||
|
<option value="nl">Nederlands</option>
|
||||||
|
<option value="pl">Polski</option>
|
||||||
|
<option value="pt">Português</option>
|
||||||
|
<option value="ru">Русский</option>
|
||||||
|
<option value="zh">中文</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<input type="hidden" name="token" value="a1b2c3d4e5f6g7h8i9j0">
|
||||||
|
<input type="hidden" name="set_session" value="1">
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div class="footer">
|
||||||
|
<a href="https://www.phpmyadmin.net/docs/" target="_blank">Documentation</a> |
|
||||||
|
<a href="https://www.phpmyadmin.net/" target="_blank">Official Homepage</a> |
|
||||||
|
<a href="https://github.com/phpmyadmin/phpmyadmin" target="_blank">Contribute</a>
|
||||||
|
<br><br>
|
||||||
|
phpMyAdmin 5.2.1
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
21
src/templates/html/robots.txt
Normal file
21
src/templates/html/robots.txt
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
User-agent: *
|
||||||
|
Disallow: /admin/
|
||||||
|
Disallow: /api/
|
||||||
|
Disallow: /backup/
|
||||||
|
Disallow: /config/
|
||||||
|
Disallow: /database/
|
||||||
|
Disallow: /private/
|
||||||
|
Disallow: /uploads/
|
||||||
|
Disallow: /wp-admin/
|
||||||
|
Disallow: /login/
|
||||||
|
Disallow: /admin/login
|
||||||
|
Disallow: /phpMyAdmin/
|
||||||
|
Disallow: /admin/login.php
|
||||||
|
Disallow: /api/v1/users
|
||||||
|
Disallow: /api/v2/secrets
|
||||||
|
Disallow: /.env
|
||||||
|
Disallow: /credentials.txt
|
||||||
|
Disallow: /passwords.txt
|
||||||
|
Disallow: /.git/
|
||||||
|
Disallow: /backup.sql
|
||||||
|
Disallow: /db_backup.sql
|
||||||
73
src/templates/html/wordpress.html
Normal file
73
src/templates/html/wordpress.html
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en-US">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>My Blog – Just another WordPress site</title>
|
||||||
|
<link rel='dns-prefetch' href='//s.w.org' />
|
||||||
|
<link rel='stylesheet' id='wp-block-library-css' href='/wp-includes/css/dist/block-library/style.min.css' type='text/css' media='all' />
|
||||||
|
<link rel='stylesheet' id='twentytwentythree-style-css' href='/wp-content/themes/twentytwentythree/style.css' type='text/css' media='all' />
|
||||||
|
<link rel='https://api.w.org/' href='/wp-json/' />
|
||||||
|
<meta name="generator" content="WordPress 6.4.2" />
|
||||||
|
<style>
|
||||||
|
body {{ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; margin: 0; padding: 0; background: #fff; }}
|
||||||
|
.site-header {{ background: #23282d; color: white; padding: 20px; border-bottom: 4px solid #0073aa; }}
|
||||||
|
.site-header h1 {{ margin: 0; font-size: 28px; }}
|
||||||
|
.site-header p {{ margin: 5px 0 0; color: #d0d0d0; }}
|
||||||
|
.site-content {{ max-width: 1200px; margin: 40px auto; padding: 0 20px; }}
|
||||||
|
.entry {{ background: #fff; margin-bottom: 40px; padding: 30px; border: 1px solid #ddd; border-radius: 4px; }}
|
||||||
|
.entry-title {{ font-size: 32px; margin-top: 0; color: #23282d; }}
|
||||||
|
.entry-meta {{ color: #666; font-size: 14px; margin-bottom: 20px; }}
|
||||||
|
.entry-content {{ line-height: 1.8; color: #444; }}
|
||||||
|
.site-footer {{ background: #f7f7f7; padding: 20px; text-align: center; color: #666; border-top: 1px solid #ddd; margin-top: 60px; }}
|
||||||
|
a {{ color: #0073aa; text-decoration: none; }}
|
||||||
|
a:hover {{ text-decoration: underline; }}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body class="home blog wp-embed-responsive">
|
||||||
|
<div id="page" class="site">
|
||||||
|
<header id="masthead" class="site-header">
|
||||||
|
<div class="site-branding">
|
||||||
|
<h1 class="site-title">My Blog</h1>
|
||||||
|
<p class="site-description">Just another WordPress site</p>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div id="content" class="site-content">
|
||||||
|
<article id="post-1" class="entry">
|
||||||
|
<header class="entry-header">
|
||||||
|
<h2 class="entry-title">Hello world!</h2>
|
||||||
|
<div class="entry-meta">
|
||||||
|
<span class="posted-on">Posted on <time datetime="2024-12-01">December 1, 2024</time></span>
|
||||||
|
<span class="byline"> by <span class="author">admin</span></span>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<div class="entry-content">
|
||||||
|
<p>Welcome to WordPress. This is your first post. Edit or delete it, then start writing!</p>
|
||||||
|
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<article id="post-2" class="entry">
|
||||||
|
<header class="entry-header">
|
||||||
|
<h2 class="entry-title">About This Site</h2>
|
||||||
|
<div class="entry-meta">
|
||||||
|
<span class="posted-on">Posted on <time datetime="2024-11-28">November 28, 2024</time></span>
|
||||||
|
<span class="byline"> by <span class="author">admin</span></span>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<div class="entry-content">
|
||||||
|
<p>This is a sample page. You can use it to write about your site, yourself, or anything else you'd like.</p>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<footer id="colophon" class="site-footer">
|
||||||
|
<div class="site-info">
|
||||||
|
Proudly powered by <a href="https://wordpress.org/">WordPress</a>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
<script type='text/javascript' src='/wp-includes/js/wp-embed.min.js'></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
238
src/templates/html/wp_login.html
Normal file
238
src/templates/html/wp_login.html
Normal file
@@ -0,0 +1,238 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en-US">
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width">
|
||||||
|
<meta name="robots" content="max-image-preview:large, noindex, noarchive">
|
||||||
|
<title>Log In ‹ WordPress — WordPress</title>
|
||||||
|
<style type="text/css">
|
||||||
|
html {
|
||||||
|
background: #f0f0f1;
|
||||||
|
min-height: 100%;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
background: #f0f0f1;
|
||||||
|
min-height: 100%;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.4em;
|
||||||
|
color: #3c434a;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
a {
|
||||||
|
color: #2271b1;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
a:hover, a:active {
|
||||||
|
color: #135e96;
|
||||||
|
}
|
||||||
|
#login {
|
||||||
|
width: 320px;
|
||||||
|
padding: 8% 0 0;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
#login h1 {
|
||||||
|
text-align: center;
|
||||||
|
margin: 0 0 20px;
|
||||||
|
}
|
||||||
|
#login h1 {
|
||||||
|
text-align: center;
|
||||||
|
margin: 0 0 30px;
|
||||||
|
font-size: 28px;
|
||||||
|
font-weight: 400;
|
||||||
|
color: #3c434a;
|
||||||
|
}
|
||||||
|
|
||||||
|
#login h1 a {
|
||||||
|
color: #2271b1;
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 28px;
|
||||||
|
display: block;
|
||||||
|
background: none;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
#login h1 a:hover {
|
||||||
|
color: #135e96;
|
||||||
|
}
|
||||||
|
.login form {
|
||||||
|
margin-top: 20px;
|
||||||
|
margin-left: 0;
|
||||||
|
padding: 26px 24px 34px;
|
||||||
|
font-weight: 400;
|
||||||
|
overflow: hidden;
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #c3c4c7;
|
||||||
|
box-shadow: 0 1px 3px rgba(0, 0, 0, .04);
|
||||||
|
}
|
||||||
|
.login form .input, .login input[type="text"], .login input[type="password"] {
|
||||||
|
font-size: 24px;
|
||||||
|
width: 100%;
|
||||||
|
padding: 3px;
|
||||||
|
margin: 2px 6px 16px 0;
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #8c8f94;
|
||||||
|
box-shadow: none;
|
||||||
|
color: #2c3338;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
.login form .input:focus {
|
||||||
|
border-color: #2271b1;
|
||||||
|
box-shadow: 0 0 0 1px #2271b1;
|
||||||
|
}
|
||||||
|
.login label {
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.5;
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
.login .forgetmenot {
|
||||||
|
margin: 2px 0 24px;
|
||||||
|
}
|
||||||
|
.login .forgetmenot label {
|
||||||
|
font-size: 12px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.login .forgetmenot input {
|
||||||
|
margin: 0 4px 0 0;
|
||||||
|
}
|
||||||
|
.wp-hide-pw {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
padding: 0 15px;
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
color: #2271b1;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.wp-hide-pw:hover {
|
||||||
|
color: #135e96;
|
||||||
|
}
|
||||||
|
.user-pass-wrap {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.wp-pwd {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
#wp-submit {
|
||||||
|
float: right;
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 2.15384615;
|
||||||
|
min-height: 32px;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
border: 1px solid #2271b1;
|
||||||
|
border-radius: 3px;
|
||||||
|
white-space: nowrap;
|
||||||
|
box-sizing: border-box;
|
||||||
|
background: #2271b1;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
#wp-submit:hover {
|
||||||
|
background: #135e96;
|
||||||
|
border-color: #135e96;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
p.submit {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
#nav {
|
||||||
|
margin: 24px 0 0 0;
|
||||||
|
padding: 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
#nav a {
|
||||||
|
color: #50575e;
|
||||||
|
}
|
||||||
|
#nav a:hover {
|
||||||
|
color: #135e96;
|
||||||
|
}
|
||||||
|
#backtoblog {
|
||||||
|
margin: 16px 0 0 0;
|
||||||
|
padding: 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
#backtoblog a {
|
||||||
|
color: #50575e;
|
||||||
|
}
|
||||||
|
#backtoblog a:hover {
|
||||||
|
color: #135e96;
|
||||||
|
}
|
||||||
|
.privacy-policy-page-link {
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
.privacy-policy-page-link a {
|
||||||
|
color: #50575e;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
@media screen and (max-width: 400px) {
|
||||||
|
#login {
|
||||||
|
width: 100%;
|
||||||
|
padding: 20px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body class="login js login-action-login wp-core-ui locale-en-us">
|
||||||
|
<div id="login">
|
||||||
|
<h1><a href="https://wordpress.org/">WordPress Login</a></h1>
|
||||||
|
<form name="loginform" id="loginform" action="/wp-login.php" method="post">
|
||||||
|
<p>
|
||||||
|
<label for="user_login">Username or Email Address</label>
|
||||||
|
<input type="text" name="log" id="user_login" class="input" value="" size="20" autocapitalize="off" autocomplete="username" required>
|
||||||
|
</p>
|
||||||
|
<div class="user-pass-wrap">
|
||||||
|
<label for="user_pass">Password</label>
|
||||||
|
<div class="wp-pwd">
|
||||||
|
<input type="password" name="pwd" id="user_pass" class="input password-input" value="" size="20" autocomplete="current-password" spellcheck="false" required>
|
||||||
|
<button type="button" class="wp-hide-pw hide-if-no-js" data-toggle="0" aria-label="Show password">
|
||||||
|
<span class="dashicons dashicons-visibility" aria-hidden="true"></span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p class="forgetmenot">
|
||||||
|
<label>
|
||||||
|
<input name="rememberme" type="checkbox" id="rememberme" value="forever">
|
||||||
|
Remember Me
|
||||||
|
</label>
|
||||||
|
</p>
|
||||||
|
<p class="submit">
|
||||||
|
<input type="submit" name="wp-submit" id="wp-submit" class="button button-primary button-large" value="Log In">
|
||||||
|
<input type="hidden" name="redirect_to" value="/wp-admin/">
|
||||||
|
<input type="hidden" name="testcookie" value="1">
|
||||||
|
</p>
|
||||||
|
</form>
|
||||||
|
<p id="nav">
|
||||||
|
<a href="/wp-login.php?action=lostpassword">Lost your password?</a>
|
||||||
|
</p>
|
||||||
|
<p id="backtoblog">
|
||||||
|
<a href="/">← Go to WordPress</a>
|
||||||
|
</p>
|
||||||
|
<div class="privacy-policy-page-link">
|
||||||
|
<a href="/privacy-policy/">Privacy Policy</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
// Toggle password visibility
|
||||||
|
document.querySelector('.wp-hide-pw').addEventListener('click', function() {{
|
||||||
|
var pwdInput = document.getElementById('user_pass');
|
||||||
|
if (pwdInput.type === 'password') {{
|
||||||
|
pwdInput.type = 'text';
|
||||||
|
this.setAttribute('aria-label', 'Hide password');
|
||||||
|
}} else {{
|
||||||
|
pwdInput.type = 'password';
|
||||||
|
this.setAttribute('aria-label', 'Show password');
|
||||||
|
}}
|
||||||
|
}});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -2,228 +2,51 @@
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
HTML templates for the deception server.
|
HTML templates for the deception server.
|
||||||
Edit these templates to customize the appearance of fake pages.
|
Templates are loaded from the html/ subdirectory.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from .template_loader import load_template
|
||||||
|
|
||||||
|
|
||||||
def login_form() -> str:
|
def login_form() -> str:
|
||||||
"""Generate fake login page"""
|
"""Generate fake login page"""
|
||||||
return """<!DOCTYPE html>
|
return load_template("login_form")
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>Admin Login</title>
|
|
||||||
<style>
|
|
||||||
body { font-family: Arial, sans-serif; background: #f0f0f0; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; }
|
|
||||||
.login-box { background: white; padding: 40px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); width: 300px; }
|
|
||||||
h2 { margin-top: 0; color: #333; }
|
|
||||||
input { width: 100%; padding: 10px; margin: 10px 0; border: 1px solid #ddd; border-radius: 4px; box-sizing: border-box; }
|
|
||||||
button { width: 100%; padding: 10px; background: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer; }
|
|
||||||
button:hover { background: #0056b3; }
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="login-box">
|
|
||||||
<h2>Admin Login</h2>
|
|
||||||
<form action="/admin/login" method="post">
|
|
||||||
<input type="text" name="username" placeholder="Username" required>
|
|
||||||
<input type="password" name="password" placeholder="Password" required>
|
|
||||||
<button type="submit">Login</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>"""
|
|
||||||
|
|
||||||
|
|
||||||
def login_error() -> str:
|
def login_error() -> str:
|
||||||
"""Generate fake login error page"""
|
"""Generate fake login error page"""
|
||||||
return """<!DOCTYPE html>
|
return load_template("login_error")
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>Login Failed</title>
|
|
||||||
<style>
|
|
||||||
body { font-family: Arial, sans-serif; background: #f0f0f0; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; }
|
|
||||||
.login-box { background: white; padding: 40px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); width: 300px; }
|
|
||||||
h2 { margin-top: 0; color: #333; }
|
|
||||||
.error { color: #d63301; background: #ffebe8; border: 1px solid #d63301; padding: 12px; margin-bottom: 20px; border-radius: 4px; }
|
|
||||||
input { width: 100%; padding: 10px; margin: 10px 0; border: 1px solid #ddd; border-radius: 4px; box-sizing: border-box; }
|
|
||||||
button { width: 100%; padding: 10px; background: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer; }
|
|
||||||
button:hover { background: #0056b3; }
|
|
||||||
a { color: #007bff; font-size: 14px; }
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="login-box">
|
|
||||||
<h2>Admin Login</h2>
|
|
||||||
<div class="error"><strong>ERROR:</strong> Invalid username or password.</div>
|
|
||||||
<form action="/admin/login" method="post">
|
|
||||||
<input type="text" name="username" placeholder="Username" required>
|
|
||||||
<input type="password" name="password" placeholder="Password" required>
|
|
||||||
<button type="submit">Login</button>
|
|
||||||
</form>
|
|
||||||
<p style="margin-top: 20px; text-align: center;"><a href="/forgot-password">Forgot your password?</a></p>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>"""
|
|
||||||
|
|
||||||
|
|
||||||
def wordpress() -> str:
|
def wordpress() -> str:
|
||||||
"""Generate fake WordPress page"""
|
"""Generate fake WordPress page"""
|
||||||
return """<!DOCTYPE html>
|
return load_template("wordpress")
|
||||||
<html lang="en-US">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<title>My Blog – Just another WordPress site</title>
|
|
||||||
<link rel='dns-prefetch' href='//s.w.org' />
|
|
||||||
<link rel='stylesheet' id='wp-block-library-css' href='/wp-includes/css/dist/block-library/style.min.css' type='text/css' media='all' />
|
|
||||||
<link rel='stylesheet' id='twentytwentythree-style-css' href='/wp-content/themes/twentytwentythree/style.css' type='text/css' media='all' />
|
|
||||||
<link rel='https://api.w.org/' href='/wp-json/' />
|
|
||||||
<meta name="generator" content="WordPress 6.4.2" />
|
|
||||||
<style>
|
|
||||||
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; margin: 0; padding: 0; background: #fff; }
|
|
||||||
.site-header { background: #23282d; color: white; padding: 20px; border-bottom: 4px solid #0073aa; }
|
|
||||||
.site-header h1 { margin: 0; font-size: 28px; }
|
|
||||||
.site-header p { margin: 5px 0 0; color: #d0d0d0; }
|
|
||||||
.site-content { max-width: 1200px; margin: 40px auto; padding: 0 20px; }
|
|
||||||
.entry { background: #fff; margin-bottom: 40px; padding: 30px; border: 1px solid #ddd; border-radius: 4px; }
|
|
||||||
.entry-title { font-size: 32px; margin-top: 0; color: #23282d; }
|
|
||||||
.entry-meta { color: #666; font-size: 14px; margin-bottom: 20px; }
|
|
||||||
.entry-content { line-height: 1.8; color: #444; }
|
|
||||||
.site-footer { background: #f7f7f7; padding: 20px; text-align: center; color: #666; border-top: 1px solid #ddd; margin-top: 60px; }
|
|
||||||
a { color: #0073aa; text-decoration: none; }
|
|
||||||
a:hover { text-decoration: underline; }
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body class="home blog wp-embed-responsive">
|
|
||||||
<div id="page" class="site">
|
|
||||||
<header id="masthead" class="site-header">
|
|
||||||
<div class="site-branding">
|
|
||||||
<h1 class="site-title">My Blog</h1>
|
|
||||||
<p class="site-description">Just another WordPress site</p>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<div id="content" class="site-content">
|
|
||||||
<article id="post-1" class="entry">
|
|
||||||
<header class="entry-header">
|
|
||||||
<h2 class="entry-title">Hello world!</h2>
|
|
||||||
<div class="entry-meta">
|
|
||||||
<span class="posted-on">Posted on <time datetime="2024-12-01">December 1, 2024</time></span>
|
|
||||||
<span class="byline"> by <span class="author">admin</span></span>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
<div class="entry-content">
|
|
||||||
<p>Welcome to WordPress. This is your first post. Edit or delete it, then start writing!</p>
|
|
||||||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
|
|
||||||
<article id="post-2" class="entry">
|
|
||||||
<header class="entry-header">
|
|
||||||
<h2 class="entry-title">About This Site</h2>
|
|
||||||
<div class="entry-meta">
|
|
||||||
<span class="posted-on">Posted on <time datetime="2024-11-28">November 28, 2024</time></span>
|
|
||||||
<span class="byline"> by <span class="author">admin</span></span>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
<div class="entry-content">
|
|
||||||
<p>This is a sample page. You can use it to write about your site, yourself, or anything else you'd like.</p>
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<footer id="colophon" class="site-footer">
|
|
||||||
<div class="site-info">
|
|
||||||
Proudly powered by <a href="https://wordpress.org/">WordPress</a>
|
|
||||||
</div>
|
|
||||||
</footer>
|
|
||||||
</div>
|
|
||||||
<script type='text/javascript' src='/wp-includes/js/wp-embed.min.js'></script>
|
|
||||||
</body>
|
|
||||||
</html>"""
|
|
||||||
|
|
||||||
|
|
||||||
def phpmyadmin() -> str:
|
def phpmyadmin() -> str:
|
||||||
"""Generate fake phpMyAdmin page"""
|
"""Generate fake phpMyAdmin page"""
|
||||||
return """<!DOCTYPE html>
|
return load_template("phpmyadmin")
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>phpMyAdmin</title>
|
def wp_login() -> str:
|
||||||
<style>
|
"""Generate fake WordPress login page"""
|
||||||
body { font-family: 'Segoe UI', Tahoma, sans-serif; margin: 0; background: #f0f0f0; }
|
return load_template("wp_login")
|
||||||
.header { background: #2979ff; color: white; padding: 10px 20px; }
|
|
||||||
.login { background: white; width: 400px; margin: 100px auto; padding: 30px; border-radius: 4px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
|
|
||||||
input { width: 100%; padding: 8px; margin: 8px 0; border: 1px solid #ddd; }
|
|
||||||
button { padding: 10px 20px; background: #2979ff; color: white; border: none; cursor: pointer; }
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="header"><h1>phpMyAdmin</h1></div>
|
|
||||||
<div class="login">
|
|
||||||
<h2>MySQL Server Login</h2>
|
|
||||||
<form action="/phpMyAdmin/index.php" method="post">
|
|
||||||
<input type="text" name="pma_username" placeholder="Username">
|
|
||||||
<input type="password" name="pma_password" placeholder="Password">
|
|
||||||
<button type="submit">Go</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>"""
|
|
||||||
|
|
||||||
|
|
||||||
def robots_txt() -> str:
|
def robots_txt() -> str:
|
||||||
"""Generate juicy robots.txt"""
|
"""Generate juicy robots.txt"""
|
||||||
return """User-agent: *
|
return load_template("robots.txt")
|
||||||
Disallow: /admin/
|
|
||||||
Disallow: /api/
|
|
||||||
Disallow: /backup/
|
|
||||||
Disallow: /config/
|
|
||||||
Disallow: /database/
|
|
||||||
Disallow: /private/
|
|
||||||
Disallow: /uploads/
|
|
||||||
Disallow: /wp-admin/
|
|
||||||
Disallow: /phpMyAdmin/
|
|
||||||
Disallow: /admin/login.php
|
|
||||||
Disallow: /api/v1/users
|
|
||||||
Disallow: /api/v2/secrets
|
|
||||||
Disallow: /.env
|
|
||||||
Disallow: /credentials.txt
|
|
||||||
Disallow: /passwords.txt
|
|
||||||
Disallow: /.git/
|
|
||||||
Disallow: /backup.sql
|
|
||||||
Disallow: /db_backup.sql
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
def directory_listing(path: str, dirs: list, files: list) -> str:
|
def directory_listing(path: str, dirs: list, files: list) -> str:
|
||||||
"""Generate fake directory listing"""
|
"""Generate fake directory listing"""
|
||||||
html = f"""<!DOCTYPE html>
|
row_template = load_template("directory_row")
|
||||||
<html>
|
|
||||||
<head><title>Index of {path}</title>
|
rows = ""
|
||||||
<style>
|
|
||||||
body {{ font-family: monospace; background: #fff; padding: 20px; }}
|
|
||||||
h1 {{ border-bottom: 1px solid #ccc; padding-bottom: 10px; }}
|
|
||||||
table {{ width: 100%; border-collapse: collapse; }}
|
|
||||||
th {{ text-align: left; padding: 10px; background: #f0f0f0; }}
|
|
||||||
td {{ padding: 8px; border-bottom: 1px solid #eee; }}
|
|
||||||
a {{ color: #0066cc; text-decoration: none; }}
|
|
||||||
a:hover {{ text-decoration: underline; }}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>Index of {path}</h1>
|
|
||||||
<table>
|
|
||||||
<tr><th>Name</th><th>Last Modified</th><th>Size</th></tr>
|
|
||||||
<tr><td><a href="../">Parent Directory</a></td><td>-</td><td>-</td></tr>
|
|
||||||
"""
|
|
||||||
|
|
||||||
for d in dirs:
|
for d in dirs:
|
||||||
html += f'<tr><td><a href="{d}">{d}</a></td><td>2024-12-01 10:30</td><td>-</td></tr>\n'
|
rows += row_template.format(href=d, name=d, date="2024-12-01 10:30", size="-")
|
||||||
|
|
||||||
for f, size in files:
|
for f, size in files:
|
||||||
html += f'<tr><td><a href="{f}">{f}</a></td><td>2024-12-01 14:22</td><td>{size}</td></tr>\n'
|
rows += row_template.format(href=f, name=f, date="2024-12-01 14:22", size=size)
|
||||||
|
|
||||||
html += '</table></body></html>'
|
return load_template("directory_listing", path=path, rows=rows)
|
||||||
return html
|
|
||||||
|
|||||||
69
src/templates/template_loader.py
Normal file
69
src/templates/template_loader.py
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
"""
|
||||||
|
Template loader for HTML templates.
|
||||||
|
Loads templates from the html/ subdirectory and supports string formatting for dynamic content.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Dict
|
||||||
|
|
||||||
|
|
||||||
|
class TemplateNotFoundError(Exception):
|
||||||
|
"""Raised when a template file cannot be found."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# Module-level cache for loaded templates
|
||||||
|
_template_cache: Dict[str, str] = {}
|
||||||
|
|
||||||
|
# Base directory for template files
|
||||||
|
_TEMPLATE_DIR = Path(__file__).parent / "html"
|
||||||
|
|
||||||
|
|
||||||
|
def load_template(name: str, **kwargs) -> str:
|
||||||
|
"""
|
||||||
|
Load a template by name and optionally substitute placeholders.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name: Template name (without extension for HTML, with extension for others like .txt)
|
||||||
|
**kwargs: Key-value pairs for placeholder substitution using str.format()
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Rendered template string
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
TemplateNotFoundError: If template file doesn't exist
|
||||||
|
|
||||||
|
Example:
|
||||||
|
>>> load_template("login_form") # Loads html/login_form.html
|
||||||
|
>>> load_template("robots.txt") # Loads html/robots.txt
|
||||||
|
>>> load_template("directory_listing", path="/var/www", rows="<tr>...</tr>")
|
||||||
|
"""
|
||||||
|
# debug
|
||||||
|
# print(f"Loading Template: {name}")
|
||||||
|
|
||||||
|
# Check cache first
|
||||||
|
if name not in _template_cache:
|
||||||
|
# Determine file path based on whether name has an extension
|
||||||
|
if '.' in name:
|
||||||
|
file_path = _TEMPLATE_DIR / name
|
||||||
|
else:
|
||||||
|
file_path = _TEMPLATE_DIR / f"{name}.html"
|
||||||
|
|
||||||
|
if not file_path.exists():
|
||||||
|
raise TemplateNotFoundError(f"Template '{name}' not found at {file_path}")
|
||||||
|
|
||||||
|
_template_cache[name] = file_path.read_text(encoding='utf-8')
|
||||||
|
|
||||||
|
template = _template_cache[name]
|
||||||
|
|
||||||
|
# Apply substitutions if kwargs provided
|
||||||
|
if kwargs:
|
||||||
|
template = template.format(**kwargs)
|
||||||
|
return template
|
||||||
|
|
||||||
|
|
||||||
|
def clear_cache() -> None:
|
||||||
|
"""Clear the template cache. Useful for testing or development."""
|
||||||
|
_template_cache.clear()
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
from typing import Dict, List, Tuple
|
from typing import Dict, List, Tuple
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
import re
|
||||||
|
|
||||||
|
|
||||||
class AccessTracker:
|
class AccessTracker:
|
||||||
@@ -17,17 +18,35 @@ class AccessTracker:
|
|||||||
'scanner', 'nikto', 'sqlmap', 'nmap', 'masscan', 'nessus', 'acunetix',
|
'scanner', 'nikto', 'sqlmap', 'nmap', 'masscan', 'nessus', 'acunetix',
|
||||||
'burp', 'zap', 'w3af', 'metasploit', 'nuclei', 'gobuster', 'dirbuster'
|
'burp', 'zap', 'w3af', 'metasploit', 'nuclei', 'gobuster', 'dirbuster'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# common attack types such as xss, shell injection, probes
|
||||||
|
self.attack_types = {
|
||||||
|
'path_traversal': r'\.\.',
|
||||||
|
'sql_injection': r"('|--|;|\bOR\b|\bUNION\b|\bSELECT\b|\bDROP\b)",
|
||||||
|
'xss_attempt': r'(<script|javascript:|onerror=|onload=)',
|
||||||
|
'common_probes': r'(wp-admin|phpmyadmin|\.env|\.git|/admin|/config)',
|
||||||
|
'shell_injection': r'(\||;|`|\$\(|&&)',
|
||||||
|
}
|
||||||
|
|
||||||
# Track IPs that accessed honeypot paths from robots.txt
|
# Track IPs that accessed honeypot paths from robots.txt
|
||||||
self.honeypot_triggered: Dict[str, List[str]] = defaultdict(list)
|
self.honeypot_triggered: Dict[str, List[str]] = defaultdict(list)
|
||||||
|
|
||||||
def record_access(self, ip: str, path: str, user_agent: str = ''):
|
def record_access(self, ip: str, path: str, user_agent: str = '', body: str = ''):
|
||||||
"""Record an access attempt"""
|
"""Record an access attempt"""
|
||||||
self.ip_counts[ip] += 1
|
self.ip_counts[ip] += 1
|
||||||
self.path_counts[path] += 1
|
self.path_counts[path] += 1
|
||||||
if user_agent:
|
if user_agent:
|
||||||
self.user_agent_counts[user_agent] += 1
|
self.user_agent_counts[user_agent] += 1
|
||||||
|
|
||||||
is_suspicious = self.is_suspicious_user_agent(user_agent) or self.is_honeypot_path(path)
|
# path attack type detection
|
||||||
|
attack_findings = self.detect_attack_type(path)
|
||||||
|
|
||||||
|
# post / put data
|
||||||
|
if len(body) > 0:
|
||||||
|
attack_findings.extend(self.detect_attack_type(body))
|
||||||
|
|
||||||
|
is_suspicious = self.is_suspicious_user_agent(user_agent) or self.is_honeypot_path(path) or len(attack_findings) > 0
|
||||||
|
|
||||||
|
|
||||||
# Track if this IP accessed a honeypot path
|
# Track if this IP accessed a honeypot path
|
||||||
if self.is_honeypot_path(path):
|
if self.is_honeypot_path(path):
|
||||||
@@ -39,9 +58,20 @@ class AccessTracker:
|
|||||||
'user_agent': user_agent,
|
'user_agent': user_agent,
|
||||||
'suspicious': is_suspicious,
|
'suspicious': is_suspicious,
|
||||||
'honeypot_triggered': self.is_honeypot_path(path),
|
'honeypot_triggered': self.is_honeypot_path(path),
|
||||||
|
'attack_types':attack_findings,
|
||||||
'timestamp': datetime.now().isoformat()
|
'timestamp': datetime.now().isoformat()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
def detect_attack_type(self, data:str) -> list[str]:
|
||||||
|
"""
|
||||||
|
Returns a list of all attack types found in path data
|
||||||
|
"""
|
||||||
|
findings = []
|
||||||
|
for name, pattern in self.attack_types.items():
|
||||||
|
if re.search(pattern, data, re.IGNORECASE):
|
||||||
|
findings.append(name)
|
||||||
|
return findings
|
||||||
|
|
||||||
def is_honeypot_path(self, path: str) -> bool:
|
def is_honeypot_path(self, path: str) -> bool:
|
||||||
"""Check if path is one of the honeypot traps from robots.txt"""
|
"""Check if path is one of the honeypot traps from robots.txt"""
|
||||||
honeypot_paths = [
|
honeypot_paths = [
|
||||||
@@ -91,6 +121,11 @@ class AccessTracker:
|
|||||||
suspicious = [log for log in self.access_log if log.get('suspicious', False)]
|
suspicious = [log for log in self.access_log if log.get('suspicious', False)]
|
||||||
return suspicious[-limit:]
|
return suspicious[-limit:]
|
||||||
|
|
||||||
|
def get_attack_type_accesses(self, limit: int = 20) -> List[Dict]:
|
||||||
|
"""Get recent accesses with detected attack types"""
|
||||||
|
attacks = [log for log in self.access_log if log.get('attack_types')]
|
||||||
|
return attacks[-limit:]
|
||||||
|
|
||||||
def get_honeypot_triggered_ips(self) -> List[Tuple[str, List[str]]]:
|
def get_honeypot_triggered_ips(self) -> List[Tuple[str, List[str]]]:
|
||||||
"""Get IPs that accessed honeypot paths"""
|
"""Get IPs that accessed honeypot paths"""
|
||||||
return [(ip, paths) for ip, paths in self.honeypot_triggered.items()]
|
return [(ip, paths) for ip, paths in self.honeypot_triggered.items()]
|
||||||
@@ -110,5 +145,6 @@ class AccessTracker:
|
|||||||
'top_paths': self.get_top_paths(10),
|
'top_paths': self.get_top_paths(10),
|
||||||
'top_user_agents': self.get_top_user_agents(10),
|
'top_user_agents': self.get_top_user_agents(10),
|
||||||
'recent_suspicious': self.get_suspicious_accesses(20),
|
'recent_suspicious': self.get_suspicious_accesses(20),
|
||||||
'honeypot_triggered_ips': self.get_honeypot_triggered_ips()
|
'honeypot_triggered_ips': self.get_honeypot_triggered_ips(),
|
||||||
|
'attack_types': self.get_attack_type_accesses(20)
|
||||||
}
|
}
|
||||||
|
|||||||
20
tests/sim_attacks.sh
Executable file
20
tests/sim_attacks.sh
Executable file
@@ -0,0 +1,20 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
TARGET="http://localhost:5000"
|
||||||
|
|
||||||
|
echo "=== Testing Path Traversal ==="
|
||||||
|
curl -s "$TARGET/../../etc/passwd"
|
||||||
|
|
||||||
|
echo -e "\n=== Testing SQL Injection ==="
|
||||||
|
curl -s -X POST "$TARGET/login" -d "user=' OR 1=1--"
|
||||||
|
|
||||||
|
echo -e "\n=== Testing XSS ==="
|
||||||
|
curl -s -X POST "$TARGET/comment" -d "msg=<script>alert(1)</script>"
|
||||||
|
|
||||||
|
echo -e "\n=== Testing Common Probes ==="
|
||||||
|
curl -s "$TARGET/.env"
|
||||||
|
curl -s "$TARGET/wp-admin/"
|
||||||
|
|
||||||
|
echo -e "\n=== Testing Shell Injection ==="
|
||||||
|
curl -s -X POST "$TARGET/ping" -d "host=127.0.0.1; cat /etc/passwd"
|
||||||
|
|
||||||
|
echo -e "\n=== Done ==="
|
||||||
Reference in New Issue
Block a user