Merge branch 'dev' into feat/randomized-server-header
This commit is contained in:
@@ -186,6 +186,7 @@ To customize the deception server installation several **environment variables**
|
|||||||
| `DASHBOARD_SECRET_PATH` | Custom dashboard path | Auto-generated |
|
| `DASHBOARD_SECRET_PATH` | Custom dashboard path | Auto-generated |
|
||||||
| `PROBABILITY_ERROR_CODES` | Error response probability (0-100%) | `0` |
|
| `PROBABILITY_ERROR_CODES` | Error response probability (0-100%) | `0` |
|
||||||
| `SERVER_HEADER` | HTTP Server header for deception, if not set use random server header | |
|
| `SERVER_HEADER` | HTTP Server header for deception, if not set use random server header | |
|
||||||
|
| `TIMEZONE` | IANA timezone for logs and dashboard (e.g., `America/New_York`, `Europe/Rome`) | System timezone |
|
||||||
|
|
||||||
## robots.txt
|
## robots.txt
|
||||||
The actual (juicy) robots.txt configuration is the following
|
The actual (juicy) robots.txt configuration is the following
|
||||||
|
|||||||
@@ -1,44 +0,0 @@
|
|||||||
apiVersion: apps/v1
|
|
||||||
kind: Deployment
|
|
||||||
metadata:
|
|
||||||
name: krawl-server
|
|
||||||
namespace: krawl
|
|
||||||
labels:
|
|
||||||
app: krawl-server
|
|
||||||
spec:
|
|
||||||
replicas: 1
|
|
||||||
selector:
|
|
||||||
matchLabels:
|
|
||||||
app: krawl-server
|
|
||||||
template:
|
|
||||||
metadata:
|
|
||||||
labels:
|
|
||||||
app: krawl-server
|
|
||||||
spec:
|
|
||||||
containers:
|
|
||||||
- name: krawl
|
|
||||||
image: ghcr.io/blessedrebus/krawl:latest
|
|
||||||
imagePullPolicy: Always
|
|
||||||
ports:
|
|
||||||
- containerPort: 5000
|
|
||||||
name: http
|
|
||||||
protocol: TCP
|
|
||||||
envFrom:
|
|
||||||
- configMapRef:
|
|
||||||
name: krawl-config
|
|
||||||
volumeMounts:
|
|
||||||
- name: wordlists
|
|
||||||
mountPath: /app/wordlists.json
|
|
||||||
subPath: wordlists.json
|
|
||||||
readOnly: true
|
|
||||||
resources:
|
|
||||||
requests:
|
|
||||||
memory: "64Mi"
|
|
||||||
cpu: "100m"
|
|
||||||
limits:
|
|
||||||
memory: "256Mi"
|
|
||||||
cpu: "500m"
|
|
||||||
volumes:
|
|
||||||
- name: wordlists
|
|
||||||
configMap:
|
|
||||||
name: krawl-wordlists
|
|
||||||
@@ -25,6 +25,8 @@ services:
|
|||||||
# - CANARY_TOKEN_URL=http://canarytokens.com/api/users/YOUR_TOKEN/passwords.txt
|
# - CANARY_TOKEN_URL=http://canarytokens.com/api/users/YOUR_TOKEN/passwords.txt
|
||||||
# Optional: Set custom dashboard path (auto-generated if not set)
|
# Optional: Set custom dashboard path (auto-generated if not set)
|
||||||
# - DASHBOARD_SECRET_PATH=/my-secret-dashboard
|
# - DASHBOARD_SECRET_PATH=/my-secret-dashboard
|
||||||
|
# Optional: Set timezone for logs and dashboard (e.g., America/New_York, Europe/Rome)
|
||||||
|
# - TIMEZONE=UTC
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "python3", "-c", "import requests; requests.get('http://localhost:5000')"]
|
test: ["CMD", "python3", "-c", "import requests; requests.get('http://localhost:5000')"]
|
||||||
|
|||||||
@@ -20,4 +20,7 @@ data:
|
|||||||
{{- end }}
|
{{- end }}
|
||||||
{{- if .Values.config.serverHeader }}
|
{{- if .Values.config.serverHeader }}
|
||||||
SERVER_HEADER: {{ .Values.config.serverHeader | quote }}
|
SERVER_HEADER: {{ .Values.config.serverHeader | quote }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
{{- if .Values.config.timezone }}
|
||||||
|
TIMEZONE: {{ .Values.config.timezone | quote }}
|
||||||
|
{{- end }}
|
||||||
|
|||||||
@@ -76,6 +76,7 @@ config:
|
|||||||
# serverHeader: "Apache/2.2.22 (Ubuntu)"
|
# serverHeader: "Apache/2.2.22 (Ubuntu)"
|
||||||
# dashboardSecretPath: "/my-secret-dashboard"
|
# dashboardSecretPath: "/my-secret-dashboard"
|
||||||
# canaryTokenUrl: set-your-canary-token-url-here
|
# canaryTokenUrl: set-your-canary-token-url-here
|
||||||
|
# timezone: "UTC" # IANA timezone (e.g., "America/New_York", "Europe/Rome"). If not set, system timezone is used.
|
||||||
|
|
||||||
networkPolicy:
|
networkPolicy:
|
||||||
enabled: true
|
enabled: true
|
||||||
|
|||||||
@@ -14,4 +14,5 @@ data:
|
|||||||
CANARY_TOKEN_TRIES: "10"
|
CANARY_TOKEN_TRIES: "10"
|
||||||
PROBABILITY_ERROR_CODES: "0"
|
PROBABILITY_ERROR_CODES: "0"
|
||||||
SERVER_HEADER: "Apache/2.2.22 (Ubuntu)"
|
SERVER_HEADER: "Apache/2.2.22 (Ubuntu)"
|
||||||
# CANARY_TOKEN_URL: set-your-canary-token-url-here
|
# CANARY_TOKEN_URL: set-your-canary-token-url-here
|
||||||
|
# TIMEZONE: "UTC" # IANA timezone (e.g., "America/New_York", "Europe/Rome")
|
||||||
@@ -3,6 +3,8 @@
|
|||||||
import os
|
import os
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Optional, Tuple
|
from typing import Optional, Tuple
|
||||||
|
from zoneinfo import ZoneInfo
|
||||||
|
import time
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -22,6 +24,40 @@ class Config:
|
|||||||
api_server_path: str = "/api/v2/users"
|
api_server_path: str = "/api/v2/users"
|
||||||
probability_error_codes: int = 0 # Percentage (0-100)
|
probability_error_codes: int = 0 # Percentage (0-100)
|
||||||
server_header: Optional[str] = None
|
server_header: Optional[str] = None
|
||||||
|
timezone: str = None # IANA timezone (e.g., 'America/New_York', 'Europe/Rome')
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
# Try to fetch timezone before if not set
|
||||||
|
def get_system_timezone() -> str:
|
||||||
|
"""Get the system's default timezone"""
|
||||||
|
try:
|
||||||
|
if os.path.islink('/etc/localtime'):
|
||||||
|
tz_path = os.readlink('/etc/localtime')
|
||||||
|
if 'zoneinfo/' in tz_path:
|
||||||
|
return tz_path.split('zoneinfo/')[-1]
|
||||||
|
|
||||||
|
local_tz = time.tzname[time.daylight]
|
||||||
|
if local_tz and local_tz != 'UTC':
|
||||||
|
return local_tz
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Default fallback to UTC
|
||||||
|
return 'UTC'
|
||||||
|
|
||||||
|
def get_timezone(self) -> ZoneInfo:
|
||||||
|
"""Get configured timezone as ZoneInfo object"""
|
||||||
|
if self.timezone:
|
||||||
|
try:
|
||||||
|
return ZoneInfo(self.timezone)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
system_tz = self.get_system_timezone()
|
||||||
|
try:
|
||||||
|
return ZoneInfo(system_tz)
|
||||||
|
except Exception:
|
||||||
|
return ZoneInfo('UTC')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_env(cls) -> 'Config':
|
def from_env(cls) -> 'Config':
|
||||||
@@ -45,6 +81,8 @@ class Config:
|
|||||||
api_server_url=os.getenv('API_SERVER_URL'),
|
api_server_url=os.getenv('API_SERVER_URL'),
|
||||||
api_server_port=int(os.getenv('API_SERVER_PORT', 8080)),
|
api_server_port=int(os.getenv('API_SERVER_PORT', 8080)),
|
||||||
api_server_path=os.getenv('API_SERVER_PATH', '/api/v2/users'),
|
api_server_path=os.getenv('API_SERVER_PATH', '/api/v2/users'),
|
||||||
probability_error_codes=int(os.getenv('PROBABILITY_ERROR_CODES', 5)),
|
probability_error_codes=int(os.getenv('PROBABILITY_ERROR_CODES', 0)),
|
||||||
server_header=os.getenv('SERVER_HEADER')
|
server_header=os.getenv('SERVER_HEADER')
|
||||||
|
timezone=os.getenv('TIMEZONE') # If not set, will use system timezone
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -8,6 +8,23 @@ Provides two loggers: app (application) and access (HTTP access logs).
|
|||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
from logging.handlers import RotatingFileHandler
|
from logging.handlers import RotatingFileHandler
|
||||||
|
from typing import Optional
|
||||||
|
from zoneinfo import ZoneInfo
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
|
||||||
|
class TimezoneFormatter(logging.Formatter):
|
||||||
|
"""Custom formatter that respects configured timezone"""
|
||||||
|
def __init__(self, fmt=None, datefmt=None, timezone: Optional[ZoneInfo] = None):
|
||||||
|
super().__init__(fmt, datefmt)
|
||||||
|
self.timezone = timezone or ZoneInfo('UTC')
|
||||||
|
|
||||||
|
def formatTime(self, record, datefmt=None):
|
||||||
|
"""Override formatTime to use configured timezone"""
|
||||||
|
dt = datetime.fromtimestamp(record.created, tz=self.timezone)
|
||||||
|
if datefmt:
|
||||||
|
return dt.strftime(datefmt)
|
||||||
|
return dt.isoformat()
|
||||||
|
|
||||||
|
|
||||||
class LoggerManager:
|
class LoggerManager:
|
||||||
@@ -20,23 +37,27 @@ class LoggerManager:
|
|||||||
cls._instance._initialized = False
|
cls._instance._initialized = False
|
||||||
return cls._instance
|
return cls._instance
|
||||||
|
|
||||||
def initialize(self, log_dir: str = "logs") -> None:
|
def initialize(self, log_dir: str = "logs", timezone: Optional[ZoneInfo] = None) -> None:
|
||||||
"""
|
"""
|
||||||
Initialize the logging system with rotating file handlers.
|
Initialize the logging system with rotating file handlers.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
log_dir: Directory for log files (created if not exists)
|
log_dir: Directory for log files (created if not exists)
|
||||||
|
timezone: ZoneInfo timezone for log timestamps (defaults to UTC)
|
||||||
"""
|
"""
|
||||||
if self._initialized:
|
if self._initialized:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
self.timezone = timezone or ZoneInfo('UTC')
|
||||||
|
|
||||||
# Create log directory if it doesn't exist
|
# Create log directory if it doesn't exist
|
||||||
os.makedirs(log_dir, exist_ok=True)
|
os.makedirs(log_dir, exist_ok=True)
|
||||||
|
|
||||||
# Common format for all loggers
|
# Common format for all loggers
|
||||||
log_format = logging.Formatter(
|
log_format = TimezoneFormatter(
|
||||||
"[%(asctime)s] %(levelname)s - %(message)s",
|
"[%(asctime)s] %(levelname)s - %(message)s",
|
||||||
datefmt="%Y-%m-%d %H:%M:%S"
|
datefmt="%Y-%m-%d %H:%M:%S",
|
||||||
|
timezone=self.timezone
|
||||||
)
|
)
|
||||||
|
|
||||||
# Rotation settings: 1MB max, 5 backups
|
# Rotation settings: 1MB max, 5 backups
|
||||||
@@ -83,7 +104,7 @@ class LoggerManager:
|
|||||||
self._credential_logger.handlers.clear()
|
self._credential_logger.handlers.clear()
|
||||||
|
|
||||||
# Credential logger uses a simple format: timestamp|ip|username|password|path
|
# Credential logger uses a simple format: timestamp|ip|username|password|path
|
||||||
credential_format = logging.Formatter("%(message)s")
|
credential_format = TimezoneFormatter("%(message)s", timezone=self.timezone)
|
||||||
|
|
||||||
credential_file_handler = RotatingFileHandler(
|
credential_file_handler = RotatingFileHandler(
|
||||||
os.path.join(log_dir, "credentials.log"),
|
os.path.join(log_dir, "credentials.log"),
|
||||||
@@ -136,6 +157,6 @@ def get_credential_logger() -> logging.Logger:
|
|||||||
return _logger_manager.credentials
|
return _logger_manager.credentials
|
||||||
|
|
||||||
|
|
||||||
def initialize_logging(log_dir: str = "logs") -> None:
|
def initialize_logging(log_dir: str = "logs", timezone: Optional[ZoneInfo] = None) -> None:
|
||||||
"""Initialize the logging system."""
|
"""Initialize the logging system."""
|
||||||
_logger_manager.initialize(log_dir)
|
_logger_manager.initialize(log_dir, timezone)
|
||||||
|
|||||||
@@ -33,6 +33,8 @@ def print_usage():
|
|||||||
print(' PROBABILITY_ERROR_CODES - Probability (0-100) to return HTTP error codes (default: 0)')
|
print(' PROBABILITY_ERROR_CODES - Probability (0-100) to return HTTP error codes (default: 0)')
|
||||||
print(' CHAR_SPACE - Characters for random links')
|
print(' CHAR_SPACE - Characters for random links')
|
||||||
print(' SERVER_HEADER - HTTP Server header for deception (default: Apache/2.2.22 (Ubuntu))')
|
print(' SERVER_HEADER - HTTP Server header for deception (default: Apache/2.2.22 (Ubuntu))')
|
||||||
|
print(' TIMEZONE - IANA timezone for logs/dashboard (e.g., America/New_York, Europe/Rome)')
|
||||||
|
print(' If not set, system timezone will be used')
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
@@ -41,15 +43,19 @@ def main():
|
|||||||
print_usage()
|
print_usage()
|
||||||
exit(0)
|
exit(0)
|
||||||
|
|
||||||
# Initialize logging
|
config = Config.from_env()
|
||||||
initialize_logging()
|
|
||||||
|
# Get timezone configuration
|
||||||
|
tz = config.get_timezone()
|
||||||
|
|
||||||
|
# Initialize logging with timezone
|
||||||
|
initialize_logging(timezone=tz)
|
||||||
app_logger = get_app_logger()
|
app_logger = get_app_logger()
|
||||||
access_logger = get_access_logger()
|
access_logger = get_access_logger()
|
||||||
credential_logger = get_credential_logger()
|
credential_logger = get_credential_logger()
|
||||||
|
|
||||||
config = Config.from_env()
|
# Initialize tracker with timezone
|
||||||
|
tracker = AccessTracker(timezone=tz)
|
||||||
tracker = AccessTracker()
|
|
||||||
|
|
||||||
Handler.config = config
|
Handler.config = config
|
||||||
Handler.tracker = tracker
|
Handler.tracker = tracker
|
||||||
@@ -71,6 +77,7 @@ def main():
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
app_logger.info(f'Starting deception server on port {config.port}...')
|
app_logger.info(f'Starting deception server on port {config.port}...')
|
||||||
|
app_logger.info(f'Timezone configured: {tz.key}')
|
||||||
app_logger.info(f'Dashboard available at: {config.dashboard_secret_path}')
|
app_logger.info(f'Dashboard available at: {config.dashboard_secret_path}')
|
||||||
if config.canary_token_url:
|
if config.canary_token_url:
|
||||||
app_logger.info(f'Canary token will appear after {config.canary_token_tries} tries')
|
app_logger.info(f'Canary token will appear after {config.canary_token_tries} tries')
|
||||||
|
|||||||
@@ -5,6 +5,18 @@ Dashboard template for viewing honeypot statistics.
|
|||||||
Customize this template to change the dashboard appearance.
|
Customize this template to change the dashboard appearance.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
|
||||||
|
def format_timestamp(iso_timestamp: str) -> str:
|
||||||
|
"""Format ISO timestamp for display (YYYY-MM-DD HH:MM:SS)"""
|
||||||
|
try:
|
||||||
|
dt = datetime.fromisoformat(iso_timestamp)
|
||||||
|
return dt.strftime("%Y-%m-%d %H:%M:%S")
|
||||||
|
except Exception:
|
||||||
|
# Fallback for old format
|
||||||
|
return iso_timestamp.split("T")[1][:8] if "T" in iso_timestamp else iso_timestamp
|
||||||
|
|
||||||
|
|
||||||
def generate_dashboard(stats: dict) -> str:
|
def generate_dashboard(stats: dict) -> str:
|
||||||
"""Generate dashboard HTML with access statistics"""
|
"""Generate dashboard HTML with access statistics"""
|
||||||
@@ -29,7 +41,7 @@ def generate_dashboard(stats: dict) -> str:
|
|||||||
|
|
||||||
# Generate suspicious accesses rows
|
# Generate suspicious accesses rows
|
||||||
suspicious_rows = '\n'.join([
|
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>'
|
f'<tr><td>{log["ip"]}</td><td>{log["path"]}</td><td style="word-break: break-all;">{log["user_agent"][:60]}</td><td>{format_timestamp(log["timestamp"])}</td></tr>'
|
||||||
for log in stats['recent_suspicious'][-10:]
|
for log in stats['recent_suspicious'][-10:]
|
||||||
]) or '<tr><td colspan="4" style="text-align:center;">No suspicious activity detected</td></tr>'
|
]) or '<tr><td colspan="4" style="text-align:center;">No suspicious activity detected</td></tr>'
|
||||||
|
|
||||||
@@ -41,13 +53,13 @@ def generate_dashboard(stats: dict) -> str:
|
|||||||
|
|
||||||
# Generate attack types rows
|
# Generate attack types rows
|
||||||
attack_type_rows = '\n'.join([
|
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>'
|
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>{format_timestamp(log["timestamp"])}</td></tr>'
|
||||||
for log in stats.get('attack_types', [])[-10:]
|
for log in stats.get('attack_types', [])[-10:]
|
||||||
]) or '<tr><td colspan="4" style="text-align:center;">No attacks detected</td></tr>'
|
]) or '<tr><td colspan="4" style="text-align:center;">No attacks detected</td></tr>'
|
||||||
|
|
||||||
# Generate credential attempts rows
|
# Generate credential attempts rows
|
||||||
credential_rows = '\n'.join([
|
credential_rows = '\n'.join([
|
||||||
f'<tr><td>{log["ip"]}</td><td>{log["username"]}</td><td>{log["password"]}</td><td>{log["path"]}</td><td>{log["timestamp"].split("T")[1][:8]}</td></tr>'
|
f'<tr><td>{log["ip"]}</td><td>{log["username"]}</td><td>{log["password"]}</td><td>{log["path"]}</td><td>{format_timestamp(log["timestamp"])}</td></tr>'
|
||||||
for log in stats.get('credential_attempts', [])[-20:]
|
for log in stats.get('credential_attempts', [])[-20:]
|
||||||
]) or '<tr><td colspan="5" style="text-align:center;">No credentials captured yet</td></tr>'
|
]) or '<tr><td colspan="5" style="text-align:center;">No credentials captured yet</td></tr>'
|
||||||
|
|
||||||
|
|||||||
@@ -1,20 +1,22 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
from typing import Dict, List, Tuple
|
from typing import Dict, List, Tuple, Optional
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from zoneinfo import ZoneInfo
|
||||||
import re
|
import re
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
|
|
||||||
|
|
||||||
class AccessTracker:
|
class AccessTracker:
|
||||||
"""Track IP addresses and paths accessed"""
|
"""Track IP addresses and paths accessed"""
|
||||||
def __init__(self):
|
def __init__(self, timezone: Optional[ZoneInfo] = None):
|
||||||
self.ip_counts: Dict[str, int] = defaultdict(int)
|
self.ip_counts: Dict[str, int] = defaultdict(int)
|
||||||
self.path_counts: Dict[str, int] = defaultdict(int)
|
self.path_counts: Dict[str, int] = defaultdict(int)
|
||||||
self.user_agent_counts: Dict[str, int] = defaultdict(int)
|
self.user_agent_counts: Dict[str, int] = defaultdict(int)
|
||||||
self.access_log: List[Dict] = []
|
self.access_log: List[Dict] = []
|
||||||
self.credential_attempts: List[Dict] = []
|
self.credential_attempts: List[Dict] = []
|
||||||
|
self.timezone = timezone or ZoneInfo('UTC')
|
||||||
self.suspicious_patterns = [
|
self.suspicious_patterns = [
|
||||||
'bot', 'crawler', 'spider', 'scraper', 'curl', 'wget', 'python-requests',
|
'bot', 'crawler', 'spider', 'scraper', 'curl', 'wget', 'python-requests',
|
||||||
'scanner', 'nikto', 'sqlmap', 'nmap', 'masscan', 'nessus', 'acunetix',
|
'scanner', 'nikto', 'sqlmap', 'nmap', 'masscan', 'nessus', 'acunetix',
|
||||||
@@ -81,7 +83,7 @@ class AccessTracker:
|
|||||||
'path': path,
|
'path': path,
|
||||||
'username': username,
|
'username': username,
|
||||||
'password': password,
|
'password': password,
|
||||||
'timestamp': datetime.now().isoformat()
|
'timestamp': datetime.now(self.timezone).isoformat()
|
||||||
})
|
})
|
||||||
|
|
||||||
def record_access(self, ip: str, path: str, user_agent: str = '', body: str = ''):
|
def record_access(self, ip: str, path: str, user_agent: str = '', body: str = ''):
|
||||||
@@ -112,7 +114,7 @@ class AccessTracker:
|
|||||||
'suspicious': is_suspicious,
|
'suspicious': is_suspicious,
|
||||||
'honeypot_triggered': self.is_honeypot_path(path),
|
'honeypot_triggered': self.is_honeypot_path(path),
|
||||||
'attack_types':attack_findings,
|
'attack_types':attack_findings,
|
||||||
'timestamp': datetime.now().isoformat()
|
'timestamp': datetime.now(self.timezone).isoformat()
|
||||||
})
|
})
|
||||||
|
|
||||||
def detect_attack_type(self, data:str) -> list[str]:
|
def detect_attack_type(self, data:str) -> list[str]:
|
||||||
|
|||||||
150
tests/test_credentials.sh
Executable file
150
tests/test_credentials.sh
Executable file
@@ -0,0 +1,150 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# This script sends various POST requests with credentials to the honeypot
|
||||||
|
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
BLUE='\033[0;34m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
RED='\033[0;31m'
|
||||||
|
NC='\033[0m'
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
HOST="localhost"
|
||||||
|
PORT="5000"
|
||||||
|
BASE_URL="http://${HOST}:${PORT}"
|
||||||
|
|
||||||
|
echo -e "${BLUE}========================================${NC}"
|
||||||
|
echo -e "${BLUE}Krawl Credential Logging Test Script${NC}"
|
||||||
|
echo -e "${BLUE}========================================${NC}\n"
|
||||||
|
|
||||||
|
# Check if server is running
|
||||||
|
echo -e "${YELLOW}Checking if server is running on ${BASE_URL}...${NC}"
|
||||||
|
if ! curl -s -f "${BASE_URL}/health" > /dev/null 2>&1; then
|
||||||
|
echo -e "${RED}❌ Server is not running. Please start the Krawl server first.${NC}"
|
||||||
|
echo -e "${YELLOW}Run: python3 src/server.py${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo -e "${GREEN}✓ Server is running${NC}\n"
|
||||||
|
|
||||||
|
# Test 1: Simple login form POST
|
||||||
|
echo -e "${YELLOW}Test 1: POST to /login with form data${NC}"
|
||||||
|
curl -s -X POST "${BASE_URL}/login" \
|
||||||
|
-H "Content-Type: application/x-www-form-urlencoded" \
|
||||||
|
-d "username=admin&password=admin123" \
|
||||||
|
> /dev/null
|
||||||
|
echo -e "${GREEN}✓ Sent: admin / admin123${NC}\n"
|
||||||
|
|
||||||
|
sleep 1
|
||||||
|
|
||||||
|
# Test 2: Admin panel login
|
||||||
|
echo -e "${YELLOW}Test 2: POST to /admin with credentials${NC}"
|
||||||
|
curl -s -X POST "${BASE_URL}/admin" \
|
||||||
|
-H "Content-Type: application/x-www-form-urlencoded" \
|
||||||
|
-d "user=root&pass=toor&submit=Login" \
|
||||||
|
> /dev/null
|
||||||
|
echo -e "${GREEN}✓ Sent: root / toor${NC}\n"
|
||||||
|
|
||||||
|
sleep 1
|
||||||
|
|
||||||
|
# Test 3: WordPress login attempt
|
||||||
|
echo -e "${YELLOW}Test 3: POST to /wp-login.php${NC}"
|
||||||
|
curl -s -X POST "${BASE_URL}/wp-login.php" \
|
||||||
|
-H "Content-Type: application/x-www-form-urlencoded" \
|
||||||
|
-d "log=wpuser&pwd=Password1&wp-submit=Log+In" \
|
||||||
|
> /dev/null
|
||||||
|
echo -e "${GREEN}✓ Sent: wpuser / Password1${NC}\n"
|
||||||
|
|
||||||
|
sleep 1
|
||||||
|
|
||||||
|
# Test 4: JSON formatted credentials
|
||||||
|
echo -e "${YELLOW}Test 4: POST to /api/login with JSON${NC}"
|
||||||
|
curl -s -X POST "${BASE_URL}/api/login" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"username":"apiuser","password":"apipass123","remember":true}' \
|
||||||
|
> /dev/null
|
||||||
|
echo -e "${GREEN}✓ Sent: apiuser / apipass123${NC}\n"
|
||||||
|
|
||||||
|
sleep 1
|
||||||
|
|
||||||
|
# Test 5: SSH-style login
|
||||||
|
echo -e "${YELLOW}Test 5: POST to /ssh with credentials${NC}"
|
||||||
|
curl -s -X POST "${BASE_URL}/ssh" \
|
||||||
|
-H "Content-Type: application/x-www-form-urlencoded" \
|
||||||
|
-d "username=sshuser&password=P@ssw0rd!" \
|
||||||
|
> /dev/null
|
||||||
|
echo -e "${GREEN}✓ Sent: sshuser / P@ssw0rd!${NC}\n"
|
||||||
|
|
||||||
|
sleep 1
|
||||||
|
|
||||||
|
# Test 6: Database admin
|
||||||
|
echo -e "${YELLOW}Test 6: POST to /phpmyadmin with credentials${NC}"
|
||||||
|
curl -s -X POST "${BASE_URL}/phpmyadmin" \
|
||||||
|
-H "Content-Type: application/x-www-form-urlencoded" \
|
||||||
|
-d "pma_username=dbadmin&pma_password=dbpass123&server=1" \
|
||||||
|
> /dev/null
|
||||||
|
echo -e "${GREEN}✓ Sent: dbadmin / dbpass123${NC}\n"
|
||||||
|
|
||||||
|
sleep 1
|
||||||
|
|
||||||
|
# Test 7: Multiple fields with email
|
||||||
|
echo -e "${YELLOW}Test 7: POST to /register with email${NC}"
|
||||||
|
curl -s -X POST "${BASE_URL}/register" \
|
||||||
|
-H "Content-Type: application/x-www-form-urlencoded" \
|
||||||
|
-d "email=test@example.com&username=newuser&password=NewPass123&confirm_password=NewPass123" \
|
||||||
|
> /dev/null
|
||||||
|
echo -e "${GREEN}✓ Sent: newuser / NewPass123 (email: test@example.com)${NC}\n"
|
||||||
|
|
||||||
|
sleep 1
|
||||||
|
|
||||||
|
# Test 8: FTP credentials
|
||||||
|
echo -e "${YELLOW}Test 8: POST to /ftp/login${NC}"
|
||||||
|
curl -s -X POST "${BASE_URL}/ftp/login" \
|
||||||
|
-H "Content-Type: application/x-www-form-urlencoded" \
|
||||||
|
-d "ftpuser=ftpadmin&ftppass=ftp123456" \
|
||||||
|
> /dev/null
|
||||||
|
echo -e "${GREEN}✓ Sent: ftpadmin / ftp123456${NC}\n"
|
||||||
|
|
||||||
|
sleep 1
|
||||||
|
|
||||||
|
# Test 9: Common brute force attempt
|
||||||
|
echo -e "${YELLOW}Test 9: Multiple attempts (simulating brute force)${NC}"
|
||||||
|
for i in {1..3}; do
|
||||||
|
curl -s -X POST "${BASE_URL}/login" \
|
||||||
|
-H "Content-Type: application/x-www-form-urlencoded" \
|
||||||
|
-d "username=admin&password=pass${i}" \
|
||||||
|
> /dev/null
|
||||||
|
echo -e "${GREEN}✓ Attempt $i: admin / pass${i}${NC}"
|
||||||
|
sleep 0.5
|
||||||
|
done
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
sleep 1
|
||||||
|
|
||||||
|
# Test 10: Special characters in credentials
|
||||||
|
echo -e "${YELLOW}Test 10: POST with special characters${NC}"
|
||||||
|
curl -s -X POST "${BASE_URL}/login" \
|
||||||
|
-H "Content-Type: application/x-www-form-urlencoded" \
|
||||||
|
--data-urlencode "username=user@domain.com" \
|
||||||
|
--data-urlencode "password=P@\$\$w0rd!#%" \
|
||||||
|
> /dev/null
|
||||||
|
echo -e "${GREEN}✓ Sent: user@domain.com / P@\$\$w0rd!#%${NC}\n"
|
||||||
|
|
||||||
|
echo -e "${BLUE}========================================${NC}"
|
||||||
|
echo -e "${GREEN}✓ All credential tests completed!${NC}"
|
||||||
|
echo -e "${BLUE}========================================${NC}\n"
|
||||||
|
|
||||||
|
echo -e "${YELLOW}Check the results:${NC}"
|
||||||
|
echo -e " 1. View the log file: ${GREEN}cat src/logs/credentials.log${NC}"
|
||||||
|
echo -e " 2. View the dashboard: ${GREEN}${BASE_URL}/dashboard${NC}"
|
||||||
|
echo -e " 3. Check recent logs: ${GREEN}tail -20 src/logs/krawl.log${NC}\n"
|
||||||
|
|
||||||
|
# Display last 10 credential entries if log file exists
|
||||||
|
if [ -f "src/logs/credentials.log" ]; then
|
||||||
|
echo -e "${BLUE}========================================${NC}"
|
||||||
|
echo -e "${BLUE}Last 10 Captured Credentials:${NC}"
|
||||||
|
echo -e "${BLUE}========================================${NC}"
|
||||||
|
tail -10 src/logs/credentials.log
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "${YELLOW}💡 Tip: Open ${BASE_URL}/dashboard in your browser to see the credentials in real-time!${NC}"
|
||||||
Reference in New Issue
Block a user