Skip to content

Repository files navigation

Honeypot Pharmacy — Project README

Group 11 · Howest Hogeschool · group11.hp.edu.technet.howest.be


Lars Nolf, René Ruts, Giel Van Poppel and Dylan Van Goethem


Table of Contents

  1. Overview — Honeypot Working
  2. Overview — Secure Web Environment
  3. Overview — Dashboards
  4. Setup Guide

1. Overview — Honeypot Working

The project contains two distinct honeypots: a web-based admin panel decoy and a fake MySQL database server.

AdminPanel Honeypot

What is it?

The /adminpanel route is a honeypot a convincing but completely fake admin panel designed to lure and log attackers. The attacker will find the path at "robots.txt"

  • Looks identical to the real /admin panel
  • Touches zero real data because everything is hardcoded
  • Every action (delete, edit, create) is simulated
  • All suspicious activity is logged to Kibana via the security log channel

How it works

Visitor hits /adminpanel
        │
        ▼
HoneypotLogger middleware runs
        │
        ├── Not honeypot-authenticated? → redirect to login wall
        │
        └── Authenticated? → serve fake panel + log the visit

The honeypot has its own separate session-based authentication, completely independent of real Laravel auth.
Logging into the honeypot does not grant any real access.


Login behaviour

The fake login form accepts and reacts to several input types:

Input Result Kibana event
admin / B4n44n! Access granted login_success
SQLi pattern (e.g. ' OR 1=1 --) Simulated bypass login_sqli_bypass
XSS payload (e.g. <script>alert(1)</script>) Blocked warning shown login_xss_attempt
Wrong credentials Rejected attempts counted login_failed
5 failed attempts IP locked for 10 minutes ip_blocked_fail2ban
Retry during ban +1 minute added to ban login_blocked_retry

Fake fail2ban

After 5 failed login attempts, the attacker's IP is blocked for 10 minutes using Laravel's cache.

Every retry during the ban:

  • Adds +1 minute to the lockout timer
  • Shows the message:

"your ban just increased to X minutes."

A live countdown timer (MM:SS) ticks down in the browser. The lockout duration and hit count are both recorded in Kibana.


File structure

app/
└── Http/
    ├── Controllers/
    │   └── Honeypot/
    │       └── HoneypotController.php      ← all honeypot logic + fake data
    └── Middleware/
        └── HoneypotLogger.php              ← auth guard + security logging

resources/views/
└── adminpanel/
    ├── index.blade.php                     ← login wall / dashboard
    ├── metrics.blade.php                   ← fake site metrics page
    ├── products/
    │   ├── index.blade.php
    │   ├── create.blade.php
    │   └── edit.blade.php
    ├── users/
    │   └── index.blade.php
    └── orders/
        ├── index.blade.php
        └── show.blade.php

Fake data

All data is hardcoded in the controller no database queries are ever made.

Data Source
Products 60 real product names, brands & prices hardcoded as PHP arrays
Users 4 fake admins (@pharmaproject.com) + the real logged-in user as a non-admin
Orders Generated in-memory from the fake users and products

Delete behaviour

Deleting a product or user only removes it from the PHP session.
On the next page load or new session, everything reappears automatically.
Nothing is ever written to or deleted from the real database.


Logging

Every honeypot visit and action is logged to the security log channel using the same LogsActivity trait as the rest of the application keeping log shape consistent in Kibana.

Log events

Event Level Trigger
HONEYPOT access info Any GET request to /adminpanel/*
HONEYPOT access warning Any POST / PATCH / DELETE request
HONEYPOT: login_success info Correct credentials entered
HONEYPOT: login_failed warning Wrong credentials
HONEYPOT: login_sqli_bypass warning SQLi pattern matched
HONEYPOT: login_xss_attempt warning XSS pattern detected
HONEYPOT: ip_blocked_fail2ban warning IP locked after 5 failures
HONEYPOT: login_blocked_retry warning Retry attempt while banned

Kibana

Logs are shipped via Filebeat from storage/logs/security.log to Elasticsearch.

To view the logs go to the kibana dashboard called webhoneypot

Routes

All routes live inside the auth middleware group, prefixed with /adminpanel.

Route::prefix('adminpanel')->middleware(HoneypotLogger::class)->group(function () {

    Route::get('/',                          [HoneypotController::class, 'index']);
    Route::post('/login',                    [HoneypotController::class, 'loginPost']);

    // Products
    Route::get('/products',                  [HoneypotController::class, 'products']);
    Route::get('/products/create',           [HoneypotController::class, 'productCreate']);
    Route::post('/products',                 [HoneypotController::class, 'productStore']);
    Route::get('/products/{id}/edit',        [HoneypotController::class, 'productEdit']);
    Route::patch('/products/{id}',           [HoneypotController::class, 'productUpdate']);
    Route::delete('/products/{id}',          [HoneypotController::class, 'productDestroy']);
    Route::patch('/products/{id}/restore',   [HoneypotController::class, 'productRestore']);

    // Orders
    Route::get('/orders',                    [HoneypotController::class, 'orders']);
    Route::get('/orders/{id}',               [HoneypotController::class, 'orderShow']);

    // Users
    Route::get('/users',                     [HoneypotController::class, 'users']);
    Route::delete('/users/{id}',             [HoneypotController::class, 'userDestroy']);
    Route::patch('/users/{id}/restore',      [HoneypotController::class, 'userRestore']);
    Route::patch('/users/{id}/toggle-admin', [HoneypotController::class, 'userToggleAdmin']);

    // Metrics
    Route::get('/metrics',                   [HoneypotController::class, 'metrics']);

});

1.2 MySQL Honeypot (scripts/database.py)

A fake MySQL server runs on port 3306 using the mysql-mimic library. It presents itself as a real MySQL instance and accepts all credentials — every login attempt succeeds to maximise attacker engagement. The server logs:

  • Every login attempt with the supplied username and password.
  • Every SQL query with the source IP, username, active database, and the full query text.
  • Disconnection events. All events go to /var/log/mysql-honeypot.log.

To make the deception convincing, the server returns plausible fake data for common reconnaissance queries. Some examples are listed below:

Query Fake response
SHOW DATABASES pharmacy_db
SHOW TABLES products, users, logs, priv_prod
SELECT on products honey pharmacy products featured on our main webshop
SELECT on users Fake usernames with activity logs
SELECT on priv_prod Fictitious controlled substances (to lure illegal intent)
SELECT on logs Fabricated activity log with realistic IPs and timestamps

On top of that the honeypot also provides interactive commands the attacker can run. These commands will also result in the honeypot database being altered:

Query Fake response
DROP DATABASE <database_name> drops all contents of the given database
DELETE FROM <table_name> WHERE <condition> delete selected content from a provided table

Other commands the attacker tries to execute will result in a custom given error message:

ERROR 1045 (28000): Access not permitted from this IP address

1.3 Additional Threat Detection (Web Layer)

Beyond the honeypot itself, three other mechanisms contribute to threat detection:

Failed login tracking (LogFailedLogin listener): Every failed login attempt is logged as a warning. If the same email address fails 5 or more times within 10 minutes, an alert-level brute-force event is emitted.

Session IP monitoring (SecurityMonitor middleware): The user's IP is stored in the session on each request. If it changes mid-session, an alert is logged — catching session hijacking attempts.

Admin panel scanning detection (EnsureAdmin middleware): The real admin panel logs every access and tracks per-IP request counts per route. Exceeding 20 requests to the same admin route triggers a scanning alert.


2. Overview — Secure Web Environment

The application is a Laravel 12 pharmacy web shop. The following security measures are in place across the stack.

2.1 Transport Security (Nginx)

The Nginx configuration (config-main/) enforces a strict transport policy:

  • HTTPS-only: HTTP on port 80 is permanently redirected (301) to HTTPS.
  • TLS 1.3 only: Older TLS versions are disabled; server cipher preference is off, deferring to the client's ordering.
  • HTTP/3 (QUIC): Enabled alongside HTTP/2 on port 443 via Alt-Svc header advertisement.
  • server_tokens off: Nginx version is not disclosed in responses or error pages.

2.2 HTTP Security Headers

The following headers are set globally in nginx.conf and repeated for the /static/ location block:

Header Value Purpose
Cache-Control no-cache, no-store, must-revalidate Prevents browsers from caching sensitive responses
X-Content-Type-Options nosniff Blocks MIME-type sniffing
X-Frame-Options DENY Prevents clickjacking via iframes
Content-Security-Policy default-src 'self' Restricts resource loading to same origin

Static assets use Cache-Control: public, immutable with a 1-year expires header for aggressive caching, while maintaining the security headers above.

2.3 Rate Limiting

A login rate limit zone is defined in nginx.conf:

limit_req_zone $binary_remote_addr zone=mylimit:10m rate=10r/s;

This zone is applied to the /login/ location with a burst of 20 and nodelay, capping each IP to 10 login requests per second before requests are rejected.

2.4 Application Security (Laravel)

Authentication & authorisation

  • Role-based access control via an is_admin flag on the User model.
  • The EnsureAdmin middleware protects all /admin routes and returns a 404 (not 403) for non-admin users, avoiding route disclosure.
  • Sessions are stored server-side in the database driver with a 120-minute lifetime. Password policy

A custom StrongPassword validation rule (app/Rules/StrongPassword.php) enforces a minimum entropy of 72 bits, calculated from character set diversity (lowercase, uppercase, digits, symbols) and password length. This goes beyond simple length or complexity rules.

Image upload hardening

The ImageSanitizer service (app/Services/ImageSanitizer.php) prevents polyglot file attacks and steganography payloads:

  1. Magic-byte validation: only files starting with the JPEG (\xFF\xD8\xFF) or PNG (\x89PNG) signatures are accepted.
  2. Full decode and re-encode via PHP's GD library: the image is decoded to a pixel buffer and then written back as a new file, stripping any embedded scripts, metadata, or payloads in the original binary.
  3. Output is saved as WebP, so the extension cannot be used as an attack vector. Structured activity logging

All controllers use the LogsActivity trait to write structured JSON events to dedicated Monolog channels:

Channel Log file Events
security storage/logs/security.log Failed logins, brute force, honeypot hits, session anomalies
admin storage/logs/admin.log Admin panel access and mutations
orders storage/logs/orders.log Order creation and viewing
products storage/logs/products.log Product CRUD actions
users storage/logs/users.log User CRUD and role changes

Every log entry includes a timestamp, a per-request UUID (from X-Request-ID or generated), the authenticated user ID, IP address, user agent, route, and HTTP method.

CSRF protection

Laravel's built-in CSRF middleware is active on all state-changing routes. The _token field is excluded from honeypot log output to avoid capturing it.

SSL configuration

The server uses a TLS certificate issued for group11.hp.edu.technet.howest.be, stored at /etc/ssl/certs/group11-csr.crt with its key at /etc/ssl/private/server.key.


3. Overview — Dashboards

Five dashboards are configured in Kibana to monitor the web server, application layer, and honeypot activity. All dashboards are built on the group-11-filebeat index, fed by Filebeat running on the web server.

3.1 Nginx Traffic Overview

Monitors all incoming HTTP traffic to the Nginx web server. Built using the Filebeat Nginx module which automatically parses access and error logs into structured fields.

Panel Type Description
Requests over time Line chart Total HTTP requests per 30 seconds — useful for spotting traffic spikes during pentesting
HTTP status codes breakdown Donut chart Distribution of 200, 404, 500 and other response codes
Top IP addresses Table Most active client IPs by request count
Top requested URLs Table Most frequently accessed paths
Top user agents Table Browsers and tools making requests — reveals automated scanners

3.2 Security Overview

Focuses on suspicious Nginx activity, particularly directory scanning and application errors.

Panel Type Description
404 errors over time Line chart Filtered to 404 responses — spikes indicate directory brute forcing
500 errors over time Line chart Filtered to 500 responses — spikes indicate something breaking under attack
Top 404 URLs Table Paths returning 404 — reveals what attackers are scanning for
Top user agents on 404s Table Tools generating 404s — identifies automated scanners like Nikto or dirb

3.3 Laravel Security Overview

Monitors application-level events from Laravel's structured JSON logs, shipped from storage/logs/security.log, admin.log, orders.log, products.log, and users.log. All panels are filtered on fields.type: laravel.

Panel Type Description
Security events over time Line chart All Laravel log events over time — baseline of application activity
Security events by message type Table Distribution of event types such as failed logins, brute force detections, and session anomalies
Top IPs hitting the application Table Clients generating the most application-level events
Events by log channel Table Activity split across security, admin, orders, products and users channels
Failed logins over time Line chart Filtered to failed login messages — shows brute force attempts as they happen
Top users triggering security events Table Accounts most involved in security events

3.4 MySQL Honeypot

Monitors the fake MySQL server running on port 3306. Fed from /var/log/mysql-honeypot.log with a Filebeat dissect processor that parses each line into structured fields (dissect.honeypot.ip, dissect.honeypot.user, dissect.honeypot.database, dissect.honeypot.query). All panels are filtered on fields.type: honeypot.

Panel Type Description
Honeypot connections over time Line chart All honeypot events over time — shows when attackers discover and engage with the fake database
Top attacking IPs Table Hosts connecting to the fake MySQL server
Top queries executed Table SQL commands attackers are running
Top usernames used Table Credentials attackers are trying

3.5 Web Honeypot

Monitors activity on the fake admin panel at /adminpanel. Fed from storage/logs/security.log and filtered on context.honeypot: true.

Panel Type Description
Honeypot visits over time Line chart When attackers discover and probe the fake admin panel
GET vs mutating actions Donut chart Distinguishes passive browsing from active attacks such as deleting products or toggling admin status
Top attacking IPs Table Hosts interacting with the honeypot
Most visited honeypot routes Table Which fake admin pages attract the most attention
Attackers by email Table Authenticated users who access the honeypot
Severity over time Line chart Escalation from INFO browsing to WARNING mutations over time

4. Setup Guide

4.1 Prerequisites

Requirement Version
PHP 8.4
Composer 2.x
Node.js + npm LTS
Nginx With QUIC/HTTP3 support
Python 3.10+
SQLite 3.x (default DB)

4.2 Clone the Repository

git clone <your-gitlab-repo-url>
cd <repo-directory>

4.3 Install PHP Dependencies

composer install

4.4 Install Node Dependencies & Build Assets

npm install
npm run build

4.5 Configure the Environment

cp .env.example .env
php artisan key:generate

Open .env and set at minimum:

APP_URL=https://group11.hp.edu.technet.howest.be
APP_DOMAIN=group11.hp.edu.technet.howest.be
APP_ENV=production
APP_DEBUG=false

The application uses SQLite by default (DB_CONNECTION=sqlite). No additional database host configuration is needed unless you switch to MySQL.

4.6 Set Up the Database

php artisan migrate
php artisan db:seed

db:seed runs UserSeeder (creates default admin and test users) and ProductSeeder (populates the pharmacy product catalogue).

4.7 Create the Storage Symlink

php artisan storage:link

This makes storage/app/public/ accessible via the web at /storage/.

4.8 Set File Permissions

Ensure Nginx's worker process can read and write the necessary directories:

chown -R www-data:www-data storage bootstrap/cache
chmod -R 775 storage bootstrap/cache

4.9 Configure Nginx

Copy the provided Nginx configuration files to your server:

cp config-main/nginx.conf /etc/nginx/nginx.conf
cp config-main/default.conf /etc/nginx/conf.d/default.conf

Place your TLS certificate and key:

/etc/ssl/certs/group11-csr.crt
/etc/ssl/private/server.key

Test and reload Nginx:

nginx -t
systemctl reload nginx

4.10 Set Up PHP-FPM

The Nginx config expects PHP 8.4 FPM on a Unix socket:

/run/php/php8.4-fpm.sock

Ensure the php8.4-fpm service is running:

systemctl enable php8.4-fpm
systemctl start php8.4-fpm

4.11 Start the MySQL Honeypot

Install the Python dependency and start the fake MySQL server:

pip install mysql-mimic
python3 scripts/database.py &

The honeypot will listen on 0.0.0.0:3306 and write all activity to /var/log/mysql-honeypot.log. Make sure port 3306 is reachable from outside (or inside your test network as appropriate).

To run it as a persistent background service, create a systemd unit:

# /etc/systemd/system/mysql-honeypot.service
[Unit]
Description=MySQL Honeypot
After=network.target

[Service]
Type=simple
User=root
ExecStart=/usr/bin/python3 /opt/mysql-honeypot/mysql-honeypot.py
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target
systemctl enable mysql-honeypot
systemctl start mysql-honeypot

4.12 Configure the Log Directory

Ensure the storage/logs/ directory is writable and pre-create the log files if needed:

touch storage/logs/security.log
touch storage/logs/admin.log
touch storage/logs/orders.log
touch storage/logs/products.log
touch storage/logs/users.log
chown www-data:www-data storage/logs/*.log

4.13 Verify the Installation

Open a browser and navigate to https://group11.hp.edu.technet.howest.be. You should see the pharmacy storefront login page.

To verify the honeypot, log in with any account and navigate to /adminpanel. Check that a new entry appears in storage/logs/security.log:

tail -f storage/logs/security.log

To verify the MySQL honeypot:

mysql -h <server-ip> -u anyuser -p
# Enter any password — connection should succeed
SHOW DATABASES;
SELECT * FROM pharmacy_db.priv_prod;

Check /var/log/mysql-honeypot.log for the recorded credentials and queries.

About

A honeypot project built during the 4th semester of the Bachelor Cybersecurity at Howest. A Laravel pharmacy web shop embedded with a fake admin panel and a fake MySQL server, monitored in real time through an Elastic Stack (Filebeat, Elasticsearch, Kibana) pipeline, in a group of 4.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages