
Originally a Honeypot for CVE-2026-20131
A low-interaction honeypot that mimics a Cisco Secure Firewall Management Center (FMC) web surface and captures unauthenticated Java serialization-style attack traffic (CVE-style insecure deserialization probes).
\xac\xed\x00\x05 (and Base64 rO0AB…) and extracts shell or URL strings using regular expressions and heuristics.requests into a non-executable downloads volume.Single-line JSON logs (compatible with Splunk and ELK), optional Telegram alerts, hashed second-stage artifacts, and a non-root container user. The project is intended for researchers who require a focused FMC-style trap without additional integration code.
git clone https://github.com/Hassan-Pouladi/Cisco-FMC-honeypot
cd FMC-Trap
cp .env.example .env
Edit .env as required (see Environment variables). For deployments behind the bundled Caddy, set TRUST_X_FORWARDED_FOR=1 so that client IP addresses in logs reflect the original client.
Edit Caddyfile: replace the placeholder hostnames with your domain (and www if applicable):
example.com, www.example.com {
reverse_proxy fmc-trap:5000
}
Optionally, uncomment the global email [email protected] block in Caddyfile for Let’s Encrypt expiry notices.
yourdomain.com to your server’s public IP, and www if used.ufw allow 80,443/tcp).The application runs as UID 10001 inside the container. The bind-mounted ./data directory must be writable:
mkdir -p data/logs data/downloads
sudo chown -R 10001:10001 data/logs data/downloads
Without correct ownership, exploit POST requests may return 500 (Permission denied when writing logs or raw blobs).
docker compose up -d --build
docker compose ps
curl -sI https://yourdomain.com/
Expect HTTP/2 200 (or 301 or 302) and Via: Caddy. In a browser, https://yourdomain.com/ should display the simulated FMC login page.
For where to read events, payloads, and Docker output, see Finding logs, downloads, and service output and Common operations.
Everything below is relative to your project directory (the folder that contains docker-compose.yml, usually FMC-Trap after you clone). Paths are on the host; Docker bind-mounts them into the container.
When something appears in the log file: fmc-trap.json receives one JSON object per line when a request matches deserialization probe behavior (magic bytes in a POST). Ordinary GET requests to the fake login page are not written to this file by default.
When the downloads folder has files: entries appear only if the pipeline extracts a URL from a probe and the harvester successfully fetches allowed remote content within MAX_DOWNLOAD_MB. If the directory stays empty, there may be no qualifying URLs yet, or fetches may be blocked by policy in the application.
Pretty-printing JSON (optional): for a single line, pipe through python -m json.tool or use jq if installed.
Internet :443/:80 → Caddy (TLS, Let’s Encrypt) → fmc-trap:5000 (Gunicorn/Flask)
↓
./data/logs, ./data/downloads (bind mounts)
expose: "5000" in Compose).Compose passes these variables into the fmc-trap service; refer to docker-compose.yml for details.
Each line in data/logs/fmc-trap.json is one JSON object. Common keys include timestamp, event_type, src_ip, headers, raw_payload_hash, extracted_commands, and downloaded_file_hash when a file was stored. Saved blobs under data/downloads/ use mode 0400 (read-only for the owner).
To reach Gunicorn directly without Caddy, add the following under fmc-trap in docker-compose.yml:
ports:
- "127.0.0.1:5000:5000"
Do not bind the application to 0.0.0.0 in production when Caddy terminates TLS.
.env on the host (never commit .env).Deploy only on systems you own or on which you are explicitly authorized to deploy instrumentation. Honeypots may collect attack traffic and malware; comply with applicable law and organizational policy. This software is intended for security research.
| Item | Notes |
|---|
| Docker and Docker Compose v2 | Install Docker |
| VPS or host | A dedicated or isolated network segment is recommended |
| Domain (optional) | For HTTPS: configure DNS and allow inbound TCP 80 and TCP 443 to the host |
| What | Host path | How to open or follow |
|---|
| Event log (JSON lines) | data/logs/fmc-trap.json | tail -f data/logs/fmc-trap.json (live stream), or open in an editor, or ingest into Splunk or ELK |
| Downloaded second-stage payloads | data/downloads/ | Files appear only when the harvester saves a remote response; each file is named with its SHA256 hash (64 hexadecimal characters), no extension. List with ls -la data/downloads (Linux or macOS) or Get-ChildItem data\downloads (Windows PowerShell) |
| Application process output | Not a file; Docker | docker compose logs -f fmc-trap |
| Caddy (TLS, proxy, ACME) | Not a file; Docker | docker compose logs -f caddy |
| Telegram alerts (optional) | Not stored in data/ | Set TELEGRAM_BOT_TOKEN and TELEGRAM_CHAT_ID in .env; alerts fire when events are logged |
| Goal | Command or action |
|---|
| See whether containers are running | docker compose ps |
Restart after editing .env or Caddyfile | docker compose up -d --build (or docker compose up -d --force-recreate if only env changed) |
| Stop the stack | docker compose down |
| Follow new JSON events continuously | tail -f data/logs/fmc-trap.json |
| Inspect the last few events | tail -n 50 data/logs/fmc-trap.json |
| Count downloaded artifacts | Open data/downloads in your file manager, or use a shell file count on that directory (files are named by SHA256) |
| Confirm the honeypot page loads | Open https://yourdomain.com/ in a browser or use curl -sI as in the verify step |
| Variable | Purpose | Default / note |
|---|
TRUST_X_FORWARDED_FOR | Use the first X-Forwarded-For hop as src_ip | 1 is recommended with Caddy |
DECEPTION_STATUS | 200 returns an HTML body; 302 returns a redirect | 200 |
DECEPTION_REDIRECT_URL | Target path when status is 302 | / |
MAX_DOWNLOAD_MB | Harvester limit per URL | 16 |
TELEGRAM_BOT_TOKEN / TELEGRAM_CHAT_ID | Optional alerts | Empty disables alerts |
FMC_SERVER_HEADER | Server response header | nginx |
VT_API_KEY | Reserved | Optional |
| Symptom | Resolution |
|---|
Permission denied when writing under data/ | Run sudo chown -R 10001:10001 data/logs data/downloads |
Bind for 0.0.0.0:80 failed: port is already allocated | Another service or container is bound to 80. Stop that service or free 80; only one process may listen on 80 per host. |
| Let’s Encrypt or ACME errors in Caddy logs | Confirm that DNS resolves to this server and that 80 and 443 are reachable from the internet. Compose configures DNS 1.1.1.1 and 8.8.8.8 for Caddy when the host resolver fails inside Docker. |
curl to HTTPS returns empty output or fails on first attempt | Wait until certificate obtained successfully appears in docker compose logs caddy. |
Client IP in logs is 172.x.x.x | Set TRUST_X_FORWARDED_FOR=1 when using Caddy, then recreate containers: docker compose up -d --force-recreate. |