First commit
This commit is contained in:
14
.dockerignore
Normal file
14
.dockerignore
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
__pycache__
|
||||||
|
*.pyc
|
||||||
|
*.pyo
|
||||||
|
*.pyd
|
||||||
|
.Python
|
||||||
|
*.so
|
||||||
|
*.egg
|
||||||
|
*.egg-info
|
||||||
|
dist
|
||||||
|
build
|
||||||
|
.git
|
||||||
|
.gitignore
|
||||||
|
README.md
|
||||||
|
*.md
|
||||||
71
.gitignore
vendored
Normal file
71
.gitignore
vendored
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
# Python
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
*.so
|
||||||
|
.Python
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
wheels/
|
||||||
|
pip-wheel-metadata/
|
||||||
|
share/python-wheels/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
MANIFEST
|
||||||
|
|
||||||
|
# Virtual Environment
|
||||||
|
venv/
|
||||||
|
env/
|
||||||
|
ENV/
|
||||||
|
env.bak/
|
||||||
|
venv.bak/
|
||||||
|
|
||||||
|
# IDEs
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# Helm
|
||||||
|
helm/charts/
|
||||||
|
helm/*.tgz
|
||||||
|
helm/values-production.yaml
|
||||||
|
helm/values-*.yaml
|
||||||
|
!helm/values.yaml
|
||||||
|
|
||||||
|
# Kubernetes secrets (if generated locally)
|
||||||
|
*.secret.yaml
|
||||||
|
secrets/
|
||||||
|
|
||||||
|
# Docker
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Environment variables
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
.env.*.local
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
*.log
|
||||||
|
logs/
|
||||||
|
|
||||||
|
# Temporary files
|
||||||
|
*.tmp
|
||||||
|
*.temp
|
||||||
|
.cache/
|
||||||
|
|
||||||
|
# Personal canary tokens or sensitive configs
|
||||||
|
*canary*token*.yaml
|
||||||
|
personal-values.yaml
|
||||||
19
Dockerfile
Normal file
19
Dockerfile
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
FROM python:3.11-slim
|
||||||
|
|
||||||
|
LABEL org.opencontainers.image.source=https://github.com/BlessedRebuS/Krawl
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY src/ /app/src/
|
||||||
|
COPY wordlists.json /app/
|
||||||
|
|
||||||
|
RUN useradd -m -u 1000 krawl && \
|
||||||
|
chown -R krawl:krawl /app
|
||||||
|
|
||||||
|
USER krawl
|
||||||
|
|
||||||
|
EXPOSE 5000
|
||||||
|
|
||||||
|
ENV PYTHONUNBUFFERED=1
|
||||||
|
|
||||||
|
CMD ["python3", "src/server.py"]
|
||||||
299
README.md
299
README.md
@@ -1,2 +1,297 @@
|
|||||||
# Krawl
|
<h1 align="center">🕷️ Krawl</h1>
|
||||||
Krawl is a Web Honeypot & Deception server that aims to foul enumerations, web crawling, fuzzing and bruteforcing
|
|
||||||
|
<p align="center">
|
||||||
|
A modern, customizable zero-dependencies honeypot server designed to detect and track malicious activity through deceptive web pages, fake credentials, and canary tokens.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
|
<a href="https://github.com/blessedrebus/krawl/blob/main/LICENSE">
|
||||||
|
<img src="https://img.shields.io/github/license/blessedrebus/krawl" alt="License">
|
||||||
|
</a>
|
||||||
|
<a href="https://github.com/blessedrebus/krawl/releases">
|
||||||
|
<img src="https://img.shields.io/github/v/release/blessedrebus/krawl" alt="Release">
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
|
<a href="https://ghcr.io/blessedrebus/krawl">
|
||||||
|
<img src="https://img.shields.io/badge/ghcr.io-krawl-blue" alt="GitHub Container Registry">
|
||||||
|
</a>
|
||||||
|
<a href="https://kubernetes.io/">
|
||||||
|
<img src="https://img.shields.io/badge/kubernetes-ready-326CE5?logo=kubernetes&logoColor=white" alt="Kubernetes">
|
||||||
|
</a>
|
||||||
|
<a href="https://github.com/BlessedRebuS/Krawl/pkgs/container/krawl-chart">
|
||||||
|
<img src="https://img.shields.io/badge/helm-chart-0F1689?logo=helm&logoColor=white" alt="Helm Chart">
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="#-overview">Overview</a> •
|
||||||
|
<a href="#-quick-start">Quick Start</a> •
|
||||||
|
<a href="#%EF%B8%8F-configuration">Configuration</a> •
|
||||||
|
<a href="#-dashboard">Dashboard</a> •
|
||||||
|
<a href="#-deception-techniques">Deception Techniques</a> •
|
||||||
|
<a href="#-contributing">Contributing</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## What is Krawl?
|
||||||
|
|
||||||
|
Krawl is a simple cloud native deception server that creates fake web applications with low hanging fruit and juicy fake random information.
|
||||||
|
|
||||||
|
It features:
|
||||||
|
|
||||||
|
- **Spider Trap Pages**: Infinite random links to waste crawler resources based on the [spidertrap project](https://github.com/adhdproject/spidertrap)
|
||||||
|
- **Fake Login Pages**: WordPress, phpMyAdmin, admin panels
|
||||||
|
- **Honeypot Paths**: Advertised in robots.txt to catch scanners
|
||||||
|
- **Fake Credentials**: Realistic-looking usernames, passwords, API keys
|
||||||
|
- **Canary Token Integration**: External alert triggering
|
||||||
|
- **Real-time Dashboard**: Monitor suspicious activity
|
||||||
|
- **Customizable Wordlists**: Easy JSON-based configuration
|
||||||
|
- **Random Error Injection**: Mimic real server behavior
|
||||||
|
|
||||||
|
## 🚀 Quick Start
|
||||||
|
## Helm Chart
|
||||||
|
|
||||||
|
Install with default values
|
||||||
|
|
||||||
|
```bash
|
||||||
|
helm install krawl ./helm \
|
||||||
|
--namespace krawl-system \
|
||||||
|
--create-namespace
|
||||||
|
```
|
||||||
|
|
||||||
|
Install with custom values
|
||||||
|
|
||||||
|
```bash
|
||||||
|
helm install krawl ./helm \
|
||||||
|
--namespace krawl-system \
|
||||||
|
--create-namespace \
|
||||||
|
--values values.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
Install with custom canary token
|
||||||
|
|
||||||
|
```bash
|
||||||
|
helm install krawl ./helm \
|
||||||
|
--namespace krawl-system \
|
||||||
|
--create-namespace \
|
||||||
|
--set config.canaryTokenUrl="http://your-canary-token-url"
|
||||||
|
```
|
||||||
|
|
||||||
|
Uninstall with
|
||||||
|
```bash
|
||||||
|
helm uninstall krawl --namespace krawl-system
|
||||||
|
```
|
||||||
|
|
||||||
|
## Kubernetes / Kustomize
|
||||||
|
Apply all manifests
|
||||||
|
|
||||||
|
```bash
|
||||||
|
kubectl apply -k manifests/
|
||||||
|
```
|
||||||
|
Retrieve dashboard path
|
||||||
|
```bash
|
||||||
|
kubectl get secret krawl-server -n krawl-system -o jsonpath='{.data.dashboard-path}' | base64 -d
|
||||||
|
```
|
||||||
|
Uninstall with
|
||||||
|
```bash
|
||||||
|
kubectl delete -k manifests/
|
||||||
|
```
|
||||||
|
|
||||||
|
## Docker
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run -d \
|
||||||
|
-p 5000:5000 \
|
||||||
|
-e CANARY_TOKEN_URL="http://your-canary-token-url" \
|
||||||
|
--name krawl \
|
||||||
|
ghcr.io/blessedrebus/krawl:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
## Docker Compose
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker-compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
## Python 3.11+
|
||||||
|
|
||||||
|
Clone the repository
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/blessedrebus/krawl.git
|
||||||
|
cd krawl/src
|
||||||
|
```
|
||||||
|
Run the server
|
||||||
|
```bash
|
||||||
|
python3 server.py
|
||||||
|
```
|
||||||
|
|
||||||
|
Visit
|
||||||
|
|
||||||
|
`http://localhost:5000`
|
||||||
|
|
||||||
|
To access the dashboard
|
||||||
|
|
||||||
|
`http://localhost:5000/dashboard-secret-path`
|
||||||
|
|
||||||
|
## Configuration via Environment Variables
|
||||||
|
|
||||||
|
To customize the deception server installation several **environment variables** can be specified.
|
||||||
|
|
||||||
|
| Variable | Description | Default |
|
||||||
|
|----------|-------------|---------|
|
||||||
|
| `PORT` | Server listening port | `5000` |
|
||||||
|
| `DELAY` | Response delay in milliseconds | `100` |
|
||||||
|
| `LINKS_MIN_LENGTH` | Minimum random link length | `5` |
|
||||||
|
| `LINKS_MAX_LENGTH` | Maximum random link length | `15` |
|
||||||
|
| `LINKS_MIN_PER_PAGE` | Minimum links per page | `10` |
|
||||||
|
| `LINKS_MAX_PER_PAGE` | Maximum links per page | `15` |
|
||||||
|
| `MAX_COUNTER` | Initial counter value | `10` |
|
||||||
|
| `CANARY_TOKEN_TRIES` | Requests before showing canary token | `10` |
|
||||||
|
| `CANARY_TOKEN_URL` | External canary token URL | None |
|
||||||
|
| `DASHBOARD_SECRET_PATH` | Custom dashboard path | Auto-generated |
|
||||||
|
| `PROBABILITY_ERROR_CODES` | Error response probability (0-100%) | `0` |
|
||||||
|
|
||||||
|
## robots.txt
|
||||||
|
The actual (juicy) robots.txt configuration is the following
|
||||||
|
|
||||||
|
```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
|
||||||
|
```
|
||||||
|
## Wordlists Customization
|
||||||
|
|
||||||
|
Edit `wordlists.json` to customize fake data:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"usernames": {
|
||||||
|
"prefixes": ["admin", "root", "user"],
|
||||||
|
"suffixes": ["_prod", "_dev", "123"]
|
||||||
|
},
|
||||||
|
"passwords": {
|
||||||
|
"prefixes": ["P@ssw0rd", "Admin"],
|
||||||
|
"simple": ["test", "password"]
|
||||||
|
},
|
||||||
|
"directory_listing": {
|
||||||
|
"files": ["credentials.txt", "backup.sql"],
|
||||||
|
"directories": ["admin/", "backup/"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
or **values.yaml** in the case of helm chart installation
|
||||||
|
|
||||||
|
## Dashboard
|
||||||
|
|
||||||
|
Access the dashboard at `http://<server-ip>:<port>/<dashboard-path>`
|
||||||
|
|
||||||
|
The attackers' triggered honeypot path and the suspicious activity (such as failed login attempts) are logged
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
The top IP Addresses is shown along with top paths and User Agents
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
The dashboard shows:
|
||||||
|
- Total and unique accesses
|
||||||
|
- Suspicious activity detection
|
||||||
|
- Honeypot triggers
|
||||||
|
- Top IPs, paths, and user-agents
|
||||||
|
- Real-time monitoring
|
||||||
|
|
||||||
|
### Retrieving Dashboard Path
|
||||||
|
|
||||||
|
Check server startup logs
|
||||||
|
|
||||||
|
**Python/Docker:**
|
||||||
|
```bash
|
||||||
|
docker logs krawl | grep "Dashboard available"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Kubernetes:**
|
||||||
|
```bash
|
||||||
|
kubectl get secret krawl-server -n krawl-system \
|
||||||
|
-o jsonpath='{.data.dashboard-path}' | base64 -d && echo
|
||||||
|
```
|
||||||
|
|
||||||
|
**Helm:**
|
||||||
|
```bash
|
||||||
|
kubectl get secret krawl -n krawl-system \
|
||||||
|
-o jsonpath='{.data.dashboard-path}' | base64 -d && echo
|
||||||
|
```
|
||||||
|
|
||||||
|
## Deception Techniques
|
||||||
|
|
||||||
|
### 1. Robots.txt Honeypots
|
||||||
|
Advertises forbidden paths that legitimate crawlers avoid but scanners investigate:
|
||||||
|
- `/admin/`, `/backup/`, `/config/`
|
||||||
|
- `/credentials.txt`, `/.env`, `/passwords.txt`
|
||||||
|
|
||||||
|
### 2. Fake Services
|
||||||
|
Mimics real applications:
|
||||||
|
- WordPress (`/wp-admin`, `/wp-login.php`)
|
||||||
|
- phpMyAdmin (`/phpmyadmin`)
|
||||||
|
- Admin panels (`/admin`, `/login`)
|
||||||
|
|
||||||
|
### 3. Credential Traps
|
||||||
|
Generates realistic but fake:
|
||||||
|
- Usernames and passwords
|
||||||
|
- API keys and tokens
|
||||||
|
- Database connection strings
|
||||||
|
- AWS credentials
|
||||||
|
|
||||||
|
### 4. Spider Traps
|
||||||
|
Infinite random links to waste automated scanner time
|
||||||
|
|
||||||
|
### 5. Error Simulation
|
||||||
|
Random HTTP errors to appear more realistic
|
||||||
|
|
||||||
|
|
||||||
|
### Custom Canary Token
|
||||||
|
|
||||||
|
Generate a canary token at [canarytokens.org](https://canarytokens.org) and configure:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export CANARY_TOKEN_URL="http://canarytokens.com/..."
|
||||||
|
python3 src/server.py
|
||||||
|
```
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
Contributions welcome! Please:
|
||||||
|
1. Fork the repository
|
||||||
|
2. Create a feature branch
|
||||||
|
3. Make your changes
|
||||||
|
4. Submit a pull request
|
||||||
|
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
|
|
||||||
|
## Disclaimer
|
||||||
|
|
||||||
|
**This is a deception/honeypot system.**
|
||||||
|
Deploy in isolated environments and monitor carefully for security events.
|
||||||
|
Use responsibly and in compliance with applicable laws and regulations.
|
||||||
|
|||||||
60
deployment.yaml
Normal file
60
deployment.yaml
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
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"
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /
|
||||||
|
port: 5000
|
||||||
|
initialDelaySeconds: 10
|
||||||
|
periodSeconds: 30
|
||||||
|
timeoutSeconds: 5
|
||||||
|
failureThreshold: 3
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /
|
||||||
|
port: 5000
|
||||||
|
initialDelaySeconds: 5
|
||||||
|
periodSeconds: 10
|
||||||
|
timeoutSeconds: 5
|
||||||
|
failureThreshold: 3
|
||||||
|
volumes:
|
||||||
|
- name: wordlists
|
||||||
|
configMap:
|
||||||
|
name: krawl-wordlists
|
||||||
33
docker-compose.yaml
Normal file
33
docker-compose.yaml
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
services:
|
||||||
|
krawl:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
container_name: krawl-server
|
||||||
|
ports:
|
||||||
|
- "5000:5000"
|
||||||
|
volumes:
|
||||||
|
- ./wordlists.json:/app/wordlists.json:ro
|
||||||
|
environment:
|
||||||
|
- PORT=5000
|
||||||
|
- DELAY=100
|
||||||
|
- LINKS_MIN_LENGTH=5
|
||||||
|
- LINKS_MAX_LENGTH=15
|
||||||
|
- LINKS_MIN_PER_PAGE=10
|
||||||
|
- LINKS_MAX_PER_PAGE=15
|
||||||
|
- MAX_COUNTER=10
|
||||||
|
- CANARY_TOKEN_TRIES=10
|
||||||
|
- PROBABILITY_ERROR_CODES=0
|
||||||
|
# Optional: Set your canary token URL
|
||||||
|
# - CANARY_TOKEN_URL=http://canarytokens.com/api/users/YOUR_TOKEN/passwords.txt
|
||||||
|
# Optional: Set custom dashboard path (auto-generated if not set)
|
||||||
|
# - DASHBOARD_SECRET_PATH=/my-secret-dashboard
|
||||||
|
restart: unless-stopped
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "python3", "-c", "import requests; requests.get('http://localhost:5000')"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 3
|
||||||
|
start_period: 10s
|
||||||
1
helm/.helmignore
Normal file
1
helm/.helmignore
Normal file
@@ -0,0 +1 @@
|
|||||||
|
.helmignore
|
||||||
15
helm/Chart.yaml
Normal file
15
helm/Chart.yaml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
apiVersion: v2
|
||||||
|
name: krawl-chart
|
||||||
|
description: A Helm chart for Krawl honeypot server
|
||||||
|
type: application
|
||||||
|
version: 0.1.2
|
||||||
|
appVersion: "1.0.0"
|
||||||
|
keywords:
|
||||||
|
- honeypot
|
||||||
|
- security
|
||||||
|
- krawl
|
||||||
|
maintainers:
|
||||||
|
- name: blessedrebus
|
||||||
|
home: https://github.com/blessedrebus/krawl
|
||||||
|
sources:
|
||||||
|
- https://github.com/blessedrebus/krawl
|
||||||
60
helm/NOTES.txt
Normal file
60
helm/NOTES.txt
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
▄▄▄ ▄▄▄ ▄▄▄▄▄▄▄ ▄▄▄▄ ▄▄▄▄ ▄▄▄ ▄▄▄▄ ▄▄▄
|
||||||
|
███ ▄███▀ ███▀▀███▄ ▄██▀▀██▄ ▀███ ███ ███▀ ███
|
||||||
|
███████ ███▄▄███▀ ███ ███ ███ ███ ███ ███
|
||||||
|
███▀███▄ ███▀▀██▄ ███▀▀███ ███▄▄███▄▄███ ███
|
||||||
|
███ ▀███ ███ ▀███ ███ ███ ▀████▀████▀ ████████
|
||||||
|
|
|
||||||
|
|
|
||||||
|
|
|
||||||
|
|
|
||||||
|
|
|
||||||
|
|| ||
|
||||||
|
\\(_)//
|
||||||
|
//(___)\\
|
||||||
|
|| ||
|
||||||
|
|
||||||
|
WARNING: This is a krawl/honeypot service. Monitor access logs for security events.
|
||||||
|
|
||||||
|
For more information, visit: https://github.com/blessedrebus/krawl
|
||||||
|
|
||||||
|
Your krawl honeypot server has been deployed successfully.
|
||||||
|
|
||||||
|
{{- if .Values.service.type }}
|
||||||
|
|
||||||
|
Service Type: {{ .Values.service.type }}
|
||||||
|
{{- if eq .Values.service.type "LoadBalancer" }}
|
||||||
|
|
||||||
|
To get the LoadBalancer IP address, run:
|
||||||
|
kubectl get svc {{ include "krawl.fullname" . }} -n {{ .Release.Namespace }}
|
||||||
|
|
||||||
|
Once the EXTERNAL-IP is assigned, access your krawl server at:
|
||||||
|
http://<EXTERNAL-IP>:{{ .Values.service.port }}
|
||||||
|
{{- else if eq .Values.service.type "NodePort" }}
|
||||||
|
|
||||||
|
To get the NodePort, run:
|
||||||
|
export NODE_PORT=$(kubectl get svc {{ include "krawl.fullname" . }} -n {{ .Release.Namespace }} -o jsonpath='{.spec.ports[0].nodePort}')
|
||||||
|
export NODE_IP=$(kubectl get nodes -o jsonpath='{.items[0].status.addresses[0].address}')
|
||||||
|
echo "Access at: http://$NODE_IP:$NODE_PORT"
|
||||||
|
{{- else if eq .Values.service.type "ClusterIP" }}
|
||||||
|
|
||||||
|
To access the service from your local machine:
|
||||||
|
kubectl port-forward svc/{{ include "krawl.fullname" . }} {{ .Values.service.port }}:{{ .Values.service.port }} -n {{ .Release.Namespace }}
|
||||||
|
|
||||||
|
Then access at: http://localhost:{{ .Values.service.port }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
Dashboard Access:
|
||||||
|
To retrieve the dashboard path, run:
|
||||||
|
kubectl get secret {{ include "krawl.fullname" . }} -n {{ .Release.Namespace }} -o jsonpath='{.data.dashboard-path}' | base64 -d && echo
|
||||||
|
|
||||||
|
Then access the dashboard at:
|
||||||
|
http://<EXTERNAL-IP>:{{ .Values.service.port }}/<dashboard-path>
|
||||||
|
|
||||||
|
{{- if .Values.ingress.enabled }}
|
||||||
|
|
||||||
|
Ingress is ENABLED. Your service will be available at:
|
||||||
|
{{- range .Values.ingress.hosts }}
|
||||||
|
- http{{ if $.Values.ingress.tls }}s{{ end }}://{{ .host }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
60
helm/templates/NOTES.txt
Normal file
60
helm/templates/NOTES.txt
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
▄▄▄ ▄▄▄ ▄▄▄▄▄▄▄ ▄▄▄▄ ▄▄▄▄ ▄▄▄ ▄▄▄▄ ▄▄▄
|
||||||
|
███ ▄███▀ ███▀▀███▄ ▄██▀▀██▄ ▀███ ███ ███▀ ███
|
||||||
|
███████ ███▄▄███▀ ███ ███ ███ ███ ███ ███
|
||||||
|
███▀███▄ ███▀▀██▄ ███▀▀███ ███▄▄███▄▄███ ███
|
||||||
|
███ ▀███ ███ ▀███ ███ ███ ▀████▀████▀ ████████
|
||||||
|
|
|
||||||
|
|
|
||||||
|
|
|
||||||
|
|
|
||||||
|
|
|
||||||
|
|| ||
|
||||||
|
\\(_)//
|
||||||
|
//(___)\\
|
||||||
|
|| ||
|
||||||
|
|
||||||
|
WARNING: This is a deception/honeypot service. Monitor access logs for security events.
|
||||||
|
|
||||||
|
For more information, visit: https://github.com/blessedrebus/deception
|
||||||
|
|
||||||
|
Your deception honeypot server has been deployed successfully.
|
||||||
|
|
||||||
|
{{- if .Values.service.type }}
|
||||||
|
|
||||||
|
Service Type: {{ .Values.service.type }}
|
||||||
|
{{- if eq .Values.service.type "LoadBalancer" }}
|
||||||
|
|
||||||
|
To get the LoadBalancer IP address, run:
|
||||||
|
kubectl get svc {{ include "krawl.fullname" . }} -n {{ .Release.Namespace }}
|
||||||
|
|
||||||
|
Once the EXTERNAL-IP is assigned, access your deception server at:
|
||||||
|
http://<EXTERNAL-IP>:{{ .Values.service.port }}
|
||||||
|
{{- else if eq .Values.service.type "NodePort" }}
|
||||||
|
|
||||||
|
To get the NodePort, run:
|
||||||
|
export NODE_PORT=$(kubectl get svc {{ include "krawl.fullname" . }} -n {{ .Release.Namespace }} -o jsonpath='{.spec.ports[0].nodePort}')
|
||||||
|
export NODE_IP=$(kubectl get nodes -o jsonpath='{.items[0].status.addresses[0].address}')
|
||||||
|
echo "Access at: http://$NODE_IP:$NODE_PORT"
|
||||||
|
{{- else if eq .Values.service.type "ClusterIP" }}
|
||||||
|
|
||||||
|
To access the service from your local machine:
|
||||||
|
kubectl port-forward svc/{{ include "krawl.fullname" . }} {{ .Values.service.port }}:{{ .Values.service.port }} -n {{ .Release.Namespace }}
|
||||||
|
|
||||||
|
Then access at: http://localhost:{{ .Values.service.port }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
Dashboard Access:
|
||||||
|
To retrieve the dashboard path, run:
|
||||||
|
kubectl get secret {{ include "krawl.fullname" . }} -n {{ .Release.Namespace }} -o jsonpath='{.data.dashboard-path}' | base64 -d && echo
|
||||||
|
|
||||||
|
Then access the dashboard at:
|
||||||
|
http://<EXTERNAL-IP>:{{ .Values.service.port }}/<dashboard-path>
|
||||||
|
|
||||||
|
{{- if .Values.ingress.enabled }}
|
||||||
|
|
||||||
|
Ingress is ENABLED. Your service will be available at:
|
||||||
|
{{- range .Values.ingress.hosts }}
|
||||||
|
- http{{ if $.Values.ingress.tls }}s{{ end }}://{{ .host }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
60
helm/templates/_helpers.tpl
Normal file
60
helm/templates/_helpers.tpl
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
{{/*
|
||||||
|
Expand the name of the chart.
|
||||||
|
*/}}
|
||||||
|
{{- define "krawl.name" -}}
|
||||||
|
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Create a default fully qualified app name.
|
||||||
|
*/}}
|
||||||
|
{{- define "krawl.fullname" -}}
|
||||||
|
{{- if .Values.fullnameOverride }}
|
||||||
|
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
|
||||||
|
{{- else }}
|
||||||
|
{{- $name := default .Chart.Name .Values.nameOverride }}
|
||||||
|
{{- if contains $name .Release.Name }}
|
||||||
|
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
|
||||||
|
{{- else }}
|
||||||
|
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Create chart name and version as used by the chart label.
|
||||||
|
*/}}
|
||||||
|
{{- define "krawl.chart" -}}
|
||||||
|
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Common labels
|
||||||
|
*/}}
|
||||||
|
{{- define "krawl.labels" -}}
|
||||||
|
helm.sh/chart: {{ include "krawl.chart" . }}
|
||||||
|
{{ include "krawl.selectorLabels" . }}
|
||||||
|
{{- if .Chart.AppVersion }}
|
||||||
|
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
|
||||||
|
{{- end }}
|
||||||
|
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Selector labels
|
||||||
|
*/}}
|
||||||
|
{{- define "krawl.selectorLabels" -}}
|
||||||
|
app.kubernetes.io/name: {{ include "krawl.name" . }}
|
||||||
|
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{/*
|
||||||
|
Create the name of the service account to use
|
||||||
|
*/}}
|
||||||
|
{{- define "krawl.serviceAccountName" -}}
|
||||||
|
{{- if .Values.serviceAccount.create }}
|
||||||
|
{{- default (include "krawl.fullname" .) .Values.serviceAccount.name }}
|
||||||
|
{{- else }}
|
||||||
|
{{- default "default" .Values.serviceAccount.name }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
17
helm/templates/configmap.yaml
Normal file
17
helm/templates/configmap.yaml
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: {{ include "krawl.fullname" . }}-config
|
||||||
|
labels:
|
||||||
|
{{- include "krawl.labels" . | nindent 4 }}
|
||||||
|
data:
|
||||||
|
PORT: {{ .Values.config.port | quote }}
|
||||||
|
DELAY: {{ .Values.config.delay | quote }}
|
||||||
|
LINKS_MIN_LENGTH: {{ .Values.config.linksMinLength | quote }}
|
||||||
|
LINKS_MAX_LENGTH: {{ .Values.config.linksMaxLength | quote }}
|
||||||
|
LINKS_MIN_PER_PAGE: {{ .Values.config.linksMinPerPage | quote }}
|
||||||
|
LINKS_MAX_PER_PAGE: {{ .Values.config.linksMaxPerPage | quote }}
|
||||||
|
MAX_COUNTER: {{ .Values.config.maxCounter | quote }}
|
||||||
|
CANARY_TOKEN_TRIES: {{ .Values.config.canaryTokenTries | quote }}
|
||||||
|
PROBABILITY_ERROR_CODES: {{ .Values.config.probabilityErrorCodes | quote }}
|
||||||
|
CANARY_TOKEN_URL: {{ .Values.config.canaryTokenUrl | quote }}
|
||||||
84
helm/templates/deployment.yaml
Normal file
84
helm/templates/deployment.yaml
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: {{ include "krawl.fullname" . }}
|
||||||
|
labels:
|
||||||
|
{{- include "krawl.labels" . | nindent 4 }}
|
||||||
|
spec:
|
||||||
|
{{- if not .Values.autoscaling.enabled }}
|
||||||
|
replicas: {{ .Values.replicaCount }}
|
||||||
|
{{- end }}
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
{{- include "krawl.selectorLabels" . | nindent 6 }}
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
{{- with .Values.podAnnotations }}
|
||||||
|
annotations:
|
||||||
|
{{- toYaml . | nindent 8 }}
|
||||||
|
{{- end }}
|
||||||
|
labels:
|
||||||
|
{{- include "krawl.selectorLabels" . | nindent 8 }}
|
||||||
|
spec:
|
||||||
|
{{- with .Values.imagePullSecrets }}
|
||||||
|
imagePullSecrets:
|
||||||
|
{{- toYaml . | nindent 8 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- with .Values.podSecurityContext }}
|
||||||
|
securityContext:
|
||||||
|
{{- toYaml . | nindent 8 }}
|
||||||
|
{{- end }}
|
||||||
|
containers:
|
||||||
|
- name: {{ .Chart.Name }}
|
||||||
|
{{- with .Values.securityContext }}
|
||||||
|
securityContext:
|
||||||
|
{{- toYaml . | nindent 12 }}
|
||||||
|
{{- end }}
|
||||||
|
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
|
||||||
|
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
containerPort: {{ .Values.config.port }}
|
||||||
|
protocol: TCP
|
||||||
|
envFrom:
|
||||||
|
- configMapRef:
|
||||||
|
name: {{ include "krawl.fullname" . }}-config
|
||||||
|
env:
|
||||||
|
- name: DASHBOARD_SECRET_PATH
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: {{ include "krawl.fullname" . }}
|
||||||
|
key: dashboard-path
|
||||||
|
volumeMounts:
|
||||||
|
- name: wordlists
|
||||||
|
mountPath: /app/wordlists.json
|
||||||
|
subPath: wordlists.json
|
||||||
|
readOnly: true
|
||||||
|
{{- with .Values.livenessProbe }}
|
||||||
|
livenessProbe:
|
||||||
|
{{- toYaml . | nindent 12 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- with .Values.readinessProbe }}
|
||||||
|
readinessProbe:
|
||||||
|
{{- toYaml . | nindent 12 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- with .Values.resources }}
|
||||||
|
resources:
|
||||||
|
{{- toYaml . | nindent 12 }}
|
||||||
|
{{- end }}
|
||||||
|
volumes:
|
||||||
|
- name: wordlists
|
||||||
|
configMap:
|
||||||
|
name: {{ include "krawl.fullname" . }}-wordlists
|
||||||
|
{{- with .Values.nodeSelector }}
|
||||||
|
nodeSelector:
|
||||||
|
{{- toYaml . | nindent 8 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- with .Values.affinity }}
|
||||||
|
affinity:
|
||||||
|
{{- toYaml . | nindent 8 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- with .Values.tolerations }}
|
||||||
|
tolerations:
|
||||||
|
{{- toYaml . | nindent 8 }}
|
||||||
|
{{- end }}
|
||||||
32
helm/templates/hpa.yaml
Normal file
32
helm/templates/hpa.yaml
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
{{- if .Values.autoscaling.enabled }}
|
||||||
|
apiVersion: autoscaling/v2
|
||||||
|
kind: HorizontalPodAutoscaler
|
||||||
|
metadata:
|
||||||
|
name: {{ include "krawl.fullname" . }}
|
||||||
|
labels:
|
||||||
|
{{- include "krawl.labels" . | nindent 4 }}
|
||||||
|
spec:
|
||||||
|
scaleTargetRef:
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
name: {{ include "krawl.fullname" . }}
|
||||||
|
minReplicas: {{ .Values.autoscaling.minReplicas }}
|
||||||
|
maxReplicas: {{ .Values.autoscaling.maxReplicas }}
|
||||||
|
metrics:
|
||||||
|
{{- if .Values.autoscaling.targetCPUUtilizationPercentage }}
|
||||||
|
- type: Resource
|
||||||
|
resource:
|
||||||
|
name: cpu
|
||||||
|
target:
|
||||||
|
type: Utilization
|
||||||
|
averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if .Values.autoscaling.targetMemoryUtilizationPercentage }}
|
||||||
|
- type: Resource
|
||||||
|
resource:
|
||||||
|
name: memory
|
||||||
|
target:
|
||||||
|
type: Utilization
|
||||||
|
averageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
42
helm/templates/ingress.yaml
Normal file
42
helm/templates/ingress.yaml
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
{{- if .Values.ingress.enabled -}}
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
name: {{ include "krawl.fullname" . }}
|
||||||
|
namespace: {{ .Release.Namespace }}
|
||||||
|
labels:
|
||||||
|
{{- include "krawl.labels" . | nindent 4 }}
|
||||||
|
{{- with .Values.ingress.annotations }}
|
||||||
|
annotations:
|
||||||
|
{{- toYaml . | nindent 4 }}
|
||||||
|
{{- end }}
|
||||||
|
spec:
|
||||||
|
{{- if .Values.ingress.className }}
|
||||||
|
ingressClassName: {{ .Values.ingress.className }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if .Values.ingress.tls }}
|
||||||
|
tls:
|
||||||
|
{{- range .Values.ingress.tls }}
|
||||||
|
- hosts:
|
||||||
|
{{- range .hosts }}
|
||||||
|
- {{ . | quote }}
|
||||||
|
{{- end }}
|
||||||
|
secretName: {{ .secretName }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
rules:
|
||||||
|
{{- range .Values.ingress.hosts }}
|
||||||
|
- host: {{ .host | quote }}
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
{{- range .paths }}
|
||||||
|
- path: {{ .path }}
|
||||||
|
pathType: {{ .pathType }}
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: {{ include "krawl.fullname" $ }}
|
||||||
|
port:
|
||||||
|
number: {{ $.Values.service.port }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
24
helm/templates/network-policy.yaml
Normal file
24
helm/templates/network-policy.yaml
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
{{- if .Values.networkPolicy.enabled -}}
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: NetworkPolicy
|
||||||
|
metadata:
|
||||||
|
name: {{ include "krawl.fullname" . }}
|
||||||
|
labels:
|
||||||
|
{{- include "krawl.labels" . | nindent 4 }}
|
||||||
|
spec:
|
||||||
|
podSelector:
|
||||||
|
matchLabels:
|
||||||
|
{{- include "krawl.selectorLabels" . | nindent 6 }}
|
||||||
|
{{- with .Values.networkPolicy.policyTypes }}
|
||||||
|
policyTypes:
|
||||||
|
{{- toYaml . | nindent 4 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- with .Values.networkPolicy.ingress }}
|
||||||
|
ingress:
|
||||||
|
{{- toYaml . | nindent 4 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- with .Values.networkPolicy.egress }}
|
||||||
|
egress:
|
||||||
|
{{- toYaml . | nindent 4 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
16
helm/templates/secret.yaml
Normal file
16
helm/templates/secret.yaml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
{{- $secret := (lookup "v1" "Secret" .Release.Namespace (include "krawl.fullname" .)) -}}
|
||||||
|
{{- $dashboardPath := "" -}}
|
||||||
|
{{- if and $secret $secret.data -}}
|
||||||
|
{{- $dashboardPath = index $secret.data "dashboard-path" | b64dec -}}
|
||||||
|
{{- else -}}
|
||||||
|
{{- $dashboardPath = printf "/%s" (randAlphaNum 32) -}}
|
||||||
|
{{- end -}}
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: {{ include "krawl.fullname" . }}
|
||||||
|
labels:
|
||||||
|
{{- include "krawl.labels" . | nindent 4 }}
|
||||||
|
type: Opaque
|
||||||
|
stringData:
|
||||||
|
dashboard-path: {{ $dashboardPath | quote }}
|
||||||
26
helm/templates/service.yaml
Normal file
26
helm/templates/service.yaml
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: {{ include "krawl.fullname" . }}
|
||||||
|
labels:
|
||||||
|
{{- include "krawl.labels" . | nindent 4 }}
|
||||||
|
{{- with .Values.service.annotations }}
|
||||||
|
annotations:
|
||||||
|
{{- toYaml . | nindent 4 }}
|
||||||
|
{{- end }}
|
||||||
|
spec:
|
||||||
|
type: {{ .Values.service.type }}
|
||||||
|
{{- if .Values.service.externalTrafficPolicy }}
|
||||||
|
externalTrafficPolicy: {{ .Values.service.externalTrafficPolicy }}
|
||||||
|
{{- end }}
|
||||||
|
sessionAffinity: ClientIP
|
||||||
|
sessionAffinityConfig:
|
||||||
|
clientIP:
|
||||||
|
timeoutSeconds: 10800
|
||||||
|
ports:
|
||||||
|
- port: {{ .Values.service.port }}
|
||||||
|
targetPort: http
|
||||||
|
protocol: TCP
|
||||||
|
name: http
|
||||||
|
selector:
|
||||||
|
{{- include "krawl.selectorLabels" . | nindent 4 }}
|
||||||
9
helm/templates/wordlists-configmap.yaml
Normal file
9
helm/templates/wordlists-configmap.yaml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: {{ include "krawl.fullname" . }}-wordlists
|
||||||
|
labels:
|
||||||
|
{{- include "krawl.labels" . | nindent 4 }}
|
||||||
|
data:
|
||||||
|
wordlists.json: |
|
||||||
|
{{- .Values.wordlists | toJson | nindent 4 }}
|
||||||
295
helm/values.yaml
Normal file
295
helm/values.yaml
Normal file
@@ -0,0 +1,295 @@
|
|||||||
|
replicaCount: 1
|
||||||
|
|
||||||
|
image:
|
||||||
|
repository: ghcr.io/blessedrebus/krawl
|
||||||
|
pullPolicy: Always
|
||||||
|
tag: "latest"
|
||||||
|
|
||||||
|
imagePullSecrets: []
|
||||||
|
nameOverride: "krawl"
|
||||||
|
fullnameOverride: ""
|
||||||
|
|
||||||
|
serviceAccount:
|
||||||
|
create: false
|
||||||
|
annotations: {}
|
||||||
|
name: ""
|
||||||
|
|
||||||
|
podAnnotations: {}
|
||||||
|
|
||||||
|
podSecurityContext: {}
|
||||||
|
|
||||||
|
securityContext: {}
|
||||||
|
|
||||||
|
service:
|
||||||
|
type: LoadBalancer
|
||||||
|
port: 5000
|
||||||
|
annotations: {}
|
||||||
|
# Preserve source IP when using LoadBalancer
|
||||||
|
externalTrafficPolicy: Local
|
||||||
|
|
||||||
|
ingress:
|
||||||
|
enabled: true
|
||||||
|
className: "nginx"
|
||||||
|
annotations: {}
|
||||||
|
hosts:
|
||||||
|
- host: krawl.example.com
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
||||||
|
tls: []
|
||||||
|
# - secretName: krawl-tls
|
||||||
|
# hosts:
|
||||||
|
# - krawl.example.com
|
||||||
|
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
cpu: 500m
|
||||||
|
memory: 256Mi
|
||||||
|
requests:
|
||||||
|
cpu: 100m
|
||||||
|
memory: 64Mi
|
||||||
|
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /
|
||||||
|
port: http
|
||||||
|
initialDelaySeconds: 10
|
||||||
|
periodSeconds: 30
|
||||||
|
timeoutSeconds: 5
|
||||||
|
failureThreshold: 3
|
||||||
|
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /
|
||||||
|
port: http
|
||||||
|
initialDelaySeconds: 5
|
||||||
|
periodSeconds: 10
|
||||||
|
timeoutSeconds: 5
|
||||||
|
failureThreshold: 3
|
||||||
|
|
||||||
|
autoscaling:
|
||||||
|
enabled: false
|
||||||
|
minReplicas: 1
|
||||||
|
maxReplicas: 5
|
||||||
|
targetCPUUtilizationPercentage: 70
|
||||||
|
targetMemoryUtilizationPercentage: 80
|
||||||
|
|
||||||
|
nodeSelector: {}
|
||||||
|
|
||||||
|
tolerations: []
|
||||||
|
|
||||||
|
affinity: {}
|
||||||
|
|
||||||
|
# Application configuration
|
||||||
|
config:
|
||||||
|
port: 5000
|
||||||
|
delay: 100
|
||||||
|
linksMinLength: 5
|
||||||
|
linksMaxLength: 15
|
||||||
|
linksMinPerPage: 10
|
||||||
|
linksMaxPerPage: 15
|
||||||
|
maxCounter: 10
|
||||||
|
canaryTokenTries: 10
|
||||||
|
probabilityErrorCodes: 0
|
||||||
|
# canaryTokenUrl: set-your-canary-token-url-here
|
||||||
|
|
||||||
|
networkPolicy:
|
||||||
|
enabled: true
|
||||||
|
policyTypes:
|
||||||
|
- Ingress
|
||||||
|
- Egress
|
||||||
|
ingress:
|
||||||
|
- from:
|
||||||
|
- podSelector: {}
|
||||||
|
- namespaceSelector: {}
|
||||||
|
- ipBlock:
|
||||||
|
cidr: 0.0.0.0/0
|
||||||
|
ports:
|
||||||
|
- protocol: TCP
|
||||||
|
port: 5000
|
||||||
|
egress:
|
||||||
|
- to:
|
||||||
|
- namespaceSelector: {}
|
||||||
|
- ipBlock:
|
||||||
|
cidr: 0.0.0.0/0
|
||||||
|
ports:
|
||||||
|
- protocol: TCP
|
||||||
|
- protocol: UDP
|
||||||
|
|
||||||
|
# Wordlists configuration
|
||||||
|
wordlists:
|
||||||
|
usernames:
|
||||||
|
prefixes:
|
||||||
|
- admin
|
||||||
|
- user
|
||||||
|
- developer
|
||||||
|
- root
|
||||||
|
- system
|
||||||
|
- db
|
||||||
|
- api
|
||||||
|
- service
|
||||||
|
- deploy
|
||||||
|
- test
|
||||||
|
- prod
|
||||||
|
- backup
|
||||||
|
- monitor
|
||||||
|
- jenkins
|
||||||
|
- webapp
|
||||||
|
suffixes:
|
||||||
|
- ""
|
||||||
|
- "_prod"
|
||||||
|
- "_dev"
|
||||||
|
- "_test"
|
||||||
|
- "123"
|
||||||
|
- "2024"
|
||||||
|
- "_backup"
|
||||||
|
- "_admin"
|
||||||
|
- "01"
|
||||||
|
- "02"
|
||||||
|
- "_user"
|
||||||
|
- "_service"
|
||||||
|
- "_api"
|
||||||
|
passwords:
|
||||||
|
prefixes:
|
||||||
|
- P@ssw0rd
|
||||||
|
- Passw0rd
|
||||||
|
- Admin
|
||||||
|
- Secret
|
||||||
|
- Welcome
|
||||||
|
- System
|
||||||
|
- Database
|
||||||
|
- Secure
|
||||||
|
- Master
|
||||||
|
- Root
|
||||||
|
simple:
|
||||||
|
- test
|
||||||
|
- demo
|
||||||
|
- temp
|
||||||
|
- change
|
||||||
|
- password
|
||||||
|
- admin
|
||||||
|
- letmein
|
||||||
|
- welcome
|
||||||
|
- default
|
||||||
|
- sample
|
||||||
|
emails:
|
||||||
|
domains:
|
||||||
|
- example.com
|
||||||
|
- company.com
|
||||||
|
- localhost.com
|
||||||
|
- test.com
|
||||||
|
- domain.com
|
||||||
|
- corporate.com
|
||||||
|
- internal.net
|
||||||
|
- enterprise.com
|
||||||
|
- business.org
|
||||||
|
api_keys:
|
||||||
|
prefixes:
|
||||||
|
- sk_live_
|
||||||
|
- sk_test_
|
||||||
|
- api_
|
||||||
|
- key_
|
||||||
|
- token_
|
||||||
|
- access_
|
||||||
|
- secret_
|
||||||
|
- prod_
|
||||||
|
- ""
|
||||||
|
databases:
|
||||||
|
names:
|
||||||
|
- production
|
||||||
|
- prod_db
|
||||||
|
- main_db
|
||||||
|
- app_database
|
||||||
|
- users_db
|
||||||
|
- customer_data
|
||||||
|
- analytics
|
||||||
|
- staging_db
|
||||||
|
- dev_database
|
||||||
|
- wordpress
|
||||||
|
- ecommerce
|
||||||
|
- crm_db
|
||||||
|
- inventory
|
||||||
|
hosts:
|
||||||
|
- localhost
|
||||||
|
- db.internal
|
||||||
|
- mysql.local
|
||||||
|
- postgres.internal
|
||||||
|
- 127.0.0.1
|
||||||
|
- db-server-01
|
||||||
|
- database.prod
|
||||||
|
- sql.company.com
|
||||||
|
applications:
|
||||||
|
names:
|
||||||
|
- WebApp
|
||||||
|
- API Gateway
|
||||||
|
- Dashboard
|
||||||
|
- Admin Panel
|
||||||
|
- CMS
|
||||||
|
- Portal
|
||||||
|
- Manager
|
||||||
|
- Console
|
||||||
|
- Control Panel
|
||||||
|
- Backend
|
||||||
|
users:
|
||||||
|
roles:
|
||||||
|
- Administrator
|
||||||
|
- Developer
|
||||||
|
- Manager
|
||||||
|
- User
|
||||||
|
- Guest
|
||||||
|
- Moderator
|
||||||
|
- Editor
|
||||||
|
- Viewer
|
||||||
|
- Analyst
|
||||||
|
- Support
|
||||||
|
directory_listing:
|
||||||
|
files:
|
||||||
|
- admin.txt
|
||||||
|
- test.exe
|
||||||
|
- backup.sql
|
||||||
|
- database.sql
|
||||||
|
- db_backup.sql
|
||||||
|
- dump.sql
|
||||||
|
- config.php
|
||||||
|
- credentials.txt
|
||||||
|
- passwords.txt
|
||||||
|
- users.csv
|
||||||
|
- .env
|
||||||
|
- id_rsa
|
||||||
|
- id_rsa.pub
|
||||||
|
- private_key.pem
|
||||||
|
- api_keys.json
|
||||||
|
- secrets.yaml
|
||||||
|
- admin_notes.txt
|
||||||
|
- settings.ini
|
||||||
|
- database.yml
|
||||||
|
- wp-config.php
|
||||||
|
- .htaccess
|
||||||
|
- server.key
|
||||||
|
- cert.pem
|
||||||
|
- shadow.bak
|
||||||
|
- passwd.old
|
||||||
|
directories:
|
||||||
|
- uploads/
|
||||||
|
- backups/
|
||||||
|
- logs/
|
||||||
|
- temp/
|
||||||
|
- cache/
|
||||||
|
- private/
|
||||||
|
- config/
|
||||||
|
- admin/
|
||||||
|
- database/
|
||||||
|
- backup/
|
||||||
|
- old/
|
||||||
|
- archive/
|
||||||
|
- .git/
|
||||||
|
- keys/
|
||||||
|
- credentials/
|
||||||
|
error_codes:
|
||||||
|
- 400
|
||||||
|
- 401
|
||||||
|
- 403
|
||||||
|
- 404
|
||||||
|
- 500
|
||||||
|
- 502
|
||||||
|
- 503
|
||||||
BIN
img/dashboard-1.png
Normal file
BIN
img/dashboard-1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 98 KiB |
BIN
img/dashboard-2.png
Normal file
BIN
img/dashboard-2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 54 KiB |
BIN
img/database.png
Normal file
BIN
img/database.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 72 KiB |
BIN
img/deception-page.png
Normal file
BIN
img/deception-page.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 40 KiB |
16
manifests/configmap.yaml
Normal file
16
manifests/configmap.yaml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: krawl-config
|
||||||
|
namespace: krawl-system
|
||||||
|
data:
|
||||||
|
PORT: "5000"
|
||||||
|
DELAY: "100"
|
||||||
|
LINKS_MIN_LENGTH: "5"
|
||||||
|
LINKS_MAX_LENGTH: "15"
|
||||||
|
LINKS_MIN_PER_PAGE: "10"
|
||||||
|
LINKS_MAX_PER_PAGE: "15"
|
||||||
|
MAX_COUNTER: "10"
|
||||||
|
CANARY_TOKEN_TRIES: "10"
|
||||||
|
PROBABILITY_ERROR_CODES: "0"
|
||||||
|
# CANARY_TOKEN_URL: set-your-canary-token-url-here
|
||||||
60
manifests/deployment.yaml
Normal file
60
manifests/deployment.yaml
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: krawl-server
|
||||||
|
namespace: krawl-system
|
||||||
|
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"
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /
|
||||||
|
port: 5000
|
||||||
|
initialDelaySeconds: 10
|
||||||
|
periodSeconds: 30
|
||||||
|
timeoutSeconds: 5
|
||||||
|
failureThreshold: 3
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /
|
||||||
|
port: 5000
|
||||||
|
initialDelaySeconds: 5
|
||||||
|
periodSeconds: 10
|
||||||
|
timeoutSeconds: 5
|
||||||
|
failureThreshold: 3
|
||||||
|
volumes:
|
||||||
|
- name: wordlists
|
||||||
|
configMap:
|
||||||
|
name: krawl-wordlists
|
||||||
26
manifests/hpa.yaml
Normal file
26
manifests/hpa.yaml
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
# Optional: HorizontalPodAutoscaler for auto-scaling
|
||||||
|
apiVersion: autoscaling/v2
|
||||||
|
kind: HorizontalPodAutoscaler
|
||||||
|
metadata:
|
||||||
|
name: krawl-hpa
|
||||||
|
namespace: krawl-system
|
||||||
|
spec:
|
||||||
|
scaleTargetRef:
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
name: krawl-server
|
||||||
|
minReplicas: 1
|
||||||
|
maxReplicas: 5
|
||||||
|
metrics:
|
||||||
|
- type: Resource
|
||||||
|
resource:
|
||||||
|
name: cpu
|
||||||
|
target:
|
||||||
|
type: Utilization
|
||||||
|
averageUtilization: 70
|
||||||
|
- type: Resource
|
||||||
|
resource:
|
||||||
|
name: memory
|
||||||
|
target:
|
||||||
|
type: Utilization
|
||||||
|
averageUtilization: 80
|
||||||
24
manifests/ingress.yaml
Normal file
24
manifests/ingress.yaml
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
name: krawl-ingress
|
||||||
|
namespace: krawl-system
|
||||||
|
annotations:
|
||||||
|
nginx.ingress.kubernetes.io/rewrite-target: /
|
||||||
|
spec:
|
||||||
|
ingressClassName: nginx
|
||||||
|
rules:
|
||||||
|
- host: krawl.example.com # Change to your domain
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: krawl-server
|
||||||
|
port:
|
||||||
|
number: 5000
|
||||||
|
# tls:
|
||||||
|
# - hosts:
|
||||||
|
# - krawl.example.com
|
||||||
|
# secretName: krawl-tls
|
||||||
14
manifests/kustomization.yaml
Normal file
14
manifests/kustomization.yaml
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
|
||||||
|
resources:
|
||||||
|
- namespace.yaml
|
||||||
|
- configmap.yaml
|
||||||
|
- wordlists-configmap.yaml
|
||||||
|
- deployment.yaml
|
||||||
|
- service.yaml
|
||||||
|
- network-policy.yaml
|
||||||
|
- ingress.yaml
|
||||||
|
- hpa.yaml
|
||||||
|
|
||||||
|
namespace: krawl-system
|
||||||
4
manifests/namespace.yaml
Normal file
4
manifests/namespace.yaml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
name: krawl-system
|
||||||
29
manifests/network-policy.yaml
Normal file
29
manifests/network-policy.yaml
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: NetworkPolicy
|
||||||
|
metadata:
|
||||||
|
name: krawl-network-policy
|
||||||
|
namespace: krawl-system
|
||||||
|
spec:
|
||||||
|
podSelector:
|
||||||
|
matchLabels:
|
||||||
|
app: krawl-server
|
||||||
|
policyTypes:
|
||||||
|
- Ingress
|
||||||
|
- Egress
|
||||||
|
ingress:
|
||||||
|
- from:
|
||||||
|
- podSelector: {}
|
||||||
|
- namespaceSelector: {}
|
||||||
|
- ipBlock:
|
||||||
|
cidr: 0.0.0.0/0
|
||||||
|
ports:
|
||||||
|
- protocol: TCP
|
||||||
|
port: 5000
|
||||||
|
egress:
|
||||||
|
- to:
|
||||||
|
- namespaceSelector: {}
|
||||||
|
- ipBlock:
|
||||||
|
cidr: 0.0.0.0/0
|
||||||
|
ports:
|
||||||
|
- protocol: TCP
|
||||||
|
- protocol: UDP
|
||||||
16
manifests/service.yaml
Normal file
16
manifests/service.yaml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: krawl-server
|
||||||
|
namespace: krawl-system
|
||||||
|
labels:
|
||||||
|
app: krawl-server
|
||||||
|
spec:
|
||||||
|
type: LoadBalancer
|
||||||
|
ports:
|
||||||
|
- port: 5000
|
||||||
|
targetPort: 5000
|
||||||
|
protocol: TCP
|
||||||
|
name: http
|
||||||
|
selector:
|
||||||
|
app: krawl-server
|
||||||
205
manifests/wordlists-configmap.yaml
Normal file
205
manifests/wordlists-configmap.yaml
Normal file
@@ -0,0 +1,205 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: krawl-wordlists
|
||||||
|
namespace: krawl-system
|
||||||
|
data:
|
||||||
|
wordlists.json: |
|
||||||
|
{
|
||||||
|
"usernames": {
|
||||||
|
"prefixes": [
|
||||||
|
"admin",
|
||||||
|
"user",
|
||||||
|
"developer",
|
||||||
|
"root",
|
||||||
|
"system",
|
||||||
|
"db",
|
||||||
|
"api",
|
||||||
|
"service",
|
||||||
|
"deploy",
|
||||||
|
"test",
|
||||||
|
"prod",
|
||||||
|
"backup",
|
||||||
|
"monitor",
|
||||||
|
"jenkins",
|
||||||
|
"webapp"
|
||||||
|
],
|
||||||
|
"suffixes": [
|
||||||
|
"",
|
||||||
|
"_prod",
|
||||||
|
"_dev",
|
||||||
|
"_test",
|
||||||
|
"123",
|
||||||
|
"2024",
|
||||||
|
"_backup",
|
||||||
|
"_admin",
|
||||||
|
"01",
|
||||||
|
"02",
|
||||||
|
"_user",
|
||||||
|
"_service",
|
||||||
|
"_api"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"passwords": {
|
||||||
|
"prefixes": [
|
||||||
|
"P@ssw0rd",
|
||||||
|
"Passw0rd",
|
||||||
|
"Admin",
|
||||||
|
"Secret",
|
||||||
|
"Welcome",
|
||||||
|
"System",
|
||||||
|
"Database",
|
||||||
|
"Secure",
|
||||||
|
"Master",
|
||||||
|
"Root"
|
||||||
|
],
|
||||||
|
"simple": [
|
||||||
|
"test",
|
||||||
|
"demo",
|
||||||
|
"temp",
|
||||||
|
"change",
|
||||||
|
"password",
|
||||||
|
"admin",
|
||||||
|
"letmein",
|
||||||
|
"welcome",
|
||||||
|
"default",
|
||||||
|
"sample"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"emails": {
|
||||||
|
"domains": [
|
||||||
|
"example.com",
|
||||||
|
"company.com",
|
||||||
|
"localhost.com",
|
||||||
|
"test.com",
|
||||||
|
"domain.com",
|
||||||
|
"corporate.com",
|
||||||
|
"internal.net",
|
||||||
|
"enterprise.com",
|
||||||
|
"business.org"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"api_keys": {
|
||||||
|
"prefixes": [
|
||||||
|
"sk_live_",
|
||||||
|
"sk_test_",
|
||||||
|
"api_",
|
||||||
|
"key_",
|
||||||
|
"token_",
|
||||||
|
"access_",
|
||||||
|
"secret_",
|
||||||
|
"prod_",
|
||||||
|
""
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"databases": {
|
||||||
|
"names": [
|
||||||
|
"production",
|
||||||
|
"prod_db",
|
||||||
|
"main_db",
|
||||||
|
"app_database",
|
||||||
|
"users_db",
|
||||||
|
"customer_data",
|
||||||
|
"analytics",
|
||||||
|
"staging_db",
|
||||||
|
"dev_database",
|
||||||
|
"wordpress",
|
||||||
|
"ecommerce",
|
||||||
|
"crm_db",
|
||||||
|
"inventory"
|
||||||
|
],
|
||||||
|
"hosts": [
|
||||||
|
"localhost",
|
||||||
|
"db.internal",
|
||||||
|
"mysql.local",
|
||||||
|
"postgres.internal",
|
||||||
|
"127.0.0.1",
|
||||||
|
"db-server-01",
|
||||||
|
"database.prod",
|
||||||
|
"sql.company.com"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"applications": {
|
||||||
|
"names": [
|
||||||
|
"WebApp",
|
||||||
|
"API Gateway",
|
||||||
|
"Dashboard",
|
||||||
|
"Admin Panel",
|
||||||
|
"CMS",
|
||||||
|
"Portal",
|
||||||
|
"Manager",
|
||||||
|
"Console",
|
||||||
|
"Control Panel",
|
||||||
|
"Backend"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"users": {
|
||||||
|
"roles": [
|
||||||
|
"Administrator",
|
||||||
|
"Developer",
|
||||||
|
"Manager",
|
||||||
|
"User",
|
||||||
|
"Guest",
|
||||||
|
"Moderator",
|
||||||
|
"Editor",
|
||||||
|
"Viewer",
|
||||||
|
"Analyst",
|
||||||
|
"Support"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"directory_listing": {
|
||||||
|
"files": [
|
||||||
|
"admin.txt",
|
||||||
|
"test.exe",
|
||||||
|
"backup.sql",
|
||||||
|
"database.sql",
|
||||||
|
"db_backup.sql",
|
||||||
|
"dump.sql",
|
||||||
|
"config.php",
|
||||||
|
"credentials.txt",
|
||||||
|
"passwords.txt",
|
||||||
|
"users.csv",
|
||||||
|
".env",
|
||||||
|
"id_rsa",
|
||||||
|
"id_rsa.pub",
|
||||||
|
"private_key.pem",
|
||||||
|
"api_keys.json",
|
||||||
|
"secrets.yaml",
|
||||||
|
"admin_notes.txt",
|
||||||
|
"settings.ini",
|
||||||
|
"database.yml",
|
||||||
|
"wp-config.php",
|
||||||
|
".htaccess",
|
||||||
|
"server.key",
|
||||||
|
"cert.pem",
|
||||||
|
"shadow.bak",
|
||||||
|
"passwd.old"
|
||||||
|
],
|
||||||
|
"directories": [
|
||||||
|
"uploads/",
|
||||||
|
"backups/",
|
||||||
|
"logs/",
|
||||||
|
"temp/",
|
||||||
|
"cache/",
|
||||||
|
"private/",
|
||||||
|
"config/",
|
||||||
|
"admin/",
|
||||||
|
"database/",
|
||||||
|
"backup/",
|
||||||
|
"old/",
|
||||||
|
"archive/",
|
||||||
|
".git/",
|
||||||
|
"keys/",
|
||||||
|
"credentials/"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"error_codes": [
|
||||||
|
400,
|
||||||
|
401,
|
||||||
|
403,
|
||||||
|
404,
|
||||||
|
500,
|
||||||
|
502,
|
||||||
|
503
|
||||||
|
]
|
||||||
|
}
|
||||||
48
src/config.py
Normal file
48
src/config.py
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import os
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import Optional, Tuple
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Config:
|
||||||
|
"""Configuration class for the deception server"""
|
||||||
|
port: int = 5000
|
||||||
|
delay: int = 100 # milliseconds
|
||||||
|
links_length_range: Tuple[int, int] = (5, 15)
|
||||||
|
links_per_page_range: Tuple[int, int] = (10, 15)
|
||||||
|
char_space: str = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
|
||||||
|
max_counter: int = 10
|
||||||
|
canary_token_url: Optional[str] = None
|
||||||
|
canary_token_tries: int = 10
|
||||||
|
dashboard_secret_path: str = None
|
||||||
|
api_server_url: Optional[str] = None
|
||||||
|
api_server_port: int = 8080
|
||||||
|
api_server_path: str = "/api/v2/users"
|
||||||
|
probability_error_codes: int = 0 # Percentage (0-100)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_env(cls) -> 'Config':
|
||||||
|
"""Create configuration from environment variables"""
|
||||||
|
return cls(
|
||||||
|
port=int(os.getenv('PORT', 5000)),
|
||||||
|
delay=int(os.getenv('DELAY', 100)),
|
||||||
|
links_length_range=(
|
||||||
|
int(os.getenv('LINKS_MIN_LENGTH', 5)),
|
||||||
|
int(os.getenv('LINKS_MAX_LENGTH', 15))
|
||||||
|
),
|
||||||
|
links_per_page_range=(
|
||||||
|
int(os.getenv('LINKS_MIN_PER_PAGE', 10)),
|
||||||
|
int(os.getenv('LINKS_MAX_PER_PAGE', 15))
|
||||||
|
),
|
||||||
|
char_space=os.getenv('CHAR_SPACE', 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'),
|
||||||
|
max_counter=int(os.getenv('MAX_COUNTER', 10)),
|
||||||
|
canary_token_url=os.getenv('CANARY_TOKEN_URL'),
|
||||||
|
canary_token_tries=int(os.getenv('CANARY_TOKEN_TRIES', 10)),
|
||||||
|
dashboard_secret_path=os.getenv('DASHBOARD_SECRET_PATH', f'/{os.urandom(16).hex()}'),
|
||||||
|
api_server_url=os.getenv('API_SERVER_URL'),
|
||||||
|
api_server_port=int(os.getenv('API_SERVER_PORT', 8080)),
|
||||||
|
api_server_path=os.getenv('API_SERVER_PATH', '/api/v2/users'),
|
||||||
|
probability_error_codes=int(os.getenv('PROBABILITY_ERROR_CODES', 5))
|
||||||
|
)
|
||||||
214
src/dashboard_template.py
Normal file
214
src/dashboard_template.py
Normal file
@@ -0,0 +1,214 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
"""
|
||||||
|
Dashboard template for viewing honeypot statistics.
|
||||||
|
Customize this template to change the dashboard appearance.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def generate_dashboard(stats: dict) -> str:
|
||||||
|
"""Generate dashboard HTML with access statistics"""
|
||||||
|
|
||||||
|
top_ips_rows = '\n'.join([
|
||||||
|
f'<tr><td class="rank">{i+1}</td><td>{ip}</td><td>{count}</td></tr>'
|
||||||
|
for i, (ip, count) in enumerate(stats['top_ips'])
|
||||||
|
]) or '<tr><td colspan="3" style="text-align:center;">No data</td></tr>'
|
||||||
|
|
||||||
|
# Generate paths rows
|
||||||
|
top_paths_rows = '\n'.join([
|
||||||
|
f'<tr><td class="rank">{i+1}</td><td>{path}</td><td>{count}</td></tr>'
|
||||||
|
for i, (path, count) in enumerate(stats['top_paths'])
|
||||||
|
]) or '<tr><td colspan="3" style="text-align:center;">No data</td></tr>'
|
||||||
|
|
||||||
|
# Generate User-Agent rows
|
||||||
|
top_ua_rows = '\n'.join([
|
||||||
|
f'<tr><td class="rank">{i+1}</td><td style="word-break: break-all;">{ua[:80]}</td><td>{count}</td></tr>'
|
||||||
|
for i, (ua, count) in enumerate(stats['top_user_agents'])
|
||||||
|
]) or '<tr><td colspan="3" style="text-align:center;">No data</td></tr>'
|
||||||
|
|
||||||
|
# Generate suspicious accesses rows
|
||||||
|
suspicious_rows = '\n'.join([
|
||||||
|
f'<tr><td>{log["ip"]}</td><td>{log["path"]}</td><td style="word-break: break-all;">{log["user_agent"][:60]}</td><td>{log["timestamp"].split("T")[1][:8]}</td></tr>'
|
||||||
|
for log in stats['recent_suspicious'][-10:]
|
||||||
|
]) or '<tr><td colspan="4" style="text-align:center;">No suspicious activity detected</td></tr>'
|
||||||
|
|
||||||
|
return f"""<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Krawl Dashboard</title>
|
||||||
|
<style>
|
||||||
|
body {{
|
||||||
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||||
|
background-color: #0d1117;
|
||||||
|
color: #c9d1d9;
|
||||||
|
margin: 0;
|
||||||
|
padding: 20px;
|
||||||
|
}}
|
||||||
|
.container {{
|
||||||
|
max-width: 1400px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}}
|
||||||
|
h1 {{
|
||||||
|
color: #58a6ff;
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 40px;
|
||||||
|
}}
|
||||||
|
.stats-grid {{
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||||
|
gap: 20px;
|
||||||
|
margin-bottom: 40px;
|
||||||
|
}}
|
||||||
|
.stat-card {{
|
||||||
|
background: #161b22;
|
||||||
|
border: 1px solid #30363d;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 20px;
|
||||||
|
text-align: center;
|
||||||
|
}}
|
||||||
|
.stat-card.alert {{
|
||||||
|
border-color: #f85149;
|
||||||
|
}}
|
||||||
|
.stat-value {{
|
||||||
|
font-size: 36px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #58a6ff;
|
||||||
|
}}
|
||||||
|
.stat-value.alert {{
|
||||||
|
color: #f85149;
|
||||||
|
}}
|
||||||
|
.stat-label {{
|
||||||
|
font-size: 14px;
|
||||||
|
color: #8b949e;
|
||||||
|
margin-top: 5px;
|
||||||
|
}}
|
||||||
|
.table-container {{
|
||||||
|
background: #161b22;
|
||||||
|
border: 1px solid #30363d;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 20px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}}
|
||||||
|
h2 {{
|
||||||
|
color: #58a6ff;
|
||||||
|
margin-top: 0;
|
||||||
|
}}
|
||||||
|
table {{
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}}
|
||||||
|
th, td {{
|
||||||
|
padding: 12px;
|
||||||
|
text-align: left;
|
||||||
|
border-bottom: 1px solid #30363d;
|
||||||
|
}}
|
||||||
|
th {{
|
||||||
|
background: #0d1117;
|
||||||
|
color: #58a6ff;
|
||||||
|
font-weight: 600;
|
||||||
|
}}
|
||||||
|
tr:hover {{
|
||||||
|
background: #1c2128;
|
||||||
|
}}
|
||||||
|
.rank {{
|
||||||
|
color: #8b949e;
|
||||||
|
font-weight: bold;
|
||||||
|
}}
|
||||||
|
.alert-section {{
|
||||||
|
background: #1c1917;
|
||||||
|
border-left: 4px solid #f85149;
|
||||||
|
}}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<h1>🕷️ Krawl Dashboard</h1>
|
||||||
|
|
||||||
|
<div class="stats-grid">
|
||||||
|
<div class="stat-card">
|
||||||
|
<div class="stat-value">{stats['total_accesses']}</div>
|
||||||
|
<div class="stat-label">Total Accesses</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-card">
|
||||||
|
<div class="stat-value">{stats['unique_ips']}</div>
|
||||||
|
<div class="stat-label">Unique IPs</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-card">
|
||||||
|
<div class="stat-value">{stats['unique_paths']}</div>
|
||||||
|
<div class="stat-label">Unique Paths</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-card alert">
|
||||||
|
<div class="stat-value alert">{stats['suspicious_accesses']}</div>
|
||||||
|
<div class="stat-label">Suspicious Accesses</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="table-container alert-section">
|
||||||
|
<h2>⚠️ Recent Suspicious Activity</h2>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>IP Address</th>
|
||||||
|
<th>Path</th>
|
||||||
|
<th>User-Agent</th>
|
||||||
|
<th>Time</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{suspicious_rows}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="table-container">
|
||||||
|
<h2>Top IP Addresses</h2>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>#</th>
|
||||||
|
<th>IP Address</th>
|
||||||
|
<th>Access Count</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{top_ips_rows}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="table-container">
|
||||||
|
<h2>Top Paths</h2>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>#</th>
|
||||||
|
<th>Path</th>
|
||||||
|
<th>Access Count</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{top_paths_rows}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="table-container">
|
||||||
|
<h2>Top User-Agents</h2>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>#</th>
|
||||||
|
<th>User-Agent</th>
|
||||||
|
<th>Count</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{top_ua_rows}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
"""
|
||||||
190
src/generators.py
Normal file
190
src/generators.py
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
"""
|
||||||
|
Generators for creating random fake data (credentials, API keys, etc.)
|
||||||
|
"""
|
||||||
|
|
||||||
|
import random
|
||||||
|
import string
|
||||||
|
import json
|
||||||
|
from templates import html_templates
|
||||||
|
from wordlists import get_wordlists
|
||||||
|
|
||||||
|
|
||||||
|
def random_username() -> str:
|
||||||
|
"""Generate random username"""
|
||||||
|
wl = get_wordlists()
|
||||||
|
return random.choice(wl.username_prefixes) + random.choice(wl.username_suffixes)
|
||||||
|
|
||||||
|
|
||||||
|
def random_password() -> str:
|
||||||
|
"""Generate random password"""
|
||||||
|
wl = get_wordlists()
|
||||||
|
templates = [
|
||||||
|
lambda: ''.join(random.choices(string.ascii_letters + string.digits, k=12)),
|
||||||
|
lambda: f"{random.choice(wl.password_prefixes)}{random.randint(100, 999)}!",
|
||||||
|
lambda: f"{random.choice(wl.simple_passwords)}{random.randint(1000, 9999)}",
|
||||||
|
lambda: ''.join(random.choices(string.ascii_lowercase, k=8)),
|
||||||
|
]
|
||||||
|
return random.choice(templates)()
|
||||||
|
|
||||||
|
|
||||||
|
def random_email(username: str = None) -> str:
|
||||||
|
"""Generate random email"""
|
||||||
|
wl = get_wordlists()
|
||||||
|
if not username:
|
||||||
|
username = random_username()
|
||||||
|
return f"{username}@{random.choice(wl.email_domains)}"
|
||||||
|
|
||||||
|
|
||||||
|
def random_api_key() -> str:
|
||||||
|
"""Generate random API key"""
|
||||||
|
wl = get_wordlists()
|
||||||
|
key = ''.join(random.choices(string.ascii_letters + string.digits, k=32))
|
||||||
|
return random.choice(wl.api_key_prefixes) + key
|
||||||
|
|
||||||
|
|
||||||
|
def random_database_name() -> str:
|
||||||
|
"""Generate random database name"""
|
||||||
|
wl = get_wordlists()
|
||||||
|
return random.choice(wl.database_names)
|
||||||
|
|
||||||
|
|
||||||
|
def credentials_txt() -> str:
|
||||||
|
"""Generate fake credentials.txt with random data"""
|
||||||
|
content = "# Production Credentials\n\n"
|
||||||
|
for i in range(random.randint(3, 7)):
|
||||||
|
username = random_username()
|
||||||
|
password = random_password()
|
||||||
|
content += f"{username}:{password}\n"
|
||||||
|
return content
|
||||||
|
|
||||||
|
|
||||||
|
def passwords_txt() -> str:
|
||||||
|
"""Generate fake passwords.txt with random data"""
|
||||||
|
content = "# Password List\n"
|
||||||
|
content += f"Admin Password: {random_password()}\n"
|
||||||
|
content += f"Database Password: {random_password()}\n"
|
||||||
|
content += f"API Key: {random_api_key()}\n\n"
|
||||||
|
content += "User Passwords:\n"
|
||||||
|
for i in range(random.randint(5, 10)):
|
||||||
|
username = random_username()
|
||||||
|
password = random_password()
|
||||||
|
content += f"{username} = {password}\n"
|
||||||
|
return content
|
||||||
|
|
||||||
|
|
||||||
|
def users_json() -> str:
|
||||||
|
"""Generate fake users.json with random data"""
|
||||||
|
wl = get_wordlists()
|
||||||
|
users = []
|
||||||
|
for i in range(random.randint(3, 8)):
|
||||||
|
username = random_username()
|
||||||
|
users.append({
|
||||||
|
"id": i + 1,
|
||||||
|
"username": username,
|
||||||
|
"email": random_email(username),
|
||||||
|
"password": random_password(),
|
||||||
|
"role": random.choice(wl.user_roles),
|
||||||
|
"api_token": random_api_key()
|
||||||
|
})
|
||||||
|
return json.dumps({"users": users}, indent=2)
|
||||||
|
|
||||||
|
|
||||||
|
def api_keys_json() -> str:
|
||||||
|
"""Generate fake api_keys.json with random data"""
|
||||||
|
keys = {
|
||||||
|
"stripe": {
|
||||||
|
"public_key": "pk_live_" + ''.join(random.choices(string.ascii_letters + string.digits, k=24)),
|
||||||
|
"secret_key": random_api_key()
|
||||||
|
},
|
||||||
|
"aws": {
|
||||||
|
"access_key_id": "AKIA" + ''.join(random.choices(string.ascii_uppercase + string.digits, k=16)),
|
||||||
|
"secret_access_key": ''.join(random.choices(string.ascii_letters + string.digits + '+/', k=40))
|
||||||
|
},
|
||||||
|
"sendgrid": {
|
||||||
|
"api_key": "SG." + ''.join(random.choices(string.ascii_letters + string.digits, k=48))
|
||||||
|
},
|
||||||
|
"twilio": {
|
||||||
|
"account_sid": "AC" + ''.join(random.choices(string.ascii_lowercase + string.digits, k=32)),
|
||||||
|
"auth_token": ''.join(random.choices(string.ascii_lowercase + string.digits, k=32))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return json.dumps(keys, indent=2)
|
||||||
|
|
||||||
|
|
||||||
|
def api_response(path: str) -> str:
|
||||||
|
"""Generate fake API JSON responses with random data"""
|
||||||
|
wl = get_wordlists()
|
||||||
|
|
||||||
|
def random_users(count: int = 3):
|
||||||
|
users = []
|
||||||
|
for i in range(count):
|
||||||
|
username = random_username()
|
||||||
|
users.append({
|
||||||
|
"id": i + 1,
|
||||||
|
"username": username,
|
||||||
|
"email": random_email(username),
|
||||||
|
"role": random.choice(wl.user_roles)
|
||||||
|
})
|
||||||
|
return users
|
||||||
|
|
||||||
|
responses = {
|
||||||
|
'/api/users': json.dumps({
|
||||||
|
"users": random_users(random.randint(2, 5)),
|
||||||
|
"total": random.randint(50, 500)
|
||||||
|
}, indent=2),
|
||||||
|
'/api/v1/users': json.dumps({
|
||||||
|
"status": "success",
|
||||||
|
"data": [{
|
||||||
|
"id": random.randint(1, 100),
|
||||||
|
"name": random_username(),
|
||||||
|
"api_key": random_api_key()
|
||||||
|
}]
|
||||||
|
}, indent=2),
|
||||||
|
'/api/v2/secrets': json.dumps({
|
||||||
|
"database": {
|
||||||
|
"host": random.choice(wl.database_hosts),
|
||||||
|
"username": random_username(),
|
||||||
|
"password": random_password(),
|
||||||
|
"database": random_database_name()
|
||||||
|
},
|
||||||
|
"api_keys": {
|
||||||
|
"stripe": random_api_key(),
|
||||||
|
"aws": 'AKIA' + ''.join(random.choices(string.ascii_uppercase + string.digits, k=16))
|
||||||
|
}
|
||||||
|
}, indent=2),
|
||||||
|
'/api/config': json.dumps({
|
||||||
|
"app_name": random.choice(wl.application_names),
|
||||||
|
"debug": random.choice([True, False]),
|
||||||
|
"secret_key": random_api_key(),
|
||||||
|
"database_url": f"postgresql://{random_username()}:{random_password()}@localhost/{random_database_name()}"
|
||||||
|
}, indent=2),
|
||||||
|
'/.env': f"""APP_NAME={random.choice(wl.application_names)}
|
||||||
|
DEBUG={random.choice(['true', 'false'])}
|
||||||
|
APP_KEY=base64:{''.join(random.choices(string.ascii_letters + string.digits, k=32))}=
|
||||||
|
DB_CONNECTION=mysql
|
||||||
|
DB_HOST=127.0.0.1
|
||||||
|
DB_PORT=3306
|
||||||
|
DB_DATABASE={random_database_name()}
|
||||||
|
DB_USERNAME={random_username()}
|
||||||
|
DB_PASSWORD={random_password()}
|
||||||
|
AWS_ACCESS_KEY_ID=AKIA{''.join(random.choices(string.ascii_uppercase + string.digits, k=16))}
|
||||||
|
AWS_SECRET_ACCESS_KEY={''.join(random.choices(string.ascii_letters + string.digits + '+/', k=40))}
|
||||||
|
STRIPE_SECRET={random_api_key()}
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
return responses.get(path, json.dumps({"error": "Not found"}, indent=2))
|
||||||
|
|
||||||
|
|
||||||
|
def directory_listing(path: str) -> str:
|
||||||
|
"""Generate fake directory listing using wordlists"""
|
||||||
|
wl = get_wordlists()
|
||||||
|
|
||||||
|
files = wl.directory_files
|
||||||
|
dirs = wl.directory_dirs
|
||||||
|
|
||||||
|
selected_files = [(f, random.randint(1024, 1024*1024))
|
||||||
|
for f in random.sample(files, min(6, len(files)))]
|
||||||
|
|
||||||
|
return html_templates.directory_listing(path, dirs, selected_files)
|
||||||
342
src/handler.py
Normal file
342
src/handler.py
Normal file
@@ -0,0 +1,342 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import random
|
||||||
|
import time
|
||||||
|
from http.server import BaseHTTPRequestHandler
|
||||||
|
from typing import Optional, List
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from config import Config
|
||||||
|
from tracker import AccessTracker
|
||||||
|
from templates import html_templates
|
||||||
|
from templates.dashboard_template import generate_dashboard
|
||||||
|
from generators import (
|
||||||
|
credentials_txt, passwords_txt, users_json, api_keys_json,
|
||||||
|
api_response, directory_listing
|
||||||
|
)
|
||||||
|
from wordlists import get_wordlists
|
||||||
|
|
||||||
|
|
||||||
|
class Handler(BaseHTTPRequestHandler):
|
||||||
|
"""HTTP request handler for the deception server"""
|
||||||
|
webpages: Optional[List[str]] = None
|
||||||
|
config: Config = None
|
||||||
|
tracker: AccessTracker = None
|
||||||
|
counter: int = 0
|
||||||
|
|
||||||
|
def _get_client_ip(self) -> str:
|
||||||
|
"""Extract client IP address from request, checking proxy headers first"""
|
||||||
|
# Headers might not be available during early error logging
|
||||||
|
if hasattr(self, 'headers') and self.headers:
|
||||||
|
# Check X-Forwarded-For header (set by load balancers/proxies)
|
||||||
|
forwarded_for = self.headers.get('X-Forwarded-For')
|
||||||
|
if forwarded_for:
|
||||||
|
# X-Forwarded-For can contain multiple IPs, get the first (original client)
|
||||||
|
return forwarded_for.split(',')[0].strip()
|
||||||
|
|
||||||
|
# Check X-Real-IP header (set by nginx and other proxies)
|
||||||
|
real_ip = self.headers.get('X-Real-IP')
|
||||||
|
if real_ip:
|
||||||
|
return real_ip.strip()
|
||||||
|
|
||||||
|
# Fallback to direct connection IP
|
||||||
|
return self.client_address[0]
|
||||||
|
|
||||||
|
def _get_user_agent(self) -> str:
|
||||||
|
"""Extract user agent from request"""
|
||||||
|
return self.headers.get('User-Agent', '')
|
||||||
|
|
||||||
|
def _should_return_error(self) -> bool:
|
||||||
|
"""Check if we should return an error based on probability"""
|
||||||
|
if self.config.probability_error_codes <= 0:
|
||||||
|
return False
|
||||||
|
return random.randint(1, 100) <= self.config.probability_error_codes
|
||||||
|
|
||||||
|
def _get_random_error_code(self) -> int:
|
||||||
|
"""Get a random error code from wordlists"""
|
||||||
|
wl = get_wordlists()
|
||||||
|
error_codes = wl.error_codes
|
||||||
|
if not error_codes:
|
||||||
|
error_codes = [400, 401, 403, 404, 500, 502, 503]
|
||||||
|
return random.choice(error_codes)
|
||||||
|
|
||||||
|
def generate_page(self, seed: str) -> str:
|
||||||
|
"""Generate a webpage containing random links or canary token"""
|
||||||
|
random.seed(seed)
|
||||||
|
num_pages = random.randint(*self.config.links_per_page_range)
|
||||||
|
|
||||||
|
html = f"""<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Krawl</title>
|
||||||
|
<style>
|
||||||
|
body {{
|
||||||
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||||
|
background-color: #0d1117;
|
||||||
|
color: #c9d1d9;
|
||||||
|
margin: 0;
|
||||||
|
padding: 40px 20px;
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}}
|
||||||
|
.container {{
|
||||||
|
max-width: 1200px;
|
||||||
|
width: 100%;
|
||||||
|
}}
|
||||||
|
h1 {{
|
||||||
|
color: #f85149;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 48px;
|
||||||
|
margin: 60px 0 30px;
|
||||||
|
}}
|
||||||
|
.counter {{
|
||||||
|
color: #f85149;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 56px;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 60px;
|
||||||
|
}}
|
||||||
|
.links-container {{
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 20px;
|
||||||
|
align-items: center;
|
||||||
|
}}
|
||||||
|
.link-box {{
|
||||||
|
background: #161b22;
|
||||||
|
border: 1px solid #30363d;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 15px 30px;
|
||||||
|
min-width: 300px;
|
||||||
|
text-align: center;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}}
|
||||||
|
.link-box:hover {{
|
||||||
|
background: #1c2128;
|
||||||
|
border-color: #58a6ff;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 12px rgba(88, 166, 255, 0.2);
|
||||||
|
}}
|
||||||
|
a {{
|
||||||
|
color: #58a6ff;
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 700;
|
||||||
|
}}
|
||||||
|
a:hover {{
|
||||||
|
color: #79c0ff;
|
||||||
|
}}
|
||||||
|
.canary-token {{
|
||||||
|
background: #1c1917;
|
||||||
|
border: 2px solid #f85149;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 30px 50px;
|
||||||
|
margin: 40px auto;
|
||||||
|
max-width: 800px;
|
||||||
|
overflow-x: auto;
|
||||||
|
}}
|
||||||
|
.canary-token a {{
|
||||||
|
color: #f85149;
|
||||||
|
font-size: 18px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<h1>Krawl me! 🕸</h1>
|
||||||
|
<div class="counter">{Handler.counter}</div>
|
||||||
|
|
||||||
|
<div class="links-container">
|
||||||
|
"""
|
||||||
|
|
||||||
|
if Handler.counter <= 0 and self.config.canary_token_url:
|
||||||
|
html += f"""
|
||||||
|
<div class="link-box canary-token">
|
||||||
|
<a href="{self.config.canary_token_url}">{self.config.canary_token_url}</a>
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
|
|
||||||
|
if self.webpages is None:
|
||||||
|
for _ in range(num_pages):
|
||||||
|
address = ''.join([
|
||||||
|
random.choice(self.config.char_space)
|
||||||
|
for _ in range(random.randint(*self.config.links_length_range))
|
||||||
|
])
|
||||||
|
html += f"""
|
||||||
|
<div class="link-box">
|
||||||
|
<a href="{address}">{address}</a>
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
|
else:
|
||||||
|
for _ in range(num_pages):
|
||||||
|
address = random.choice(self.webpages)
|
||||||
|
html += f"""
|
||||||
|
<div class="link-box">
|
||||||
|
<a href="{address}">{address}</a>
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
|
|
||||||
|
html += """
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>"""
|
||||||
|
return html
|
||||||
|
|
||||||
|
def do_HEAD(self):
|
||||||
|
"""Sends header information"""
|
||||||
|
self.send_response(200)
|
||||||
|
self.send_header("Content-type", "text/html")
|
||||||
|
self.end_headers()
|
||||||
|
|
||||||
|
def do_POST(self):
|
||||||
|
"""Handle POST requests (mainly login attempts)"""
|
||||||
|
client_ip = self._get_client_ip()
|
||||||
|
user_agent = self._get_user_agent()
|
||||||
|
|
||||||
|
self.tracker.record_access(client_ip, self.path, user_agent)
|
||||||
|
|
||||||
|
print(f"[LOGIN ATTEMPT] {client_ip} - {self.path} - {user_agent[:50]}")
|
||||||
|
|
||||||
|
content_length = int(self.headers.get('Content-Length', 0))
|
||||||
|
if content_length > 0:
|
||||||
|
post_data = self.rfile.read(content_length).decode('utf-8')
|
||||||
|
print(f"[POST DATA] {post_data[:200]}")
|
||||||
|
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
self.send_response(200)
|
||||||
|
self.send_header('Content-type', 'text/html')
|
||||||
|
self.end_headers()
|
||||||
|
self.wfile.write(html_templates.login_error().encode())
|
||||||
|
|
||||||
|
def serve_special_path(self, path: str) -> bool:
|
||||||
|
"""Serve special paths like robots.txt, API endpoints, etc."""
|
||||||
|
|
||||||
|
if path == '/robots.txt':
|
||||||
|
self.send_response(200)
|
||||||
|
self.send_header('Content-type', 'text/plain')
|
||||||
|
self.end_headers()
|
||||||
|
self.wfile.write(html_templates.robots_txt().encode())
|
||||||
|
return True
|
||||||
|
|
||||||
|
if path in ['/credentials.txt', '/passwords.txt', '/admin_notes.txt']:
|
||||||
|
self.send_response(200)
|
||||||
|
self.send_header('Content-type', 'text/plain')
|
||||||
|
self.end_headers()
|
||||||
|
if 'credentials' in path:
|
||||||
|
self.wfile.write(credentials_txt().encode())
|
||||||
|
else:
|
||||||
|
self.wfile.write(passwords_txt().encode())
|
||||||
|
return True
|
||||||
|
|
||||||
|
if path in ['/users.json', '/api_keys.json', '/config.json']:
|
||||||
|
self.send_response(200)
|
||||||
|
self.send_header('Content-type', 'application/json')
|
||||||
|
self.end_headers()
|
||||||
|
if 'users' in path:
|
||||||
|
self.wfile.write(users_json().encode())
|
||||||
|
elif 'api_keys' in path:
|
||||||
|
self.wfile.write(api_keys_json().encode())
|
||||||
|
else:
|
||||||
|
self.wfile.write(api_response('/api/config').encode())
|
||||||
|
return True
|
||||||
|
|
||||||
|
if path in ['/admin', '/admin/', '/admin/login', '/login', '/wp-login.php']:
|
||||||
|
self.send_response(200)
|
||||||
|
self.send_header('Content-type', 'text/html')
|
||||||
|
self.end_headers()
|
||||||
|
self.wfile.write(html_templates.login_form().encode())
|
||||||
|
return True
|
||||||
|
|
||||||
|
if path == '/wp-admin' or path == '/wp-admin/':
|
||||||
|
self.send_response(200)
|
||||||
|
self.send_header('Content-type', 'text/html')
|
||||||
|
self.end_headers()
|
||||||
|
self.wfile.write(html_templates.login_form().encode())
|
||||||
|
return True
|
||||||
|
|
||||||
|
if path in ['/wp-content/', '/wp-includes/'] or 'wordpress' in path.lower():
|
||||||
|
self.send_response(200)
|
||||||
|
self.send_header('Content-type', 'text/html')
|
||||||
|
self.end_headers()
|
||||||
|
self.wfile.write(html_templates.wordpress().encode())
|
||||||
|
return True
|
||||||
|
|
||||||
|
if 'phpmyadmin' in path.lower() or path in ['/pma/', '/phpMyAdmin/']:
|
||||||
|
self.send_response(200)
|
||||||
|
self.send_header('Content-type', 'text/html')
|
||||||
|
self.end_headers()
|
||||||
|
self.wfile.write(html_templates.phpmyadmin().encode())
|
||||||
|
return True
|
||||||
|
|
||||||
|
if path.startswith('/api/') or path.startswith('/api') or path in ['/.env']:
|
||||||
|
self.send_response(200)
|
||||||
|
self.send_header('Content-type', 'application/json')
|
||||||
|
self.end_headers()
|
||||||
|
self.wfile.write(api_response(path).encode())
|
||||||
|
return True
|
||||||
|
|
||||||
|
if path in ['/backup/', '/uploads/', '/private/', '/admin/', '/config/', '/database/']:
|
||||||
|
self.send_response(200)
|
||||||
|
self.send_header('Content-type', 'text/html')
|
||||||
|
self.end_headers()
|
||||||
|
self.wfile.write(directory_listing(path).encode())
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def do_GET(self):
|
||||||
|
"""Responds to webpage requests"""
|
||||||
|
client_ip = self._get_client_ip()
|
||||||
|
user_agent = self._get_user_agent()
|
||||||
|
|
||||||
|
if self.config.dashboard_secret_path and self.path == self.config.dashboard_secret_path:
|
||||||
|
self.send_response(200)
|
||||||
|
self.send_header('Content-type', 'text/html')
|
||||||
|
self.end_headers()
|
||||||
|
try:
|
||||||
|
stats = self.tracker.get_stats()
|
||||||
|
self.wfile.write(generate_dashboard(stats).encode())
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error generating dashboard: {e}")
|
||||||
|
return
|
||||||
|
|
||||||
|
self.tracker.record_access(client_ip, self.path, user_agent)
|
||||||
|
|
||||||
|
if self.tracker.is_suspicious_user_agent(user_agent):
|
||||||
|
print(f"[SUSPICIOUS] {client_ip} - {user_agent[:50]} - {self.path}")
|
||||||
|
|
||||||
|
if self._should_return_error():
|
||||||
|
error_code = self._get_random_error_code()
|
||||||
|
print(f"[ERROR] Returning {error_code} to {client_ip} - {self.path}")
|
||||||
|
self.send_response(error_code)
|
||||||
|
self.end_headers()
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.serve_special_path(self.path):
|
||||||
|
return
|
||||||
|
|
||||||
|
time.sleep(self.config.delay / 1000.0)
|
||||||
|
self.send_response(200)
|
||||||
|
self.send_header('Content-type', 'text/html')
|
||||||
|
self.end_headers()
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.wfile.write(self.generate_page(self.path).encode())
|
||||||
|
|
||||||
|
Handler.counter -= 1
|
||||||
|
|
||||||
|
if Handler.counter < 0:
|
||||||
|
Handler.counter = self.config.canary_token_tries
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error generating page: {e}")
|
||||||
|
|
||||||
|
def log_message(self, format, *args):
|
||||||
|
"""Override to customize logging"""
|
||||||
|
client_ip = self._get_client_ip()
|
||||||
|
print(f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] {client_ip} - {format % args}")
|
||||||
83
src/server.py
Normal file
83
src/server.py
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
"""
|
||||||
|
Main server module for the deception honeypot.
|
||||||
|
Run this file to start the server.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from http.server import HTTPServer
|
||||||
|
|
||||||
|
from config import Config
|
||||||
|
from tracker import AccessTracker
|
||||||
|
from handler import Handler
|
||||||
|
|
||||||
|
|
||||||
|
def print_usage():
|
||||||
|
"""Print usage information"""
|
||||||
|
print(f'Usage: {sys.argv[0]} [FILE]\n')
|
||||||
|
print('FILE is file containing a list of webpage names to serve, one per line.')
|
||||||
|
print('If no file is provided, random links will be generated.\n')
|
||||||
|
print('Environment Variables:')
|
||||||
|
print(' PORT - Server port (default: 5000)')
|
||||||
|
print(' DELAY - Response delay in ms (default: 100)')
|
||||||
|
print(' LINKS_MIN_LENGTH - Min link length (default: 5)')
|
||||||
|
print(' LINKS_MAX_LENGTH - Max link length (default: 15)')
|
||||||
|
print(' LINKS_MIN_PER_PAGE - Min links per page (default: 10)')
|
||||||
|
print(' LINKS_MAX_PER_PAGE - Max links per page (default: 15)')
|
||||||
|
print(' MAX_COUNTER - Max counter value (default: 10)')
|
||||||
|
print(' CANARY_TOKEN_URL - Canary token URL to display')
|
||||||
|
print(' CANARY_TOKEN_TRIES - Number of tries before showing token (default: 10)')
|
||||||
|
print(' DASHBOARD_SECRET_PATH - Secret path for dashboard (auto-generated if not set)')
|
||||||
|
print(' PROBABILITY_ERROR_CODES - Probability (0-100) to return HTTP error codes (default: 0)')
|
||||||
|
print(' CHAR_SPACE - Characters for random links')
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Main entry point for the deception server"""
|
||||||
|
if '-h' in sys.argv or '--help' in sys.argv:
|
||||||
|
print_usage()
|
||||||
|
exit(0)
|
||||||
|
|
||||||
|
config = Config.from_env()
|
||||||
|
|
||||||
|
tracker = AccessTracker()
|
||||||
|
|
||||||
|
Handler.config = config
|
||||||
|
Handler.tracker = tracker
|
||||||
|
Handler.counter = config.canary_token_tries
|
||||||
|
|
||||||
|
if len(sys.argv) == 2:
|
||||||
|
try:
|
||||||
|
with open(sys.argv[1], 'r') as f:
|
||||||
|
Handler.webpages = f.readlines()
|
||||||
|
|
||||||
|
if not Handler.webpages:
|
||||||
|
print('The file provided was empty. Using randomly generated links.')
|
||||||
|
Handler.webpages = None
|
||||||
|
except IOError:
|
||||||
|
print('Can\'t read input file. Using randomly generated links.')
|
||||||
|
|
||||||
|
try:
|
||||||
|
print(f'Starting deception server on port {config.port}...')
|
||||||
|
print(f'Dashboard available at: {config.dashboard_secret_path}')
|
||||||
|
if config.canary_token_url:
|
||||||
|
print(f'Canary token will appear after {config.canary_token_tries} tries')
|
||||||
|
else:
|
||||||
|
print('No canary token configured (set CANARY_TOKEN_URL to enable)')
|
||||||
|
|
||||||
|
server = HTTPServer(('0.0.0.0', config.port), Handler)
|
||||||
|
print('Server started. Use <Ctrl-C> to stop.')
|
||||||
|
server.serve_forever()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print('\nStopping server...')
|
||||||
|
server.socket.close()
|
||||||
|
print('Server stopped')
|
||||||
|
except Exception as e:
|
||||||
|
print(f'Error starting HTTP server on port {config.port}: {e}')
|
||||||
|
print(f'Make sure you are root, if needed, and that port {config.port} is open.')
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
241
src/templates/dashboard_template.py
Normal file
241
src/templates/dashboard_template.py
Normal file
@@ -0,0 +1,241 @@
|
|||||||
|
#!/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"""
|
||||||
|
|
||||||
|
# Generate IP rows
|
||||||
|
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>'
|
||||||
|
|
||||||
|
# Generate honeypot triggered IPs rows
|
||||||
|
honeypot_rows = '\n'.join([
|
||||||
|
f'<tr><td>{ip}</td><td style="word-break: break-all;">{", ".join(paths)}</td><td>{len(paths)}</td></tr>'
|
||||||
|
for ip, paths in stats.get('honeypot_triggered_ips', [])
|
||||||
|
]) or '<tr><td colspan="3" style="text-align:center;">No honeypot triggers yet</td></tr>'
|
||||||
|
|
||||||
|
return f"""<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Krawl Dashboard</title>
|
||||||
|
<style>
|
||||||
|
body {{
|
||||||
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||||
|
background-color: #0d1117;
|
||||||
|
color: #c9d1d9;
|
||||||
|
margin: 0;
|
||||||
|
padding: 20px;
|
||||||
|
}}
|
||||||
|
.container {{
|
||||||
|
max-width: 1400px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}}
|
||||||
|
h1 {{
|
||||||
|
color: #58a6ff;
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 40px;
|
||||||
|
}}
|
||||||
|
.stats-grid {{
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||||
|
gap: 20px;
|
||||||
|
margin-bottom: 40px;
|
||||||
|
}}
|
||||||
|
.stat-card {{
|
||||||
|
background: #161b22;
|
||||||
|
border: 1px solid #30363d;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 20px;
|
||||||
|
text-align: center;
|
||||||
|
}}
|
||||||
|
.stat-card.alert {{
|
||||||
|
border-color: #f85149;
|
||||||
|
}}
|
||||||
|
.stat-value {{
|
||||||
|
font-size: 36px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #58a6ff;
|
||||||
|
}}
|
||||||
|
.stat-value.alert {{
|
||||||
|
color: #f85149;
|
||||||
|
}}
|
||||||
|
.stat-label {{
|
||||||
|
font-size: 14px;
|
||||||
|
color: #8b949e;
|
||||||
|
margin-top: 5px;
|
||||||
|
}}
|
||||||
|
.table-container {{
|
||||||
|
background: #161b22;
|
||||||
|
border: 1px solid #30363d;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 20px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}}
|
||||||
|
h2 {{
|
||||||
|
color: #58a6ff;
|
||||||
|
margin-top: 0;
|
||||||
|
}}
|
||||||
|
table {{
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}}
|
||||||
|
th, td {{
|
||||||
|
padding: 12px;
|
||||||
|
text-align: left;
|
||||||
|
border-bottom: 1px solid #30363d;
|
||||||
|
}}
|
||||||
|
th {{
|
||||||
|
background: #0d1117;
|
||||||
|
color: #58a6ff;
|
||||||
|
font-weight: 600;
|
||||||
|
}}
|
||||||
|
tr:hover {{
|
||||||
|
background: #1c2128;
|
||||||
|
}}
|
||||||
|
.rank {{
|
||||||
|
color: #8b949e;
|
||||||
|
font-weight: bold;
|
||||||
|
}}
|
||||||
|
.alert-section {{
|
||||||
|
background: #1c1917;
|
||||||
|
border-left: 4px solid #f85149;
|
||||||
|
}}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<h1>🕷️ Krawl Dashboard</h1>
|
||||||
|
|
||||||
|
<div class="stats-grid">
|
||||||
|
<div class="stat-card">
|
||||||
|
<div class="stat-value">{stats['total_accesses']}</div>
|
||||||
|
<div class="stat-label">Total Accesses</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-card">
|
||||||
|
<div class="stat-value">{stats['unique_ips']}</div>
|
||||||
|
<div class="stat-label">Unique IPs</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-card">
|
||||||
|
<div class="stat-value">{stats['unique_paths']}</div>
|
||||||
|
<div class="stat-label">Unique Paths</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-card alert">
|
||||||
|
<div class="stat-value alert">{stats['suspicious_accesses']}</div>
|
||||||
|
<div class="stat-label">Suspicious Accesses</div>
|
||||||
|
</div>
|
||||||
|
<div class="stat-card alert">
|
||||||
|
<div class="stat-value alert">{stats.get('honeypot_ips', 0)}</div>
|
||||||
|
<div class="stat-label">Honeypot Caught</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="table-container alert-section">
|
||||||
|
<h2>🍯 Honeypot Triggers</h2>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>IP Address</th>
|
||||||
|
<th>Accessed Paths</th>
|
||||||
|
<th>Count</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{honeypot_rows}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="table-container alert-section">
|
||||||
|
<h2>⚠️ Recent Suspicious Activity</h2>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>IP Address</th>
|
||||||
|
<th>Path</th>
|
||||||
|
<th>User-Agent</th>
|
||||||
|
<th>Time</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{suspicious_rows}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="table-container">
|
||||||
|
<h2>Top IP Addresses</h2>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>#</th>
|
||||||
|
<th>IP Address</th>
|
||||||
|
<th>Access Count</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{top_ips_rows}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="table-container">
|
||||||
|
<h2>Top Paths</h2>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>#</th>
|
||||||
|
<th>Path</th>
|
||||||
|
<th>Access Count</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{top_paths_rows}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="table-container">
|
||||||
|
<h2>Top User-Agents</h2>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>#</th>
|
||||||
|
<th>User-Agent</th>
|
||||||
|
<th>Count</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{top_ua_rows}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
"""
|
||||||
229
src/templates/html_templates.py
Normal file
229
src/templates/html_templates.py
Normal file
@@ -0,0 +1,229 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
"""
|
||||||
|
HTML templates for the deception server.
|
||||||
|
Edit these templates to customize the appearance of fake pages.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def login_form() -> str:
|
||||||
|
"""Generate fake login page"""
|
||||||
|
return """<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Admin Login</title>
|
||||||
|
<style>
|
||||||
|
body { font-family: Arial, sans-serif; background: #f0f0f0; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; }
|
||||||
|
.login-box { background: white; padding: 40px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); width: 300px; }
|
||||||
|
h2 { margin-top: 0; color: #333; }
|
||||||
|
input { width: 100%; padding: 10px; margin: 10px 0; border: 1px solid #ddd; border-radius: 4px; box-sizing: border-box; }
|
||||||
|
button { width: 100%; padding: 10px; background: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer; }
|
||||||
|
button:hover { background: #0056b3; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="login-box">
|
||||||
|
<h2>Admin Login</h2>
|
||||||
|
<form action="/admin/login" method="post">
|
||||||
|
<input type="text" name="username" placeholder="Username" required>
|
||||||
|
<input type="password" name="password" placeholder="Password" required>
|
||||||
|
<button type="submit">Login</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>"""
|
||||||
|
|
||||||
|
|
||||||
|
def login_error() -> str:
|
||||||
|
"""Generate fake login error page"""
|
||||||
|
return """<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Login Failed</title>
|
||||||
|
<style>
|
||||||
|
body { font-family: Arial, sans-serif; background: #f0f0f0; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; }
|
||||||
|
.login-box { background: white; padding: 40px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); width: 300px; }
|
||||||
|
h2 { margin-top: 0; color: #333; }
|
||||||
|
.error { color: #d63301; background: #ffebe8; border: 1px solid #d63301; padding: 12px; margin-bottom: 20px; border-radius: 4px; }
|
||||||
|
input { width: 100%; padding: 10px; margin: 10px 0; border: 1px solid #ddd; border-radius: 4px; box-sizing: border-box; }
|
||||||
|
button { width: 100%; padding: 10px; background: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer; }
|
||||||
|
button:hover { background: #0056b3; }
|
||||||
|
a { color: #007bff; font-size: 14px; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="login-box">
|
||||||
|
<h2>Admin Login</h2>
|
||||||
|
<div class="error"><strong>ERROR:</strong> Invalid username or password.</div>
|
||||||
|
<form action="/admin/login" method="post">
|
||||||
|
<input type="text" name="username" placeholder="Username" required>
|
||||||
|
<input type="password" name="password" placeholder="Password" required>
|
||||||
|
<button type="submit">Login</button>
|
||||||
|
</form>
|
||||||
|
<p style="margin-top: 20px; text-align: center;"><a href="/forgot-password">Forgot your password?</a></p>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>"""
|
||||||
|
|
||||||
|
|
||||||
|
def wordpress() -> str:
|
||||||
|
"""Generate fake WordPress page"""
|
||||||
|
return """<!DOCTYPE html>
|
||||||
|
<html lang="en-US">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>My Blog – Just another WordPress site</title>
|
||||||
|
<link rel='dns-prefetch' href='//s.w.org' />
|
||||||
|
<link rel='stylesheet' id='wp-block-library-css' href='/wp-includes/css/dist/block-library/style.min.css' type='text/css' media='all' />
|
||||||
|
<link rel='stylesheet' id='twentytwentythree-style-css' href='/wp-content/themes/twentytwentythree/style.css' type='text/css' media='all' />
|
||||||
|
<link rel='https://api.w.org/' href='/wp-json/' />
|
||||||
|
<meta name="generator" content="WordPress 6.4.2" />
|
||||||
|
<style>
|
||||||
|
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; margin: 0; padding: 0; background: #fff; }
|
||||||
|
.site-header { background: #23282d; color: white; padding: 20px; border-bottom: 4px solid #0073aa; }
|
||||||
|
.site-header h1 { margin: 0; font-size: 28px; }
|
||||||
|
.site-header p { margin: 5px 0 0; color: #d0d0d0; }
|
||||||
|
.site-content { max-width: 1200px; margin: 40px auto; padding: 0 20px; }
|
||||||
|
.entry { background: #fff; margin-bottom: 40px; padding: 30px; border: 1px solid #ddd; border-radius: 4px; }
|
||||||
|
.entry-title { font-size: 32px; margin-top: 0; color: #23282d; }
|
||||||
|
.entry-meta { color: #666; font-size: 14px; margin-bottom: 20px; }
|
||||||
|
.entry-content { line-height: 1.8; color: #444; }
|
||||||
|
.site-footer { background: #f7f7f7; padding: 20px; text-align: center; color: #666; border-top: 1px solid #ddd; margin-top: 60px; }
|
||||||
|
a { color: #0073aa; text-decoration: none; }
|
||||||
|
a:hover { text-decoration: underline; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body class="home blog wp-embed-responsive">
|
||||||
|
<div id="page" class="site">
|
||||||
|
<header id="masthead" class="site-header">
|
||||||
|
<div class="site-branding">
|
||||||
|
<h1 class="site-title">My Blog</h1>
|
||||||
|
<p class="site-description">Just another WordPress site</p>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div id="content" class="site-content">
|
||||||
|
<article id="post-1" class="entry">
|
||||||
|
<header class="entry-header">
|
||||||
|
<h2 class="entry-title">Hello world!</h2>
|
||||||
|
<div class="entry-meta">
|
||||||
|
<span class="posted-on">Posted on <time datetime="2024-12-01">December 1, 2024</time></span>
|
||||||
|
<span class="byline"> by <span class="author">admin</span></span>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<div class="entry-content">
|
||||||
|
<p>Welcome to WordPress. This is your first post. Edit or delete it, then start writing!</p>
|
||||||
|
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.</p>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<article id="post-2" class="entry">
|
||||||
|
<header class="entry-header">
|
||||||
|
<h2 class="entry-title">About This Site</h2>
|
||||||
|
<div class="entry-meta">
|
||||||
|
<span class="posted-on">Posted on <time datetime="2024-11-28">November 28, 2024</time></span>
|
||||||
|
<span class="byline"> by <span class="author">admin</span></span>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<div class="entry-content">
|
||||||
|
<p>This is a sample page. You can use it to write about your site, yourself, or anything else you'd like.</p>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<footer id="colophon" class="site-footer">
|
||||||
|
<div class="site-info">
|
||||||
|
Proudly powered by <a href="https://wordpress.org/">WordPress</a>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
<script type='text/javascript' src='/wp-includes/js/wp-embed.min.js'></script>
|
||||||
|
</body>
|
||||||
|
</html>"""
|
||||||
|
|
||||||
|
|
||||||
|
def phpmyadmin() -> str:
|
||||||
|
"""Generate fake phpMyAdmin page"""
|
||||||
|
return """<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>phpMyAdmin</title>
|
||||||
|
<style>
|
||||||
|
body { font-family: 'Segoe UI', Tahoma, sans-serif; margin: 0; background: #f0f0f0; }
|
||||||
|
.header { background: #2979ff; color: white; padding: 10px 20px; }
|
||||||
|
.login { background: white; width: 400px; margin: 100px auto; padding: 30px; border-radius: 4px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
|
||||||
|
input { width: 100%; padding: 8px; margin: 8px 0; border: 1px solid #ddd; }
|
||||||
|
button { padding: 10px 20px; background: #2979ff; color: white; border: none; cursor: pointer; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="header"><h1>phpMyAdmin</h1></div>
|
||||||
|
<div class="login">
|
||||||
|
<h2>MySQL Server Login</h2>
|
||||||
|
<form action="/phpMyAdmin/index.php" method="post">
|
||||||
|
<input type="text" name="pma_username" placeholder="Username">
|
||||||
|
<input type="password" name="pma_password" placeholder="Password">
|
||||||
|
<button type="submit">Go</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>"""
|
||||||
|
|
||||||
|
|
||||||
|
def robots_txt() -> str:
|
||||||
|
"""Generate juicy robots.txt"""
|
||||||
|
return """User-agent: *
|
||||||
|
Disallow: /admin/
|
||||||
|
Disallow: /api/
|
||||||
|
Disallow: /backup/
|
||||||
|
Disallow: /config/
|
||||||
|
Disallow: /database/
|
||||||
|
Disallow: /private/
|
||||||
|
Disallow: /uploads/
|
||||||
|
Disallow: /wp-admin/
|
||||||
|
Disallow: /phpMyAdmin/
|
||||||
|
Disallow: /admin/login.php
|
||||||
|
Disallow: /api/v1/users
|
||||||
|
Disallow: /api/v2/secrets
|
||||||
|
Disallow: /.env
|
||||||
|
Disallow: /credentials.txt
|
||||||
|
Disallow: /passwords.txt
|
||||||
|
Disallow: /.git/
|
||||||
|
Disallow: /backup.sql
|
||||||
|
Disallow: /db_backup.sql
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def directory_listing(path: str, dirs: list, files: list) -> str:
|
||||||
|
"""Generate fake directory listing"""
|
||||||
|
html = f"""<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head><title>Index of {path}</title>
|
||||||
|
<style>
|
||||||
|
body {{ font-family: monospace; background: #fff; padding: 20px; }}
|
||||||
|
h1 {{ border-bottom: 1px solid #ccc; padding-bottom: 10px; }}
|
||||||
|
table {{ width: 100%; border-collapse: collapse; }}
|
||||||
|
th {{ text-align: left; padding: 10px; background: #f0f0f0; }}
|
||||||
|
td {{ padding: 8px; border-bottom: 1px solid #eee; }}
|
||||||
|
a {{ color: #0066cc; text-decoration: none; }}
|
||||||
|
a:hover {{ text-decoration: underline; }}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Index of {path}</h1>
|
||||||
|
<table>
|
||||||
|
<tr><th>Name</th><th>Last Modified</th><th>Size</th></tr>
|
||||||
|
<tr><td><a href="../">Parent Directory</a></td><td>-</td><td>-</td></tr>
|
||||||
|
"""
|
||||||
|
|
||||||
|
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'
|
||||||
|
|
||||||
|
for f, size in files:
|
||||||
|
html += f'<tr><td><a href="{f}">{f}</a></td><td>2024-12-01 14:22</td><td>{size}</td></tr>\n'
|
||||||
|
|
||||||
|
html += '</table></body></html>'
|
||||||
|
return html
|
||||||
114
src/tracker.py
Normal file
114
src/tracker.py
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
from typing import Dict, List, Tuple
|
||||||
|
from collections import defaultdict
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
|
||||||
|
class AccessTracker:
|
||||||
|
"""Track IP addresses and paths accessed"""
|
||||||
|
def __init__(self):
|
||||||
|
self.ip_counts: Dict[str, int] = defaultdict(int)
|
||||||
|
self.path_counts: Dict[str, int] = defaultdict(int)
|
||||||
|
self.user_agent_counts: Dict[str, int] = defaultdict(int)
|
||||||
|
self.access_log: List[Dict] = []
|
||||||
|
self.suspicious_patterns = [
|
||||||
|
'bot', 'crawler', 'spider', 'scraper', 'curl', 'wget', 'python-requests',
|
||||||
|
'scanner', 'nikto', 'sqlmap', 'nmap', 'masscan', 'nessus', 'acunetix',
|
||||||
|
'burp', 'zap', 'w3af', 'metasploit', 'nuclei', 'gobuster', 'dirbuster'
|
||||||
|
]
|
||||||
|
# Track IPs that accessed honeypot paths from robots.txt
|
||||||
|
self.honeypot_triggered: Dict[str, List[str]] = defaultdict(list)
|
||||||
|
|
||||||
|
def record_access(self, ip: str, path: str, user_agent: str = ''):
|
||||||
|
"""Record an access attempt"""
|
||||||
|
self.ip_counts[ip] += 1
|
||||||
|
self.path_counts[path] += 1
|
||||||
|
if user_agent:
|
||||||
|
self.user_agent_counts[user_agent] += 1
|
||||||
|
|
||||||
|
is_suspicious = self.is_suspicious_user_agent(user_agent) or self.is_honeypot_path(path)
|
||||||
|
|
||||||
|
# Track if this IP accessed a honeypot path
|
||||||
|
if self.is_honeypot_path(path):
|
||||||
|
self.honeypot_triggered[ip].append(path)
|
||||||
|
|
||||||
|
self.access_log.append({
|
||||||
|
'ip': ip,
|
||||||
|
'path': path,
|
||||||
|
'user_agent': user_agent,
|
||||||
|
'suspicious': is_suspicious,
|
||||||
|
'honeypot_triggered': self.is_honeypot_path(path),
|
||||||
|
'timestamp': datetime.now().isoformat()
|
||||||
|
})
|
||||||
|
|
||||||
|
def is_honeypot_path(self, path: str) -> bool:
|
||||||
|
"""Check if path is one of the honeypot traps from robots.txt"""
|
||||||
|
honeypot_paths = [
|
||||||
|
'/admin',
|
||||||
|
'/admin/',
|
||||||
|
'/backup',
|
||||||
|
'/backup/',
|
||||||
|
'/config',
|
||||||
|
'/config/',
|
||||||
|
'/private',
|
||||||
|
'/private/',
|
||||||
|
'/database',
|
||||||
|
'/database/',
|
||||||
|
'/credentials.txt',
|
||||||
|
'/passwords.txt',
|
||||||
|
'/admin_notes.txt',
|
||||||
|
'/api_keys.json',
|
||||||
|
'/.env',
|
||||||
|
'/wp-admin',
|
||||||
|
'/wp-admin/',
|
||||||
|
'/phpmyadmin',
|
||||||
|
'/phpMyAdmin/'
|
||||||
|
]
|
||||||
|
return path in honeypot_paths or any(hp in path.lower() for hp in ['/backup', '/admin', '/config', '/private', '/database', 'phpmyadmin'])
|
||||||
|
|
||||||
|
def is_suspicious_user_agent(self, user_agent: str) -> bool:
|
||||||
|
"""Check if user agent matches suspicious patterns"""
|
||||||
|
if not user_agent:
|
||||||
|
return True
|
||||||
|
ua_lower = user_agent.lower()
|
||||||
|
return any(pattern in ua_lower for pattern in self.suspicious_patterns)
|
||||||
|
|
||||||
|
def get_top_ips(self, limit: int = 10) -> List[Tuple[str, int]]:
|
||||||
|
"""Get top N IP addresses by access count"""
|
||||||
|
return sorted(self.ip_counts.items(), key=lambda x: x[1], reverse=True)[:limit]
|
||||||
|
|
||||||
|
def get_top_paths(self, limit: int = 10) -> List[Tuple[str, int]]:
|
||||||
|
"""Get top N paths by access count"""
|
||||||
|
return sorted(self.path_counts.items(), key=lambda x: x[1], reverse=True)[:limit]
|
||||||
|
|
||||||
|
def get_top_user_agents(self, limit: int = 10) -> List[Tuple[str, int]]:
|
||||||
|
"""Get top N user agents by access count"""
|
||||||
|
return sorted(self.user_agent_counts.items(), key=lambda x: x[1], reverse=True)[:limit]
|
||||||
|
|
||||||
|
def get_suspicious_accesses(self, limit: int = 20) -> List[Dict]:
|
||||||
|
"""Get recent suspicious accesses"""
|
||||||
|
suspicious = [log for log in self.access_log if log.get('suspicious', False)]
|
||||||
|
return suspicious[-limit:]
|
||||||
|
|
||||||
|
def get_honeypot_triggered_ips(self) -> List[Tuple[str, List[str]]]:
|
||||||
|
"""Get IPs that accessed honeypot paths"""
|
||||||
|
return [(ip, paths) for ip, paths in self.honeypot_triggered.items()]
|
||||||
|
|
||||||
|
def get_stats(self) -> Dict:
|
||||||
|
"""Get statistics summary"""
|
||||||
|
suspicious_count = sum(1 for log in self.access_log if log.get('suspicious', False))
|
||||||
|
honeypot_count = sum(1 for log in self.access_log if log.get('honeypot_triggered', False))
|
||||||
|
return {
|
||||||
|
'total_accesses': len(self.access_log),
|
||||||
|
'unique_ips': len(self.ip_counts),
|
||||||
|
'unique_paths': len(self.path_counts),
|
||||||
|
'suspicious_accesses': suspicious_count,
|
||||||
|
'honeypot_triggered': honeypot_count,
|
||||||
|
'honeypot_ips': len(self.honeypot_triggered),
|
||||||
|
'top_ips': self.get_top_ips(10),
|
||||||
|
'top_paths': self.get_top_paths(10),
|
||||||
|
'top_user_agents': self.get_top_user_agents(10),
|
||||||
|
'recent_suspicious': self.get_suspicious_accesses(20),
|
||||||
|
'honeypot_triggered_ips': self.get_honeypot_triggered_ips()
|
||||||
|
}
|
||||||
123
src/wordlists.py
Normal file
123
src/wordlists.py
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
"""
|
||||||
|
Wordlists loader - reads all wordlists from wordlists.json
|
||||||
|
This allows easy customization without touching Python code.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
class Wordlists:
|
||||||
|
"""Loads and provides access to wordlists from wordlists.json"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self._data = self._load_config()
|
||||||
|
|
||||||
|
def _load_config(self):
|
||||||
|
"""Load wordlists from JSON file"""
|
||||||
|
config_path = Path(__file__).parent.parent / 'wordlists.json'
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(config_path, 'r') as f:
|
||||||
|
return json.load(f)
|
||||||
|
except FileNotFoundError:
|
||||||
|
print(f"⚠️ Warning: {config_path} not found, using default values")
|
||||||
|
return self._get_defaults()
|
||||||
|
except json.JSONDecodeError as e:
|
||||||
|
print(f"⚠️ Warning: Invalid JSON in {config_path}: {e}")
|
||||||
|
return self._get_defaults()
|
||||||
|
|
||||||
|
def _get_defaults(self):
|
||||||
|
"""Fallback default wordlists if JSON file is missing or invalid"""
|
||||||
|
return {
|
||||||
|
"usernames": {
|
||||||
|
"prefixes": ["admin", "user", "root"],
|
||||||
|
"suffixes": ["", "_prod", "_dev"]
|
||||||
|
},
|
||||||
|
"passwords": {
|
||||||
|
"prefixes": ["P@ssw0rd", "Admin"],
|
||||||
|
"simple": ["test", "demo", "password"]
|
||||||
|
},
|
||||||
|
"emails": {
|
||||||
|
"domains": ["example.com", "test.com"]
|
||||||
|
},
|
||||||
|
"api_keys": {
|
||||||
|
"prefixes": ["sk_live_", "api_", ""]
|
||||||
|
},
|
||||||
|
"databases": {
|
||||||
|
"names": ["production", "main_db"],
|
||||||
|
"hosts": ["localhost", "db.internal"]
|
||||||
|
},
|
||||||
|
"applications": {
|
||||||
|
"names": ["WebApp", "Dashboard"]
|
||||||
|
},
|
||||||
|
"users": {
|
||||||
|
"roles": ["Administrator", "User"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def username_prefixes(self):
|
||||||
|
return self._data.get("usernames", {}).get("prefixes", [])
|
||||||
|
|
||||||
|
@property
|
||||||
|
def username_suffixes(self):
|
||||||
|
return self._data.get("usernames", {}).get("suffixes", [])
|
||||||
|
|
||||||
|
@property
|
||||||
|
def password_prefixes(self):
|
||||||
|
return self._data.get("passwords", {}).get("prefixes", [])
|
||||||
|
|
||||||
|
@property
|
||||||
|
def simple_passwords(self):
|
||||||
|
return self._data.get("passwords", {}).get("simple", [])
|
||||||
|
|
||||||
|
@property
|
||||||
|
def email_domains(self):
|
||||||
|
return self._data.get("emails", {}).get("domains", [])
|
||||||
|
|
||||||
|
@property
|
||||||
|
def api_key_prefixes(self):
|
||||||
|
return self._data.get("api_keys", {}).get("prefixes", [])
|
||||||
|
|
||||||
|
@property
|
||||||
|
def database_names(self):
|
||||||
|
return self._data.get("databases", {}).get("names", [])
|
||||||
|
|
||||||
|
@property
|
||||||
|
def database_hosts(self):
|
||||||
|
return self._data.get("databases", {}).get("hosts", [])
|
||||||
|
|
||||||
|
@property
|
||||||
|
def application_names(self):
|
||||||
|
return self._data.get("applications", {}).get("names", [])
|
||||||
|
|
||||||
|
@property
|
||||||
|
def user_roles(self):
|
||||||
|
return self._data.get("users", {}).get("roles", [])
|
||||||
|
|
||||||
|
@property
|
||||||
|
def directory_files(self):
|
||||||
|
return self._data.get("directory_listing", {}).get("files", [])
|
||||||
|
|
||||||
|
@property
|
||||||
|
def directory_dirs(self):
|
||||||
|
return self._data.get("directory_listing", {}).get("directories", [])
|
||||||
|
|
||||||
|
@property
|
||||||
|
def error_codes(self):
|
||||||
|
return self._data.get("error_codes", [])
|
||||||
|
|
||||||
|
|
||||||
|
_wordlists_instance = None
|
||||||
|
|
||||||
|
def get_wordlists():
|
||||||
|
"""Get the singleton Wordlists instance"""
|
||||||
|
global _wordlists_instance
|
||||||
|
if _wordlists_instance is None:
|
||||||
|
_wordlists_instance = Wordlists()
|
||||||
|
return _wordlists_instance
|
||||||
|
|
||||||
197
wordlists.json
Normal file
197
wordlists.json
Normal file
@@ -0,0 +1,197 @@
|
|||||||
|
{
|
||||||
|
"usernames": {
|
||||||
|
"prefixes": [
|
||||||
|
"admin",
|
||||||
|
"user",
|
||||||
|
"developer",
|
||||||
|
"root",
|
||||||
|
"system",
|
||||||
|
"db",
|
||||||
|
"api",
|
||||||
|
"service",
|
||||||
|
"deploy",
|
||||||
|
"test",
|
||||||
|
"prod",
|
||||||
|
"backup",
|
||||||
|
"monitor",
|
||||||
|
"jenkins",
|
||||||
|
"webapp"
|
||||||
|
],
|
||||||
|
"suffixes": [
|
||||||
|
"",
|
||||||
|
"_prod",
|
||||||
|
"_dev",
|
||||||
|
"_test",
|
||||||
|
"123",
|
||||||
|
"2024",
|
||||||
|
"_backup",
|
||||||
|
"_admin",
|
||||||
|
"01",
|
||||||
|
"02",
|
||||||
|
"_user",
|
||||||
|
"_service",
|
||||||
|
"_api"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"passwords": {
|
||||||
|
"prefixes": [
|
||||||
|
"P@ssw0rd",
|
||||||
|
"Passw0rd",
|
||||||
|
"Admin",
|
||||||
|
"Secret",
|
||||||
|
"Welcome",
|
||||||
|
"System",
|
||||||
|
"Database",
|
||||||
|
"Secure",
|
||||||
|
"Master",
|
||||||
|
"Root"
|
||||||
|
],
|
||||||
|
"simple": [
|
||||||
|
"test",
|
||||||
|
"demo",
|
||||||
|
"temp",
|
||||||
|
"change",
|
||||||
|
"password",
|
||||||
|
"admin",
|
||||||
|
"letmein",
|
||||||
|
"welcome",
|
||||||
|
"default",
|
||||||
|
"sample"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"emails": {
|
||||||
|
"domains": [
|
||||||
|
"example.com",
|
||||||
|
"company.com",
|
||||||
|
"localhost.com",
|
||||||
|
"test.com",
|
||||||
|
"domain.com",
|
||||||
|
"corporate.com",
|
||||||
|
"internal.net",
|
||||||
|
"enterprise.com",
|
||||||
|
"business.org"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"api_keys": {
|
||||||
|
"prefixes": [
|
||||||
|
"sk_live_",
|
||||||
|
"sk_test_",
|
||||||
|
"api_",
|
||||||
|
"key_",
|
||||||
|
"token_",
|
||||||
|
"access_",
|
||||||
|
"secret_",
|
||||||
|
"prod_",
|
||||||
|
""
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"databases": {
|
||||||
|
"names": [
|
||||||
|
"production",
|
||||||
|
"prod_db",
|
||||||
|
"main_db",
|
||||||
|
"app_database",
|
||||||
|
"users_db",
|
||||||
|
"customer_data",
|
||||||
|
"analytics",
|
||||||
|
"staging_db",
|
||||||
|
"dev_database",
|
||||||
|
"wordpress",
|
||||||
|
"ecommerce",
|
||||||
|
"crm_db",
|
||||||
|
"inventory"
|
||||||
|
],
|
||||||
|
"hosts": [
|
||||||
|
"localhost",
|
||||||
|
"db.internal",
|
||||||
|
"mysql.local",
|
||||||
|
"postgres.internal",
|
||||||
|
"127.0.0.1",
|
||||||
|
"db-server-01",
|
||||||
|
"database.prod",
|
||||||
|
"sql.company.com"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"applications": {
|
||||||
|
"names": [
|
||||||
|
"WebApp",
|
||||||
|
"API Gateway",
|
||||||
|
"Dashboard",
|
||||||
|
"Admin Panel",
|
||||||
|
"CMS",
|
||||||
|
"Portal",
|
||||||
|
"Manager",
|
||||||
|
"Console",
|
||||||
|
"Control Panel",
|
||||||
|
"Backend"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"users": {
|
||||||
|
"roles": [
|
||||||
|
"Administrator",
|
||||||
|
"Developer",
|
||||||
|
"Manager",
|
||||||
|
"User",
|
||||||
|
"Guest",
|
||||||
|
"Moderator",
|
||||||
|
"Editor",
|
||||||
|
"Viewer",
|
||||||
|
"Analyst",
|
||||||
|
"Support"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"directory_listing": {
|
||||||
|
"files": [
|
||||||
|
"test.exe",
|
||||||
|
"backup.sql",
|
||||||
|
"database.sql",
|
||||||
|
"db_backup.sql",
|
||||||
|
"dump.sql",
|
||||||
|
"config.php",
|
||||||
|
"credentials.txt",
|
||||||
|
"passwords.txt",
|
||||||
|
"users.csv",
|
||||||
|
".env",
|
||||||
|
"id_rsa",
|
||||||
|
"id_rsa.pub",
|
||||||
|
"private_key.pem",
|
||||||
|
"api_keys.json",
|
||||||
|
"secrets.yaml",
|
||||||
|
"admin_notes.txt",
|
||||||
|
"settings.ini",
|
||||||
|
"database.yml",
|
||||||
|
"wp-config.php",
|
||||||
|
".htaccess",
|
||||||
|
"server.key",
|
||||||
|
"cert.pem",
|
||||||
|
"shadow.bak",
|
||||||
|
"passwd.old"
|
||||||
|
],
|
||||||
|
"directories": [
|
||||||
|
"uploads/",
|
||||||
|
"backups/",
|
||||||
|
"logs/",
|
||||||
|
"temp/",
|
||||||
|
"cache/",
|
||||||
|
"private/",
|
||||||
|
"config/",
|
||||||
|
"admin/",
|
||||||
|
"database/",
|
||||||
|
"backup/",
|
||||||
|
"old/",
|
||||||
|
"archive/",
|
||||||
|
".git/",
|
||||||
|
"keys/",
|
||||||
|
"credentials/"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"error_codes": [
|
||||||
|
400,
|
||||||
|
401,
|
||||||
|
403,
|
||||||
|
404,
|
||||||
|
500,
|
||||||
|
502,
|
||||||
|
503
|
||||||
|
]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user