Merge pull request #26 from BlessedRebuS/feat/add-deception-features

Feat/add deception features
This commit is contained in:
Phillip Tarrant
2026-01-03 14:01:02 -06:00
committed by GitHub
14 changed files with 841 additions and 15 deletions

7
.gitignore vendored
View File

@@ -61,6 +61,13 @@ secrets/
*.log
logs/
# Data and databases
data/
**/data/
*.db
*.sqlite
*.sqlite3
# Temporary files
*.tmp
*.temp

View File

@@ -6,6 +6,7 @@ import time
from datetime import datetime
from http.server import BaseHTTPRequestHandler
from typing import Optional, List
from urllib.parse import urlparse, parse_qs
from config import Config
from tracker import AccessTracker
@@ -16,6 +17,9 @@ from generators import (
api_response, directory_listing
)
from wordlists import get_wordlists
from sql_errors import generate_sql_error_response, get_sql_response_with_data
from xss_detector import detect_xss_pattern, generate_xss_response
from server_errors import generate_server_error
class Handler(BaseHTTPRequestHandler):
@@ -67,6 +71,67 @@ class Handler(BaseHTTPRequestHandler):
if not error_codes:
error_codes = [400, 401, 403, 404, 500, 502, 503]
return random.choice(error_codes)
def _parse_query_string(self) -> str:
"""Extract query string from the request path"""
parsed = urlparse(self.path)
return parsed.query
def _handle_sql_endpoint(self, path: str) -> bool:
"""
Handle SQL injection honeypot endpoints.
Returns True if the path was handled, False otherwise.
"""
# SQL-vulnerable endpoints
sql_endpoints = ['/api/search', '/api/sql', '/api/database']
base_path = urlparse(path).path
if base_path not in sql_endpoints:
return False
try:
# Get query parameters
query_string = self._parse_query_string()
# Log SQL injection attempt
client_ip = self._get_client_ip()
user_agent = self._get_user_agent()
# Always check for SQL injection patterns
error_msg, content_type, status_code = generate_sql_error_response(query_string or "")
if error_msg:
# SQL injection detected - log and return error
self.access_logger.warning(f"[SQL INJECTION DETECTED] {client_ip} - {base_path} - Query: {query_string[:100] if query_string else 'empty'}")
self.send_response(status_code)
self.send_header('Content-type', content_type)
self.end_headers()
self.wfile.write(error_msg.encode())
else:
# No injection detected - return fake data
self.access_logger.info(f"[SQL ENDPOINT] {client_ip} - {base_path} - Query: {query_string[:100] if query_string else 'empty'}")
self.send_response(200)
self.send_header('Content-type', 'application/json')
self.end_headers()
response_data = get_sql_response_with_data(base_path, query_string or "")
self.wfile.write(response_data.encode())
return True
except BrokenPipeError:
# Client disconnected
return True
except Exception as e:
self.app_logger.error(f"Error handling SQL endpoint {path}: {str(e)}")
# Still send a response even on error
try:
self.send_response(500)
self.send_header('Content-type', 'application/json')
self.end_headers()
self.wfile.write(b'{"error": "Internal server error"}')
except:
pass
return True
def generate_page(self, seed: str) -> str:
"""Generate a webpage containing random links or canary token"""
@@ -207,6 +272,68 @@ class Handler(BaseHTTPRequestHandler):
user_agent = self._get_user_agent()
post_data = ""
from urllib.parse import urlparse
base_path = urlparse(self.path).path
if base_path in ['/api/search', '/api/sql', '/api/database']:
content_length = int(self.headers.get('Content-Length', 0))
if content_length > 0:
post_data = self.rfile.read(content_length).decode('utf-8', errors="replace")
self.access_logger.info(f"[SQL ENDPOINT POST] {client_ip} - {base_path} - Data: {post_data[:100] if post_data else 'empty'}")
error_msg, content_type, status_code = generate_sql_error_response(post_data)
try:
if error_msg:
self.access_logger.warning(f"[SQL INJECTION DETECTED POST] {client_ip} - {base_path}")
self.send_response(status_code)
self.send_header('Content-type', content_type)
self.end_headers()
self.wfile.write(error_msg.encode())
else:
self.send_response(200)
self.send_header('Content-type', 'application/json')
self.end_headers()
response_data = get_sql_response_with_data(base_path, post_data)
self.wfile.write(response_data.encode())
except BrokenPipeError:
pass
except Exception as e:
self.app_logger.error(f"Error in SQL POST handler: {str(e)}")
return
if base_path == '/api/contact':
content_length = int(self.headers.get('Content-Length', 0))
if content_length > 0:
post_data = self.rfile.read(content_length).decode('utf-8', errors="replace")
parsed_data = {}
for pair in post_data.split('&'):
if '=' in pair:
key, value = pair.split('=', 1)
from urllib.parse import unquote_plus
parsed_data[unquote_plus(key)] = unquote_plus(value)
xss_detected = any(detect_xss_pattern(v) for v in parsed_data.values())
if xss_detected:
self.access_logger.warning(f"[XSS ATTEMPT DETECTED] {client_ip} - {base_path} - Data: {post_data[:200]}")
else:
self.access_logger.info(f"[XSS ENDPOINT POST] {client_ip} - {base_path}")
try:
self.send_response(200)
self.send_header('Content-type', 'text/html')
self.end_headers()
response_html = generate_xss_response(parsed_data)
self.wfile.write(response_html.encode())
except BrokenPipeError:
pass
except Exception as e:
self.app_logger.error(f"Error in XSS POST handler: {str(e)}")
return
self.access_logger.warning(f"[LOGIN ATTEMPT] {client_ip} - {self.path} - {user_agent[:50]}")
content_length = int(self.headers.get('Content-Length', 0))
@@ -215,20 +342,16 @@ class Handler(BaseHTTPRequestHandler):
self.access_logger.warning(f"[POST DATA] {post_data[:200]}")
# Parse and log credentials
username, password = self.tracker.parse_credentials(post_data)
if username or password:
# Log to dedicated credentials.log file
timestamp = datetime.now().strftime("%Y-%m-%dT%H:%M:%SZ")
credential_line = f"{timestamp}|{client_ip}|{username or 'N/A'}|{password or 'N/A'}|{self.path}"
self.credential_logger.info(credential_line)
# Also record in tracker for dashboard
self.tracker.record_credential_attempt(client_ip, self.path, username or 'N/A', password or 'N/A')
self.access_logger.warning(f"[CREDENTIALS CAPTURED] {client_ip} - Username: {username or 'N/A'} - Path: {self.path}")
# 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)
@@ -248,6 +371,10 @@ class Handler(BaseHTTPRequestHandler):
def serve_special_path(self, path: str) -> bool:
"""Serve special paths like robots.txt, API endpoints, etc."""
# Check SQL injection honeypot endpoints first
if self._handle_sql_endpoint(path):
return True
try:
if path == '/robots.txt':
self.send_response(200)
@@ -285,7 +412,28 @@ class Handler(BaseHTTPRequestHandler):
self.wfile.write(html_templates.login_form().encode())
return True
# WordPress login page
if path in ['/users', '/user', '/database', '/db', '/search']:
self.send_response(200)
self.send_header('Content-type', 'text/html')
self.end_headers()
self.wfile.write(html_templates.product_search().encode())
return True
if path in ['/info', '/input', '/contact', '/feedback', '/comment']:
self.send_response(200)
self.send_header('Content-type', 'text/html')
self.end_headers()
self.wfile.write(html_templates.input_form().encode())
return True
if path == '/server':
error_html, content_type = generate_server_error()
self.send_response(500)
self.send_header('Content-type', content_type)
self.end_headers()
self.wfile.write(error_html.encode())
return True
if path in ['/wp-login.php', '/wp-login', '/wp-admin', '/wp-admin/']:
self.send_response(200)
self.send_header('Content-type', 'text/html')

65
src/server_errors.py Normal file
View File

@@ -0,0 +1,65 @@
#!/usr/bin/env python3
import random
from wordlists import get_wordlists
def generate_server_error() -> tuple[str, str]:
wl = get_wordlists()
server_errors = wl.server_errors
if not server_errors:
return ("500 Internal Server Error", "text/html")
server_type = random.choice(list(server_errors.keys()))
server_config = server_errors[server_type]
error_codes = {
400: "Bad Request",
401: "Unauthorized",
403: "Forbidden",
404: "Not Found",
500: "Internal Server Error",
502: "Bad Gateway",
503: "Service Unavailable"
}
code = random.choice(list(error_codes.keys()))
message = error_codes[code]
template = server_config.get('template', '')
version = random.choice(server_config.get('versions', ['1.0']))
html = template.replace('{code}', str(code))
html = html.replace('{message}', message)
html = html.replace('{version}', version)
if server_type == 'apache':
os = random.choice(server_config.get('os', ['Ubuntu']))
html = html.replace('{os}', os)
html = html.replace('{host}', 'localhost')
return (html, "text/html")
def get_server_header(server_type: str = None) -> str:
wl = get_wordlists()
server_errors = wl.server_errors
if not server_errors:
return "nginx/1.18.0"
if not server_type:
server_type = random.choice(list(server_errors.keys()))
server_config = server_errors.get(server_type, {})
version = random.choice(server_config.get('versions', ['1.0']))
server_headers = {
'nginx': f"nginx/{version}",
'apache': f"Apache/{version}",
'iis': f"Microsoft-IIS/{version}",
'tomcat': f"Apache-Coyote/1.1"
}
return server_headers.get(server_type, "nginx/1.18.0")

112
src/sql_errors.py Normal file
View File

@@ -0,0 +1,112 @@
#!/usr/bin/env python3
import random
import re
from typing import Optional, Tuple
from wordlists import get_wordlists
def detect_sql_injection_pattern(query_string: str) -> Optional[str]:
if not query_string:
return None
query_lower = query_string.lower()
patterns = {
'quote': [r"'", r'"', r'`'],
'comment': [r'--', r'#', r'/\*', r'\*/'],
'union': [r'\bunion\b', r'\bunion\s+select\b'],
'boolean': [r'\bor\b.*=.*', r'\band\b.*=.*', r"'.*or.*'.*=.*'"],
'time_based': [r'\bsleep\b', r'\bwaitfor\b', r'\bdelay\b', r'\bbenchmark\b'],
'stacked': [r';.*select', r';.*drop', r';.*insert', r';.*update', r';.*delete'],
'command': [r'\bexec\b', r'\bexecute\b', r'\bxp_cmdshell\b'],
'info_schema': [r'information_schema', r'table_schema', r'table_name'],
}
for injection_type, pattern_list in patterns.items():
for pattern in pattern_list:
if re.search(pattern, query_lower):
return injection_type
return None
def get_random_sql_error(db_type: str = None, injection_type: str = None) -> Tuple[str, str]:
wl = get_wordlists()
sql_errors = wl.sql_errors
if not sql_errors:
return ("Database error occurred", "text/plain")
if not db_type:
db_type = random.choice(list(sql_errors.keys()))
db_errors = sql_errors.get(db_type, {})
if injection_type and injection_type in db_errors:
errors = db_errors[injection_type]
elif 'generic' in db_errors:
errors = db_errors['generic']
else:
all_errors = []
for error_list in db_errors.values():
if isinstance(error_list, list):
all_errors.extend(error_list)
errors = all_errors if all_errors else ["Database error occurred"]
error_message = random.choice(errors) if errors else "Database error occurred"
if '{table}' in error_message:
tables = ['users', 'products', 'orders', 'customers', 'accounts', 'sessions']
error_message = error_message.replace('{table}', random.choice(tables))
if '{column}' in error_message:
columns = ['id', 'name', 'email', 'password', 'username', 'created_at']
error_message = error_message.replace('{column}', random.choice(columns))
return (error_message, "text/plain")
def generate_sql_error_response(query_string: str, db_type: str = None) -> Tuple[str, str, int]:
injection_type = detect_sql_injection_pattern(query_string)
if not injection_type:
return (None, None, None)
error_message, content_type = get_random_sql_error(db_type, injection_type)
status_code = 500
if random.random() < 0.3:
status_code = 200
return (error_message, content_type, status_code)
def get_sql_response_with_data(path: str, params: str) -> str:
import json
from generators import random_username, random_email, random_password
injection_type = detect_sql_injection_pattern(params)
if injection_type in ['union', 'boolean', 'stacked']:
data = {
"success": True,
"results": [
{
"id": i,
"username": random_username(),
"email": random_email(),
"password_hash": random_password(),
"role": random.choice(["admin", "user", "moderator"])
}
for i in range(1, random.randint(2, 5))
]
}
return json.dumps(data, indent=2)
return json.dumps({
"success": True,
"message": "Query executed successfully",
"results": []
}, indent=2)

View File

@@ -0,0 +1,66 @@
<!DOCTYPE html>
<html>
<head>
<title>Search</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 600px;
margin: 50px auto;
padding: 20px;
}
h1 {
color: #333;
}
input {
width: 100%;
padding: 8px;
margin: 10px 0;
box-sizing: border-box;
}
button {
background: #4CAF50;
color: white;
padding: 10px 20px;
border: none;
cursor: pointer;
}
button:hover {
background: #45a049;
}
#results {
margin-top: 20px;
padding: 10px;
border: 1px solid #ddd;
background: #f9f9f9;
display: none;
}
</style>
</head>
<body>
<h1>Search</h1>
<form id="searchForm">
<input type="text" id="searchQuery" placeholder="Enter search query..." required>
<button type="submit">Search</button>
</form>
<div id="results"></div>
<script>
document.getElementById('searchForm').addEventListener('submit', async (e) => {
e.preventDefault();
const query = document.getElementById('searchQuery').value;
const results = document.getElementById('results');
try {
const response = await fetch(`/api/search?q=${encodeURIComponent(query)}`);
const text = await response.text();
results.innerHTML = `<pre>${text}</pre>`;
results.style.display = 'block';
} catch (err) {
results.innerHTML = `<p>Error: ${err.message}</p>`;
results.style.display = 'block';
}
});
</script>
</body>
</html>

View File

@@ -0,0 +1,74 @@
<!DOCTYPE html>
<html>
<head>
<title>Contact</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 500px;
margin: 50px auto;
padding: 20px;
}
h1 {
color: #333;
}
input, textarea {
width: 100%;
padding: 8px;
margin: 10px 0;
border: 1px solid #ddd;
box-sizing: border-box;
}
textarea {
min-height: 100px;
}
button {
background: #4CAF50;
color: white;
padding: 10px 20px;
border: none;
cursor: pointer;
}
button:hover {
background: #45a049;
}
#response {
margin-top: 20px;
padding: 10px;
display: none;
}
</style>
</head>
<body>
<h1>Contact</h1>
<form id="contactForm">
<input type="text" name="name" placeholder="Name" required>
<input type="email" name="email" placeholder="Email" required>
<textarea name="message" placeholder="Message" required></textarea>
<button type="submit">Submit</button>
</form>
<div id="response"></div>
<script>
document.getElementById('contactForm').addEventListener('submit', function(e) {
e.preventDefault();
const formData = new FormData(this);
fetch('/api/contact', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams(formData)
})
.then(response => response.text())
.then(text => {
document.getElementById('response').innerHTML = text;
document.getElementById('response').style.display = 'block';
})
.catch(error => {
document.getElementById('response').innerHTML = 'Error: ' + error.message;
document.getElementById('response').style.display = 'block';
});
});
</script>
</body>
</html>

View File

@@ -11,8 +11,18 @@ Disallow: /login/
Disallow: /admin/login
Disallow: /phpMyAdmin/
Disallow: /admin/login.php
Disallow: /users
Disallow: /search
Disallow: /contact
Disallow: /info
Disallow: /input
Disallow: /feedback
Disallow: /server
Disallow: /api/v1/users
Disallow: /api/v2/secrets
Disallow: /api/search
Disallow: /api/sql
Disallow: /api/database
Disallow: /.env
Disallow: /credentials.txt
Disallow: /passwords.txt

View File

@@ -50,3 +50,13 @@ def directory_listing(path: str, dirs: list, files: list) -> str:
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)
def product_search() -> str:
"""Generate product search page with SQL injection honeypot"""
return load_template("generic_search")
def input_form() -> str:
"""Generate input form page for XSS honeypot"""
return load_template("input_form")

View File

@@ -5,6 +5,7 @@ from collections import defaultdict
from datetime import datetime
import re
import urllib.parse
from wordlists import get_wordlists
class AccessTracker:
@@ -21,14 +22,19 @@ class AccessTracker:
'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'(\||;|`|\$\(|&&)',
}
# Load attack patterns from wordlists
wl = get_wordlists()
self.attack_types = wl.attack_patterns
# Fallback if wordlists not loaded
if not self.attack_types:
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)

View File

@@ -111,6 +111,18 @@ class Wordlists:
@property
def error_codes(self):
return self._data.get("error_codes", [])
@property
def sql_errors(self):
return self._data.get("sql_errors", {})
@property
def attack_patterns(self):
return self._data.get("attack_patterns", {})
@property
def server_errors(self):
return self._data.get("server_errors", {})
_wordlists_instance = None

73
src/xss_detector.py Normal file
View File

@@ -0,0 +1,73 @@
#!/usr/bin/env python3
import re
from typing import Optional
from wordlists import get_wordlists
def detect_xss_pattern(input_string: str) -> bool:
if not input_string:
return False
wl = get_wordlists()
xss_pattern = wl.attack_patterns.get('xss_attempt', '')
if not xss_pattern:
xss_pattern = r'(<script|</script|javascript:|onerror=|onload=|onclick=|<iframe|<img|<svg|eval\(|alert\()'
return bool(re.search(xss_pattern, input_string, re.IGNORECASE))
def generate_xss_response(input_data: dict) -> str:
xss_detected = False
reflected_content = []
for key, value in input_data.items():
if detect_xss_pattern(value):
xss_detected = True
reflected_content.append(f"<p><strong>{key}:</strong> {value}</p>")
if xss_detected:
html = f"""
<!DOCTYPE html>
<html>
<head>
<title>Submission Received</title>
<style>
body {{ font-family: Arial, sans-serif; max-width: 600px; margin: 50px auto; padding: 20px; }}
.success {{ background: #d4edda; padding: 20px; border-radius: 8px; border: 1px solid #c3e6cb; }}
h2 {{ color: #155724; }}
p {{ margin: 10px 0; }}
</style>
</head>
<body>
<div class="success">
<h2>Thank you for your submission!</h2>
<p>We have received your information:</p>
{''.join(reflected_content)}
<p><em>We will get back to you shortly.</em></p>
</div>
</body>
</html>
"""
return html
return """
<!DOCTYPE html>
<html>
<head>
<title>Submission Received</title>
<style>
body { font-family: Arial, sans-serif; max-width: 600px; margin: 50px auto; padding: 20px; }
.success { background: #d4edda; padding: 20px; border-radius: 8px; border: 1px solid #c3e6cb; }
h2 { color: #155724; }
</style>
</head>
<body>
<div class="success">
<h2>Thank you for your submission!</h2>
<p>Your message has been received and we will respond soon.</p>
</div>
</body>
</html>
"""

View File

@@ -17,4 +17,4 @@ 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 ==="
echo -e "\n=== Done ==="

View File

@@ -0,0 +1,78 @@
#!/bin/bash
# Test script for SQL injection honeypot endpoints
BASE_URL="http://localhost:5000"
echo "========================================="
echo "Testing SQL Injection Honeypot Endpoints"
echo "========================================="
echo ""
# Test 1: Normal query
echo "Test 1: Normal GET request to /api/search"
curl -s "${BASE_URL}/api/search?q=test" | head -20
echo ""
echo "---"
echo ""
# Test 2: SQL injection with single quote
echo "Test 2: SQL injection with single quote"
curl -s "${BASE_URL}/api/search?id=1'" | head -20
echo ""
echo "---"
echo ""
# Test 3: UNION-based injection
echo "Test 3: UNION-based SQL injection"
curl -s "${BASE_URL}/api/search?id=1%20UNION%20SELECT%20*" | head -20
echo ""
echo "---"
echo ""
# Test 4: Boolean-based injection
echo "Test 4: Boolean-based SQL injection"
curl -s "${BASE_URL}/api/sql?user=admin'%20OR%201=1--" | head -20
echo ""
echo "---"
echo ""
# Test 5: Comment-based injection
echo "Test 5: Comment-based SQL injection"
curl -s "${BASE_URL}/api/database?q=test'--" | head -20
echo ""
echo "---"
echo ""
# Test 6: Time-based injection
echo "Test 6: Time-based SQL injection"
curl -s "${BASE_URL}/api/search?id=1%20AND%20SLEEP(5)" | head -20
echo ""
echo "---"
echo ""
# Test 7: POST request with SQL injection
echo "Test 7: POST request with SQL injection"
curl -s -X POST "${BASE_URL}/api/search" -d "username=admin'%20OR%201=1--&password=test" | head -20
echo ""
echo "---"
echo ""
# Test 8: Information schema query
echo "Test 8: Information schema injection"
curl -s "${BASE_URL}/api/sql?table=information_schema.tables" | head -20
echo ""
echo "---"
echo ""
# Test 9: Stacked queries
echo "Test 9: Stacked queries injection"
curl -s "${BASE_URL}/api/database?id=1;DROP%20TABLE%20users" | head -20
echo ""
echo "---"
echo ""
echo "========================================="
echo "Tests completed!"
echo "Check logs for detailed attack detection"
echo "========================================="

View File

@@ -193,5 +193,170 @@
500,
502,
503
]
],
"server_errors": {
"nginx": {
"versions": ["1.18.0", "1.20.1", "1.22.0", "1.24.0"],
"template": "<!DOCTYPE html>\n<html>\n<head>\n<title>{code} {message}</title>\n<style>\nbody {{\n width: 35em;\n margin: 0 auto;\n font-family: Tahoma, Verdana, Arial, sans-serif;\n}}\n</style>\n</head>\n<body>\n<h1>An error occurred.</h1>\n<p>Sorry, the page you are looking for is currently unavailable.<br/>\nPlease try again later.</p>\n<p>If you are the system administrator of this resource then you should check the error log for details.</p>\n<p><em>Faithfully yours, nginx/{version}.</em></p>\n</body>\n</html>"
},
"apache": {
"versions": ["2.4.41", "2.4.52", "2.4.54", "2.4.57"],
"os": ["Ubuntu", "Debian", "CentOS"],
"template": "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\n<html><head>\n<title>{code} {message}</title>\n</head><body>\n<h1>{message}</h1>\n<p>The requested URL was not found on this server.</p>\n<hr>\n<address>Apache/{version} ({os}) Server at {host} Port 80</address>\n</body></html>"
},
"iis": {
"versions": ["10.0", "8.5", "8.0"],
"template": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n<html xmlns=\"http://www.w3.org/1999/xhtml\">\n<head>\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=iso-8859-1\"/>\n<title>{code} - {message}</title>\n<style type=\"text/css\">\nbody{{margin:0;font-size:.7em;font-family:Verdana, Arial, Helvetica, sans-serif;background:#EEEEEE;}}\nfieldset{{padding:0 15px 10px 15px;}}\nh1{{font-size:2.4em;margin:0;color:#FFF;}}\nh2{{font-size:1.7em;margin:0;color:#CC0000;}}\nh3{{font-size:1.2em;margin:10px 0 0 0;color:#000000;}}\n#header{{width:96%;margin:0 0 0 0;padding:6px 2% 6px 2%;font-family:\"trebuchet MS\", Verdana, sans-serif;color:#FFF;\nbackground-color:#555555;}}\n#content{{margin:0 0 0 2%;position:relative;}}\n</style>\n</head>\n<body>\n<div id=\"header\"><h1>Server Error</h1></div>\n<div id=\"content\">\n <div class=\"content-container\"><fieldset>\n <h2>{code} - {message}</h2>\n <h3>The page cannot be displayed because an internal server error has occurred.</h3>\n </fieldset></div>\n</div>\n</body>\n</html>"
},
"tomcat": {
"versions": ["9.0.65", "10.0.27", "10.1.5"],
"template": "<!doctype html><html lang=\"en\"><head><title>HTTP Status {code} - {message}</title><style type=\"text/css\">body {{font-family:Tahoma,Arial,sans-serif;}} h1, h2, h3, b {{color:white;background-color:#525D76;}} h1 {{font-size:22px;}} h2 {{font-size:16px;}} h3 {{font-size:14px;}} p {{font-size:12px;}} a {{color:black;}} .line {{height:1px;background-color:#525D76;border:none;}}</style></head><body><h1>HTTP Status {code} - {message}</h1><hr class=\"line\" /><p><b>Type</b> Status Report</p><p><b>Description</b> The server encountered an internal error that prevented it from fulfilling this request.</p><hr class=\"line\" /><h3>Apache Tomcat/{version}</h3></body></html>"
}
},
"sql_errors": {
"mysql": {
"generic": [
"You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''1'' at line 1",
"Unknown column '{column}' in 'where clause'",
"Table '{table}' doesn't exist",
"Operand should contain 1 column(s)",
"Subquery returns more than 1 row",
"Duplicate entry 'admin' for key 'PRIMARY'"
],
"quote": [
"You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''''' at line 1",
"Unclosed quotation mark after the character string ''",
"You have an error in your SQL syntax near '\\'' LIMIT 0,30'"
],
"union": [
"The used SELECT statements have a different number of columns",
"Operand should contain 1 column(s)",
"Mixing of GROUP columns (MIN(),MAX(),COUNT(),...) with no GROUP columns is illegal"
],
"boolean": [
"You have an error in your SQL syntax near 'OR 1=1' at line 1",
"Unknown column '1' in 'where clause'"
],
"time_based": [
"Query execution was interrupted",
"Lock wait timeout exceeded; try restarting transaction"
],
"comment": [
"You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '--' at line 1"
]
},
"postgresql": {
"generic": [
"ERROR: syntax error at or near \"1\"",
"ERROR: column \"{column}\" does not exist",
"ERROR: relation \"{table}\" does not exist",
"ERROR: operator does not exist: integer = text",
"ERROR: invalid input syntax for type integer: \"admin\""
],
"quote": [
"ERROR: unterminated quoted string at or near \"'\"",
"ERROR: syntax error at or near \"'\"",
"ERROR: unterminated quoted identifier at or near \"'\""
],
"union": [
"ERROR: each UNION query must have the same number of columns",
"ERROR: UNION types integer and text cannot be matched"
],
"boolean": [
"ERROR: syntax error at or near \"OR\"",
"ERROR: invalid input syntax for type boolean: \"1=1\""
],
"time_based": [
"ERROR: canceling statement due to user request",
"ERROR: function pg_sleep(integer) does not exist"
],
"info_schema": [
"ERROR: permission denied for table {table}",
"ERROR: permission denied for schema information_schema"
]
},
"mssql": {
"generic": [
"Msg 102, Level 15, State 1, Line 1\nIncorrect syntax near '1'.",
"Msg 207, Level 16, State 1, Line 1\nInvalid column name '{column}'.",
"Msg 208, Level 16, State 1, Line 1\nInvalid object name '{table}'.",
"Msg 245, Level 16, State 1, Line 1\nConversion failed when converting the varchar value 'admin' to data type int."
],
"quote": [
"Msg 105, Level 15, State 1, Line 1\nUnclosed quotation mark after the character string ''.",
"Msg 102, Level 15, State 1, Line 1\nIncorrect syntax near '''."
],
"union": [
"Msg 205, Level 16, State 1, Line 1\nAll queries combined using a UNION, INTERSECT or EXCEPT operator must have an equal number of expressions in their target lists.",
"Msg 8167, Level 16, State 1, Line 1\nThe type of column \"{column}\" conflicts with the type of other columns specified in the UNION, INTERSECT, or EXCEPT list."
],
"boolean": [
"Msg 102, Level 15, State 1, Line 1\nIncorrect syntax near 'OR'."
],
"command": [
"Msg 15281, Level 16, State 1, Procedure xp_cmdshell, Line 1\nSQL Server blocked access to procedure 'sys.xp_cmdshell' of component 'xp_cmdshell'"
]
},
"oracle": {
"generic": [
"ORA-00933: SQL command not properly ended",
"ORA-00904: \"{column}\": invalid identifier",
"ORA-00942: table or view \"{table}\" does not exist",
"ORA-01722: invalid number",
"ORA-01756: quoted string not properly terminated"
],
"quote": [
"ORA-01756: quoted string not properly terminated",
"ORA-00933: SQL command not properly ended"
],
"union": [
"ORA-01789: query block has incorrect number of result columns",
"ORA-01790: expression must have same datatype as corresponding expression"
],
"boolean": [
"ORA-00933: SQL command not properly ended",
"ORA-00920: invalid relational operator"
]
},
"sqlite": {
"generic": [
"near \"1\": syntax error",
"no such column: {column}",
"no such table: {table}",
"unrecognized token: \"'\"",
"incomplete input"
],
"quote": [
"unrecognized token: \"'\"",
"incomplete input",
"near \"'\": syntax error"
],
"union": [
"SELECTs to the left and right of UNION do not have the same number of result columns"
]
},
"mongodb": {
"generic": [
"MongoError: Can't canonicalize query: BadValue unknown operator: $where",
"MongoError: Failed to parse: { $where: \"this.{column} == '1'\" }",
"SyntaxError: unterminated string literal",
"MongoError: exception: invalid operator: $gt"
],
"quote": [
"SyntaxError: unterminated string literal",
"SyntaxError: missing } after property list"
],
"command": [
"MongoError: $where is not allowed in this context",
"MongoError: can't eval: security"
]
}
},
"attack_patterns": {
"path_traversal": "\\.\\.",
"sql_injection": "('|\"|`|--|#|/\\*|\\*/|\\bunion\\b|\\bunion\\s+select\\b|\\bor\\b.*=.*|\\band\\b.*=.*|'.*or.*'.*=.*'|\\bsleep\\b|\\bwaitfor\\b|\\bdelay\\b|\\bbenchmark\\b|;.*select|;.*drop|;.*insert|;.*update|;.*delete|\\bexec\\b|\\bexecute\\b|\\bxp_cmdshell\\b|information_schema|table_schema|table_name)",
"xss_attempt": "(<script|</script|javascript:|onerror=|onload=|onclick=|onmouseover=|onfocus=|onblur=|<iframe|<img|<svg|<embed|<object|<body|<input|eval\\(|alert\\(|prompt\\(|confirm\\(|document\\.|window\\.|<style|expression\\(|vbscript:|data:text/html)",
"common_probes": "(wp-admin|phpmyadmin|\\.env|\\.git|/admin|/config)",
"shell_injection": "(\\||;|`|\\$\\(|&&)"
}
}