Merge pull request #2 from BlessedRebuS/ptarrant/main

Ptarrant/main
This commit is contained in:
Patrick Di Fazio
2025-12-25 21:31:49 +01:00
committed by GitHub
16 changed files with 1073 additions and 493 deletions

View File

@@ -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>&#128375;&#65039; 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>&#9888;&#65039; 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>
"""

View File

@@ -197,26 +197,37 @@ 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)
try:
self.send_response(200) self.send_response(200)
self.send_header('Content-type', 'text/html') self.send_header('Content-type', 'text/html')
self.end_headers() self.end_headers()
self.wfile.write(html_templates.login_error().encode()) 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."""
try:
if path == '/robots.txt': if path == '/robots.txt':
self.send_response(200) self.send_response(200)
self.send_header('Content-type', 'text/plain') self.send_header('Content-type', 'text/plain')
@@ -246,18 +257,19 @@ class Handler(BaseHTTPRequestHandler):
self.wfile.write(api_response('/api/config').encode()) self.wfile.write(api_response('/api/config').encode())
return True return True
if path in ['/admin', '/admin/', '/admin/login', '/login', '/wp-login.php']: if path in ['/admin', '/admin/', '/admin/login', '/login']:
self.send_response(200) self.send_response(200)
self.send_header('Content-type', 'text/html') self.send_header('Content-type', 'text/html')
self.end_headers() self.end_headers()
self.wfile.write(html_templates.login_form().encode()) self.wfile.write(html_templates.login_form().encode())
return True return True
if path == '/wp-admin' or path == '/wp-admin/': # WordPress login page
if path in ['/wp-login.php', '/wp-login', '/wp-admin', '/wp-admin/']:
self.send_response(200) self.send_response(200)
self.send_header('Content-type', 'text/html') self.send_header('Content-type', 'text/html')
self.end_headers() self.end_headers()
self.wfile.write(html_templates.login_form().encode()) self.wfile.write(html_templates.wp_login().encode())
return True return True
if path in ['/wp-content/', '/wp-includes/'] or 'wordpress' in path.lower(): if path in ['/wp-content/', '/wp-includes/'] or 'wordpress' in path.lower():
@@ -287,6 +299,12 @@ class Handler(BaseHTTPRequestHandler):
self.end_headers() self.end_headers()
self.wfile.write(directory_listing(path).encode()) self.wfile.write(directory_listing(path).encode())
return True 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
View 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',
]

View File

@@ -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>&#128520; 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>

View 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>

View File

@@ -0,0 +1 @@
<tr><td><a href="{href}">{name}</a></td><td>{date}</td><td>{size}</td></tr>

View 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>

View 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>

View 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>

View 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

View 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 &#8211; 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>

View 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 &lsaquo; WordPress &mdash; 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="/">&larr; 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>

View File

@@ -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 &#8211; 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>
<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 = ""
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

View 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()

View File

@@ -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
View 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 ==="