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
|
||||
Krawl is a Web Honeypot & Deception server that aims to foul enumerations, web crawling, fuzzing and bruteforcing
|
||||
<h1 align="center">🕷️ Krawl</h1>
|
||||
|
||||
<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