Building a real-world web honeypot for CVE-2019–6340 (RCE in Drupal core)

Heading
6 min readApr 9, 2019

A while ago I started a project for managing real-word web honeypots. I initially built it to manage some WordPress honeypots but after Drupalgeddon2 came out I started a rework. Unfortunately, it needed quite a long time to finally release it. But I am finally able to share my efforts: https://gitlab.com/SecurityBender/webhoneypot-framework.

The Webhoneypot Framework is written in Python 3 and needs docker and docker-compose to run. A honeypot can be configured using a JSON and a corresponding docker-compose file. Whereas the docker-compose file describes the containers and their setup used for the honeypot the JSON file is used to configure how the framework detects attacks and takes snapshots of the honeypot.

The basic structure of the JSON configuration looks like the following:

{
"docker_compose_file": "./docker-compose.yml",
"pre_start": "...",
"post_start": "...",
"snapshot_path": "./snapshots",
"snapshots": [
...
],
"detections": [
...
]
}

The docker_compose_file option is pretty self-explanatory. With the options pre_start and post_start one is able to execute scripts before starting respectively after starting a honeypot. I usually use this to adjust file permissions and file ownership of the web root.

The main function of the framework is to create snapshots of the honeypot after an attack is detected. Currently, it is possible to create snapshots of directories or of MySQL databases.

To take a MySQL snapshot one should add the mysql_snapshot option under snapshots. This creates a MySQL dump of a standard MySQL container (https://hub.docker.com/_/mysql):

{
"type": "mysql_snapshot",
"mysql_container_name": "webhoneypot_mysql_1",
"mysql_user": "root",
"mysql_password": "Password123!",
"mysql_restore_path": "./mysql/config"
}

The MySQL container from the docker library allows restoring of database dumps by placing a file into the /docker-entrypoint-initdb.d folder of the container. So the mysql_restore_path option should point to the mount point of this directory.

Another option is to create directory snapshots with the folder_snapshot option:

{
"type": "folder_snapshot",
"folder_path": "./apache/html"
},

This will create a ZIP archive of the specified folder and place it in the configured snapshot folder.

To detect an attack the webhoneypot framework currently only supports folder_changed and file_contains.

The detection method folder_changed is very basic and detects arbitrary file changes in comparison to the initial snapshot:

{
"type": "folder_changed",
"folder_path": "./apache/html",
"ignore_files": [
"folder_to_ignore/"
]
}

The folder_path option must match one that is specified in a folder_snapshot block. To get rid of some noise it is possible to ignore certain files or folders.

The second detection method file_contains checks if the specified file contains a specific string or regex:

{
"type": "file_contains",
"file_path": "./nginx/logs/access.log",
"pattern": "pattern to search"
}

To run the script simply clone the repository and run webhoneypot.py:

$ git clone https://gitlab.com/SecurityBender/webhoneypot-framework.git && cd webhoneypot-framework
$ ./webhoneypot.py -h
usage: webhoneypot.py [-h] -c CONFIG option
Management script for docker-based web application honeypotspositional arguments:
option the honeypot option, can be
[start|stop|init|update|reset]
optional arguments:
-h, --help show this help message and exit
-c CONFIG, --config CONFIG
the honeypot configuration file

The script supports five options:

  • start: start the honeypot containers
  • stop: stop the honeypot containers
  • init: initialize the honeypot (e.g. first setup) and create an initial snapshot
  • update: update the honeypot and create a new initial snapshot
  • reset: snapshot the honeypot, detect changes and restore the initial snapshot

Capturing CVE-2019–6340 in the wild

Now with the basic in mind, we are able to build and configure a Drupal honeypot to capture CVE-2019–6340 attempts. CVE-2019–6340 is a RCE vulnerability in the Drupal core which lets an attacker execute arbitrary commands on the host system.

Configuring the honeypot

I already made a configuration for building Drupalgeddon2 honeypots. It is available on GitLab. Let’s clone it and prepare it for CVE-2019–6340.

$ git clone https://gitlab.com/SecurityBender/webhoneypot-drupal.git
$ cd webhoneypot-drupal

In the directory we have several directories and files:

webhoneypot-drupal/
├── apache/
│ └── Dockerfile
├── nginx/
│ ├── config/
│ │ └── default.conf
│ └── Dockerfile
├── docker-compose.yml
├── drupal.json
└── drupal.sh

The apache and nginx directories contain the data and configuration files for the Apache and nginx containers, respectively. The most important files are docker-compose.yml and drupal.json.

The docker-compose.yml contains all information to start the necessary containers:

version: '2'

services:
apache:
build: apache/
volumes:
- ./apache/html:/var/www/html
- ./apache/tmp:/tmp
networks:
- webhoneypot

nginx:
build: nginx/
volumes:
- ./nginx/config/default.conf:/etc/nginx/conf.d/default.conf:ro
- ./nginx/logs:/var/log/nginx
ports:
- "80:80"
networks:
- webhoneypot
depends_on:
- apache

networks:
webhoneypot:
driver: bridge

In this setup, we have an Apache container which hosts and runs Drupal and a nginx which is used as a reverse proxy. We mount the html and the tmp directories to the Apache container to make them easily accessible from the host and to let us take a snapshot and restore them. We also mount the log directory to the nginx container for the same reasons.

The main configuration is done in drupal.json:

{
"docker_compose_file": "./docker-compose.yml",
"pre_start": "",
"post_start": "./drupal.sh",
"snapshot_path": "./snapshots",
"snapshots": [
{
"type": "folder_snapshot",
"folder_path": "./apache/html"
},
{
"type": "folder_snapshot",
"folder_path": "./apache/tmp"
},
{
"type": "folder_snapshot",
"folder_path": "./nginx/logs"
}
],
"detections": [
{
"type": "folder_changed",
"folder_path": "./apache/html",
"ignore_files": [
"sites/default/files/php/twig/"
]
},
{
"type": "folder_changed",
"folder_path": "./apache/tmp",
"ignore_files": [
]
},
{
"type": "file_contains",
"file_path": "./nginx/logs/access.log",
"pattern": "node/1\\?_format=hal_json"
}
]
}

At first, we specify with docker_compose_file the file that is used to create, start and stop the docker containers via docker-compose. After every start of the containers, drupal.sh is executed to adjust the owner and rights of the ./apache/html (the web root) directory and clearing the nginx access logs. Additionally, we configure the honeypot to take snapshots of the ./apache/html, ./apache/tmp and ./nginx/logs directories after each reset. I noticed that a lot of Drupalgeddon2 exploits drop files in /tmp so I added it.

The most critical part are the detections. As you might see we want to detect changes in the ./apache/html and the ./apache/tmp directories. All files that differ from the initial snapshot are detected. Because Drupal stores some generated files in sites/default/files/php/twig/, we ignore any changes to this directory. Another option for detection is to check a file for containing a specific regular expression. In this case, we monitor the nginx access log for the characteristic CVE-2019–6340 url.

Running the honeypot

After we have configured the honeypot we are ready to set it up. Quickly grep a vulnerable Drupal version (e.g. 8.6.9) and unpack it in the ./apache/html directory:

$ wget -q https://ftp.drupal.org/files/projects/drupal-8.6.9.zip
$ unzip drupal-8.6.9.zip -d ./apache/
$ mv ./apache/drupal-8.6.9 ./apache/html

Now let’s clone the Webhoneypot Framework and initialize the Drupal honeypot. Using ./webhoneypot.py -c ../webhoneypot-drupal/drupal.json init the script will start and build the required containers.

$ ./webhoneypot.py -c ../webhoneypot-drupal/drupal.json init
2019-04-07 15:11:50,455 - DEBUG - Starting docker compose /opt/webhoneypot-drupal/docker-compose.yml ...
Creating network "webhoneypot-drupal_webhoneypot" with driver "bridge"
Building apache
[...]
Building nginx
[...]
Creating webhoneypot-drupal_apache_1 ... done
Creating webhoneypot-drupal_nginx_1 ... done
Press any key if honeypot setup is finished

The initial startup succeeded and we are able to access the Drupal instance over the public IP address. After the configuration (enabling RESTful service, etc.) — which is harder than I thought — is finished we press ENTER and the script will start creating an initial snapshot of the honeypot.

2019-04-07 15:23:17,604 - DEBUG - Creating snapshot "initial" ...
2019-04-07 15:23:17,621 - DEBUG - Create folder backup of "/opt/webhoneypot-drupal/apache/html" to "/opt/webhoneypot-drupal/snapshots/initial/_opt_webhoneypot-drupal_apache_html.zip" ...
2019-04-07 15:23:48,775 - DEBUG - Create folder backup of "/opt/webhoneypot-drupal/apache/tmp" to "/opt/webhoneypot-drupal/snapshots/initial/_opt_webhoneypot-drupal_apache_tmp.zip" ...
2019-04-07 15:23:48,787 - DEBUG - Create folder backup of "/opt/webhoneypot-drupal/nginx/logs" to "/opt/webhoneypot-drupal/snapshots/initial/_opt_webhoneypot-drupal_nginx_logs.zip" ...

Testing the honeypot

Everything is now running fine and we are able to test it. I grabbed a POC from @leonjza and ran it:

$ python3 poc.py http://<my-ip>/ "id"     
CVE-2019-6340 Drupal 8 REST Services Unauthenticated RCE PoC
by @leonjza
References:
https://www.drupal.org/sa-core-2019-003
https://www.ambionics.io/blog/drupal8-rce
[warning] Caching heavily affects reliability of this exploit.
Nodes are used as they are discovered, but once they are done,
you will have to wait for cache expiry.
Targeting http://37.120.165.218/...
[+] Finding a usable node id...
[x] Node enum found a cached article at: 1, skipping
[+] Using node_id 2
[+] Target appears to be vulnerable!
uid=33(www-data) gid=33(www-data) groups=33(www-data)

It worked. Let’s now see if our detection is working:

$ ./webhoneypot.py -c ../webhoneypot-drupal/drupal.json reset
2019-04-07 17:04:11,767 - DEBUG - Creating snapshot "20190407_170411" ...
[...]
2019-04-07 17:04:48,115 - DEBUG - Stopping docker compose /opt/docker/webhoneypot-drupal/docker-compose.yml ...
Stopping webhoneypot-drupal_nginx_1 ... done
Stopping webhoneypot-drupal_apache_1 ... done
Removing webhoneypot-drupal_nginx_1 ... done
Removing webhoneypot-drupal_apache_1 ... done
Removing network webhoneypot-drupal_webhoneypot
2019-04-07 17:05:08,442 - INFO - Pattern "node/1\?_format=hal_json" detected in file!
2019-04-07 17:05:08,443 - DEBUG - Restoring snapshot "initial" ...
[...]

And there it is. A working real world Drupal honeypot to detect CVE-2019–6340 attacks.

Caution

A compromised honeypot can be used for a lot of bad stuff (e.g. crypto mining, sending spam, DoS, you name it). I highly recommend to set up a regular cronjob (every 15 to 30 minutes) that resets the honeypot. Additionally, you can restrict the CPU time of the container or block some outgoing connection via the host firewall.

--

--