@@ -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)"""
|
||||
client_ip = self._get_client_ip()
|
||||
user_agent = self._get_user_agent()
|
||||
|
||||
self.tracker.record_access(client_ip, self.path, user_agent)
|
||||
|
||||
post_data = ""
|
||||
|
||||
print(f"[LOGIN ATTEMPT] {client_ip} - {self.path} - {user_agent[:50]}")
|
||||
|
||||
content_length = int(self.headers.get('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]}")
|
||||
|
||||
# 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)
|
||||
|
||||
self.send_response(200)
|
||||
self.send_header('Content-type', 'text/html')
|
||||
self.end_headers()
|
||||
self.wfile.write(html_templates.login_error().encode())
|
||||
try:
|
||||
self.send_response(200)
|
||||
self.send_header('Content-type', 'text/html')
|
||||
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:
|
||||
"""Serve special paths like robots.txt, API endpoints, etc."""
|
||||
|
||||
if path == '/robots.txt':
|
||||
self.send_response(200)
|
||||
self.send_header('Content-type', 'text/plain')
|
||||
self.end_headers()
|
||||
self.wfile.write(html_templates.robots_txt().encode())
|
||||
return True
|
||||
|
||||
if path in ['/credentials.txt', '/passwords.txt', '/admin_notes.txt']:
|
||||
self.send_response(200)
|
||||
self.send_header('Content-type', 'text/plain')
|
||||
self.end_headers()
|
||||
if 'credentials' in path:
|
||||
self.wfile.write(credentials_txt().encode())
|
||||
else:
|
||||
self.wfile.write(passwords_txt().encode())
|
||||
return True
|
||||
|
||||
if path in ['/users.json', '/api_keys.json', '/config.json']:
|
||||
self.send_response(200)
|
||||
self.send_header('Content-type', 'application/json')
|
||||
self.end_headers()
|
||||
if 'users' in path:
|
||||
self.wfile.write(users_json().encode())
|
||||
elif 'api_keys' in path:
|
||||
self.wfile.write(api_keys_json().encode())
|
||||
else:
|
||||
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)
|
||||
self.send_header('Content-type', 'text/html')
|
||||
self.end_headers()
|
||||
self.wfile.write(html_templates.login_form().encode())
|
||||
return True
|
||||
|
||||
if path == '/wp-admin' or path == '/wp-admin/':
|
||||
self.send_response(200)
|
||||
self.send_header('Content-type', 'text/html')
|
||||
self.end_headers()
|
||||
self.wfile.write(html_templates.login_form().encode())
|
||||
return True
|
||||
|
||||
if path in ['/wp-content/', '/wp-includes/'] or 'wordpress' in path.lower():
|
||||
self.send_response(200)
|
||||
self.send_header('Content-type', 'text/html')
|
||||
self.end_headers()
|
||||
self.wfile.write(html_templates.wordpress().encode())
|
||||
return True
|
||||
|
||||
if 'phpmyadmin' in path.lower() or path in ['/pma/', '/phpMyAdmin/']:
|
||||
self.send_response(200)
|
||||
self.send_header('Content-type', 'text/html')
|
||||
self.end_headers()
|
||||
self.wfile.write(html_templates.phpmyadmin().encode())
|
||||
return True
|
||||
|
||||
if path.startswith('/api/') or path.startswith('/api') or path in ['/.env']:
|
||||
self.send_response(200)
|
||||
self.send_header('Content-type', 'application/json')
|
||||
self.end_headers()
|
||||
self.wfile.write(api_response(path).encode())
|
||||
return True
|
||||
|
||||
if path in ['/backup/', '/uploads/', '/private/', '/admin/', '/config/', '/database/']:
|
||||
self.send_response(200)
|
||||
self.send_header('Content-type', 'text/html')
|
||||
self.end_headers()
|
||||
self.wfile.write(directory_listing(path).encode())
|
||||
return True
|
||||
try:
|
||||
if path == '/robots.txt':
|
||||
self.send_response(200)
|
||||
self.send_header('Content-type', 'text/plain')
|
||||
self.end_headers()
|
||||
self.wfile.write(html_templates.robots_txt().encode())
|
||||
return True
|
||||
|
||||
if path in ['/credentials.txt', '/passwords.txt', '/admin_notes.txt']:
|
||||
self.send_response(200)
|
||||
self.send_header('Content-type', 'text/plain')
|
||||
self.end_headers()
|
||||
if 'credentials' in path:
|
||||
self.wfile.write(credentials_txt().encode())
|
||||
else:
|
||||
self.wfile.write(passwords_txt().encode())
|
||||
return True
|
||||
|
||||
if path in ['/users.json', '/api_keys.json', '/config.json']:
|
||||
self.send_response(200)
|
||||
self.send_header('Content-type', 'application/json')
|
||||
self.end_headers()
|
||||
if 'users' in path:
|
||||
self.wfile.write(users_json().encode())
|
||||
elif 'api_keys' in path:
|
||||
self.wfile.write(api_keys_json().encode())
|
||||
else:
|
||||
self.wfile.write(api_response('/api/config').encode())
|
||||
return True
|
||||
|
||||
if path in ['/admin', '/admin/', '/admin/login', '/login']:
|
||||
self.send_response(200)
|
||||
self.send_header('Content-type', 'text/html')
|
||||
self.end_headers()
|
||||
self.wfile.write(html_templates.login_form().encode())
|
||||
return True
|
||||
|
||||
# WordPress login page
|
||||
if path in ['/wp-login.php', '/wp-login', '/wp-admin', '/wp-admin/']:
|
||||
self.send_response(200)
|
||||
self.send_header('Content-type', 'text/html')
|
||||
self.end_headers()
|
||||
self.wfile.write(html_templates.wp_login().encode())
|
||||
return True
|
||||
|
||||
if path in ['/wp-content/', '/wp-includes/'] or 'wordpress' in path.lower():
|
||||
self.send_response(200)
|
||||
self.send_header('Content-type', 'text/html')
|
||||
self.end_headers()
|
||||
self.wfile.write(html_templates.wordpress().encode())
|
||||
return True
|
||||
|
||||
if 'phpmyadmin' in path.lower() or path in ['/pma/', '/phpMyAdmin/']:
|
||||
self.send_response(200)
|
||||
self.send_header('Content-type', 'text/html')
|
||||
self.end_headers()
|
||||
self.wfile.write(html_templates.phpmyadmin().encode())
|
||||
return True
|
||||
|
||||
if path.startswith('/api/') or path.startswith('/api') or path in ['/.env']:
|
||||
self.send_response(200)
|
||||
self.send_header('Content-type', 'application/json')
|
||||
self.end_headers()
|
||||
self.wfile.write(api_response(path).encode())
|
||||
return True
|
||||
|
||||
if path in ['/backup/', '/uploads/', '/private/', '/admin/', '/config/', '/database/']:
|
||||
self.send_response(200)
|
||||
self.send_header('Content-type', 'text/html')
|
||||
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
|
||||
|
||||
@@ -302,6 +320,8 @@ class Handler(BaseHTTPRequestHandler):
|
||||
try:
|
||||
stats = self.tracker.get_stats()
|
||||
self.wfile.write(generate_dashboard(stats).encode())
|
||||
except BrokenPipeError:
|
||||
pass
|
||||
except Exception as e:
|
||||
print(f"Error generating dashboard: {e}")
|
||||
return
|
||||
@@ -333,6 +353,9 @@ class Handler(BaseHTTPRequestHandler):
|
||||
|
||||
if Handler.counter < 0:
|
||||
Handler.counter = self.config.canary_token_tries
|
||||
except BrokenPipeError:
|
||||
# Client disconnected, ignore silently
|
||||
pass
|
||||
except Exception as 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', [])
|
||||
]) 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>
|
||||
<html>
|
||||
<head>
|
||||
@@ -188,6 +194,24 @@ def generate_dashboard(stats: dict) -> str:
|
||||
</table>
|
||||
</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">
|
||||
<h2>Top IP Addresses</h2>
|
||||
<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.
|
||||
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:
|
||||
"""Generate fake login page"""
|
||||
return """<!DOCTYPE html>
|
||||
<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>"""
|
||||
return load_template("login_form")
|
||||
|
||||
|
||||
def login_error() -> str:
|
||||
"""Generate fake login error page"""
|
||||
return """<!DOCTYPE html>
|
||||
<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>"""
|
||||
return load_template("login_error")
|
||||
|
||||
|
||||
def wordpress() -> str:
|
||||
"""Generate fake WordPress page"""
|
||||
return """<!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>"""
|
||||
return load_template("wordpress")
|
||||
|
||||
|
||||
def phpmyadmin() -> str:
|
||||
"""Generate fake phpMyAdmin page"""
|
||||
return """<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>phpMyAdmin</title>
|
||||
<style>
|
||||
body { font-family: 'Segoe UI', Tahoma, sans-serif; margin: 0; background: #f0f0f0; }
|
||||
.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>"""
|
||||
return load_template("phpmyadmin")
|
||||
|
||||
|
||||
def wp_login() -> str:
|
||||
"""Generate fake WordPress login page"""
|
||||
return load_template("wp_login")
|
||||
|
||||
|
||||
def robots_txt() -> str:
|
||||
"""Generate juicy robots.txt"""
|
||||
return """User-agent: *
|
||||
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
|
||||
"""
|
||||
return load_template("robots.txt")
|
||||
|
||||
|
||||
def directory_listing(path: str, dirs: list, files: list) -> str:
|
||||
"""Generate fake directory listing"""
|
||||
html = f"""<!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>
|
||||
"""
|
||||
|
||||
row_template = load_template("directory_row")
|
||||
|
||||
rows = ""
|
||||
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:
|
||||
html += f'<tr><td><a href="{f}">{f}</a></td><td>2024-12-01 14:22</td><td>{size}</td></tr>\n'
|
||||
|
||||
html += '</table></body></html>'
|
||||
return html
|
||||
rows += row_template.format(href=f, name=f, date="2024-12-01 14:22", size=size)
|
||||
|
||||
return load_template("directory_listing", path=path, rows=rows)
|
||||
|
||||
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 collections import defaultdict
|
||||
from datetime import datetime
|
||||
import re
|
||||
|
||||
|
||||
class AccessTracker:
|
||||
@@ -17,17 +18,35 @@ class AccessTracker:
|
||||
'scanner', 'nikto', 'sqlmap', 'nmap', 'masscan', 'nessus', 'acunetix',
|
||||
'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
|
||||
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"""
|
||||
self.ip_counts[ip] += 1
|
||||
self.path_counts[path] += 1
|
||||
if user_agent:
|
||||
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
|
||||
if self.is_honeypot_path(path):
|
||||
@@ -39,9 +58,20 @@ class AccessTracker:
|
||||
'user_agent': user_agent,
|
||||
'suspicious': is_suspicious,
|
||||
'honeypot_triggered': self.is_honeypot_path(path),
|
||||
'attack_types':attack_findings,
|
||||
'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:
|
||||
"""Check if path is one of the honeypot traps from robots.txt"""
|
||||
honeypot_paths = [
|
||||
@@ -91,6 +121,11 @@ class AccessTracker:
|
||||
suspicious = [log for log in self.access_log if log.get('suspicious', False)]
|
||||
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]]]:
|
||||
"""Get IPs that accessed honeypot paths"""
|
||||
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_user_agents': self.get_top_user_agents(10),
|
||||
'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