Honeypot using Cowrie and ELK stack

After acquiring a vm from azure’s student plan, I wanted to put it to good use, so I decide to build a small honeypot using Cowrie and ELK to sharpen my SOC and analysis skills. and also to troll.

interface


What’s a Honeypot?

A honeypot is a decoy system designed to look like a real target. It doesn’t serve any real purpose , it exists purely to attract attackers, log everything they do, and help you understand how they operate.

I used Cowrie, an SSH honeypot that pretends to be a vulnerable Linux server. When an attacker connects, Cowrie lets them “log in” with almost any credentials, records every command they type, and logs every file they try to download while they think they’re on a real machine. very funny.


The Stack

Here’s everything running on a single Azure VPS (Ubuntu 24.04, Standard D2s v3 — 2 vCPUs, 8GB RAM):

Internet → Cowrie (SSH honeypot, port 2222)
         → Filebeat (ships JSON logs)
         → Logstash (parses, enriches with GeoIP + DNS)
         → Elasticsearch (stores everything)
         → Kibana (visualizes it)
         → Nginx (reverse proxy to Kibana)
         → Discord (real-time alerts via webhook) (i'm not a discord mod trust)

Even tho it’s all on one machine and not production-grade, but its perfect for my use case.


Setting Up Cowrie

Cowrie runs as a Python application. I installed it under a dedicated user (cowrie) in ~/cowrie. The key config change is enabling JSON output so the ELK stack can consume it:

[output_json]
enabled = true

Cowrie logs everything to ~/cowrie/var/log/cowrie/cowrie.json. A single connection generates multiple events ; connect, login attempt, commands, disconnect ; each as a JSON line.


Installing the ELK Stack

Elastic 8.x is on apt. one thing to note: unlike older versions, Elasticsearch 8 uses HTTPS and requires authentication by default. that might cause you trouble i’m just saying.

wget -qO - https://artifacts.elastic.co/GPG-KEY-elasticsearch | \
  sudo gpg --yes --dearmor -o /usr/share/keyrings/elasticsearch-keyring.gpg

echo "deb [signed-by=/usr/share/keyrings/elasticsearch-keyring.gpg] \
  https://artifacts.elastic.co/packages/8.x/apt stable main" | \
  sudo tee /etc/apt/sources.list.d/elastic-8.x.list

sudo apt-get update
sudo apt-get install -y elasticsearch logstash kibana filebeat nginx apache2-utils

after installing, elasticsearch auto-generates a password for the elastic user. get it with:

sudo /usr/share/elasticsearch/bin/elasticsearch-reset-password -u elastic

save it to be able to login later.

Single-node config

Edit /etc/elasticsearch/elasticsearch.yml:

discovery.type: single-node

Connecting Kibana

kibana 8 has an enrollment flow. generate a token, enroll, get a verification code, simple as dat:

sudo /usr/share/elasticsearch/bin/elasticsearch-create-enrollment-token -s kibana
sudo systemctl start kibana
sudo /usr/share/kibana/bin/kibana-setup --enrollment-token YOUR_TOKEN

Then access the setup page in your browser. One thing that caused me agony to figure out: if you go through the interactive browser enrollment, stale session cookies will cause an “unauthenticated” loop on login even with correct credentials. Always use a fresh incognito window. and if that doesn’t help, i figured out the solution is adding this header to the nginx reverse proxy and it solved it. it strips nginx auth from kibana

proxy_set_header Authorization "";

Nginx reverse proxy

Kibana listens on localhost:5601. I put Nginx in front on port 8080 with basic auth as an extra layer:

sudo htpasswd -c /etc/nginx/htpasswd.users admin_kibana
server {
    listen 8080;

    auth_basic "Restricted Access";
    auth_basic_user_file /etc/nginx/htpasswd.users;

    location / {
        proxy_pass http://localhost:5601;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header Authorization "";   # this is what i mentioned earlier 
    }
}

This took me a while to figure out because of the auth issue, because nginx forwards its own auth header to kibana which tries to use admin_kibana as elasticsearch user, but alhamdulilah i solved it.


Logstash Pipeline

Cowrie ships a Logstash config in its docs/elk/ folder. I copied it and extended it with human-readable summaries.

The pipeline does four things:

  1. Receives logs from Filebeat on port 5044
  2. Parses the JSON into structured fields under honeypot.*
  3. Enriches with GeoIP (MaxMind GeoLite2-City) and reverse DNS
  4. Adds summary fields I defined

The GeoIP enrichment is particularly useful, every attacking IP gets a country, city, region, and coordinates attached, which feeds the map visualization in kibana, maybe i did that to look like a cool hackerman but who cares :)

one important change for Elastic 8.x , the Elasticsearch output needs credentials and HTTPS:

output.elasticsearch {
    hosts => ["https://localhost:9200"]
    user => "elastic"
    password => "urpassword"
    ssl_enabled => true
    ssl_verification_mode => "none"
}

Filebeat

Filebeat watches the Cowrie JSON log file and ships events to Logstash. The key parts of filebeat.yml:

filebeat.inputs:
- type: log
  enabled: true
  paths:
    - /home/cowrie/cowrie/var/log/cowrie/cowrie.json*

output.logstash:
  hosts: ["localhost:5044"]

make sure output.elasticsearch is disabled you want logs going to Logstash for enrichment, not directly to Elasticsearch, otherwise it’ll just be a EK stack lol.


Real-Time Discord Alerts

I added a Discord webhook to Logstash so I get notified in real time whenever someone connects, attempts a login, or runs a command. The webhook fires as a rich embed with the event type, source IP and timestamp.

This required the logstash-output-http plugin, which fortunately ships bundled with Logstash 8.x, so no installation needed.


What I found

now onto the juicy part! within the first few hours, the honeypot was already logging connections. Here’s a real event from the first day:

{
  "honeypot.eventid": "cowrie.session.closed",
  "honeypot.src_ip": "220.197.14.60",
  "source.geo.country_name": "China",
  "source.geo.city_name": "Guiyang",
  "source.geo.region_name": "Guizhou",
  "honeypot.duration_ms": 120089,
  "summary": "Session closed after 120089ms from 220.197.14.60"
}

an IP from Guiyang, Guizhou province, China connected and held the session open for exactly 2 minutes (hitting the timeout) this is classic automated scanner behaviour: connect, try some credentials, wait for a response, move on.

CHANGEMENT

I changed cowrie’s port to 22 (default ssh port), and that’s when the real traffic started coming in, lots of automated bots that ran scans, connected, then deconnected such as this one:

bot

Aaand another:
gottem

We could see this one actually used default credentials to log in: creds

What caught my attention is that attacker’s run commands blazingly fast, to explore the file system, one even tried to check the processsor it has, and another checked for nvidia graphics (maybe for crypto mining)

cmds

Additional Info

I actually intended to use Wazuh as a SIEM to monitor the honeypot, but I encountered a LOT of issues trying to link it with cowrie’s logs, and ended up drowning in a sea of config files, hell naw. i ain’t doing all that.

BUT!

Wazus was running on my VPS and monitoring it (not the cowrie system cuz i didn’t figure that out), and it did catch some bruteforce attempts, here are some pictures:

bruteforce
auth
details

That’s interesting ain’t it!

What I learned

  • Configuring a VPS: configuring my vps for a specific purpose by managing network ports, different rules .. etc
  • Logging, Alerting, SIEM workflow: this small experiment taught me how to monitor, log, use SIEMs (especially Wazuh which is an actual entreprise level SIEM)
  • Reverse Proxy Setup
  • Attackers are everywhere: i didn’t advertise this server anywhere. it showed up in automated scans within minutes of being reachable.

References

if you want to set this up yourself, the Cowrie docs are at cowrie.readthedocs.io and the ELK-specific setup is at docs.cowrie.org/en/latest/elk/README.html.